OWASP Top 10:2025 kodenivå-mønstre for Kotlin, Go, Java og Node.js — tilgangskontroll, forsyningskjede, injeksjon og feilhåndtering
Install
mkdir -p .claude/skills/security-owasp && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16643" && unzip -o skill.zip -d .claude/skills/security-owasp && rm skill.zipInstalls to .claude/skills/security-owasp
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
OWASP Top 10:2025 kodenivå-mønstre for Kotlin, Go, Java og Node.js — tilgangskontroll, forsyningskjede, injeksjon og feilhåndtering131 charsno explicit “when” trigger
About this skill
OWASP Top 10:2025 — Code-Level Security
Tactical security patterns for Kotlin, Go, Java, and Node.js on NAIS, aligned with the 2025 OWASP Top 10.
Complements @security-champion-agent (architecture-level threat modeling) and the security-review skill (scanning tools).
Full code examples for each category: see
examples.mdin this skill directory.
A01: Broken Access Control (incl. SSRF)
// ❌ IDOR — trusts user-supplied ID without ownership check
get("/api/vedtak/{id}") {
val vedtak = vedtakRepository.findById(call.parameters["id"]!!.toLong())
call.respond(vedtak)
}
// ✅ Verify ownership before returning resource
get("/api/vedtak/{id}") {
val bruker = call.hentBruker()
val vedtak = vedtakRepository.findById(call.parameters["id"]!!.toLong())
?: return@get call.respond(HttpStatusCode.NotFound)
if (vedtak.brukerId != bruker.id) return@get call.respond(HttpStatusCode.Forbidden)
call.respond(vedtak.toDTO())
}
// ✅ SSRF prevention — validate outbound URL against allowlist
func fetchExternal(targetURL string) error {
parsed, err := url.Parse(targetURL)
if err != nil { return err }
if !isAllowedHost(parsed.Host) { return fmt.Errorf("host not allowed: %s", parsed.Host) }
// proceed with request
}
- Deny by default — require explicit grants, not explicit denials
- Resource-level checks — not just "is authenticated" but "owns this resource"
- M2M tokens — validate
azpclaim againstAZURE_APP_PRE_AUTHORIZED_APPS - SSRF — validate outbound URLs; use Nais
accessPolicy.outboundas defense-in-depth
A02: Security Misconfiguration
// ❌ Open CORS
install(CORS) { anyHost() }
// ✅ Restrict to known origins
install(CORS) { allowHost("my-app.intern.nav.no", schemes = listOf("https")) }
// ❌ Debug endpoint exposed on public ingress
mux.HandleFunc("/debug/pprof/", pprof.Index)
// ✅ Debug endpoints on separate internal-only port (Nais handles this)
internalMux := http.NewServeMux()
internalMux.HandleFunc("/debug/pprof/", pprof.Index)
go http.ListenAndServe(":9090", internalMux) // not exposed via ingress
- CORS restricted to known origins — never
*oranyHost() - Debug/admin endpoints not on public ingress
- Error responses sanitized — no stack traces, SQL errors, or file paths to client
- Default-deny Nais
accessPolicy— explicit inbound/outbound only
A03: Software Supply Chain Failures (NEW in 2025)
// go.sum provides integrity verification — always commit it
// Use govulncheck for known vulnerabilities
// $ govulncheck ./...
// ✅ Pin dependencies to exact versions in go.mod
require (
golang.org/x/crypto v0.31.0
github.com/jackc/pgx/v5 v5.7.2
)
// build.gradle.kts — use dependency locking and BOM
dependencyLocking { lockAllConfigurations() }
dependencyManagement {
imports { mavenBom("org.springframework.boot:spring-boot-dependencies:3.4.1") }
}
// Run: ./gradlew dependencies --write-locks
# ✅ GitHub Actions — pin to full SHA, never @main or floating tags
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# ❌ Vulnerable to supply chain attack
- uses: actions/checkout@main
- Pin all dependencies to exact versions; use lockfiles
- Scan dependencies:
govulncheck ./...,trivy repo .,./gradlew dependencyCheckAnalyze - GitHub Actions pinned to full commit SHA (not tags)
- Generate SBOM for production artifacts when possible
- Prefer well-maintained, first-party packages
A04: Cryptographic Failures
// ❌ Disabling TLS verification
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// ✅ Default TLS config (Go enforces TLS 1.2+ by default)
client := &http.Client{}
// ❌ Weak hashing for passwords
val hash = MessageDigest.getInstance("MD5").digest(password.toByteArray())
// ✅ bcrypt for password hashing
val hashed = BCrypt.hashpw(password, BCrypt.gensalt(12))
- Passwords: bcrypt (cost ≥ 12) or argon2id — never MD5/SHA-1/SHA-256
- Secrets: always from Nais environment variables or Secret resources — never hardcoded
- TLS 1.2+: never set
InsecureSkipVerify: true - Encryption: AES-256-GCM; rotate keys periodically
A05: Injection
// ❌ SQL injection via string template
session.run(queryOf("SELECT * FROM vedtak WHERE status = '$status'").map { ... }.asList)
// ✅ Parameterized query (Kotliquery)
session.run(queryOf("SELECT * FROM vedtak WHERE status = ?", status).map { ... }.asList)
// ❌ Command injection via shell
exec.Command("sh", "-c", fmt.Sprintf("process %s", userInput)).Run()
// ✅ Pass arguments directly (no shell interpretation)
exec.Command("process", userInput).Run()
- All SQL queries parameterized (
?/$1) — never string concatenation - No shell execution with user-controlled input
- Validate/sanitize all external input at service boundary
A09: Security Logging and Alerting Failures
// ✅ Structured logging with correlation ID, no PII
log.info("Vedtak opprettet", kv("vedtakId", vedtak.id), kv("sakId", sak.id))
// ❌ PII in logs — GDPR violation
log.info("Vedtak for bruker ${bruker.fnr}")
- No PII in logs (fnr, name, address, tokens)
- Audit trail for sensitive operations (vedtak, utbetaling, tilgang)
- Correlation IDs propagated across services (OpenTelemetry trace context)
- Alerting on anomalous patterns (auth failures, rate spikes)
A10: Mishandling of Exceptional Conditions (NEW in 2025)
// ❌ Panic leaks to caller, crashes service
func processRequest(data []byte) Result {
var req Request
json.Unmarshal(data, &req) // ignores error, req may be zero-value
return handle(req)
}
// ✅ Handle errors explicitly, fail safely
func processRequest(data []byte) (Result, error) {
var req Request
if err := json.Unmarshal(data, &req); err != nil {
return Result{}, fmt.Errorf("invalid request payload: %w", err)
}
return handle(req)
}
// ❌ Swallowing exceptions silently
fun process(data: String): Result {
try { return parse(data) }
catch (e: Exception) { return Result.empty() } // silent failure, no logging
}
// ✅ Log, wrap, and surface errors appropriately
fun process(data: String): Result {
return try { parse(data) }
catch (e: Exception) {
log.error("Parsing failed", kv("error", e.message))
throw ServiceException("Could not process input", e)
}
}
- Always handle errors — never ignore returned errors in Go
- Recover from panics at HTTP handler boundaries (middleware)
- Fail securely: deny access by default when state is uncertain
- Sanitize error messages: internal details stay in logs, not in responses
- Centralized error handling via middleware/exception mappers
Quick Reference Checklist
- A01 — Resource-level access checks on every endpoint (not just auth)
- A01 — M2M tokens validate
azpagainst pre-authorized apps - A01 — Outbound URLs validated; Nais egress policy configured
- A02 — CORS restricted to known origins
- A02 — Debug endpoints not on public ingress
- A02 — Error responses sanitized (no stack traces to client)
- A03 — Dependencies pinned to exact versions with lockfiles
- A03 —
govulncheck/trivy repo .pass without HIGH/CRITICAL - A03 — GitHub Actions pinned to full SHA
- A04 — bcrypt/argon2id for passwords, never MD5/SHA-1
- A04 — TLS 1.2+ enforced, no
InsecureSkipVerify - A04 — Secrets from environment/Nais, never hardcoded
- A05 — All SQL queries parameterized (
?/$1) - A05 — No shell execution with user input
- A07 — JWT validates
exp,iss,aud, and algorithm - A08 — Deserialization into concrete types only
- A09 — No PII in logs (fnr, name, address)
- A09 — Audit trail for sensitive operations
- A10 — All errors handled (no ignored returns in Go)
- A10 — Panic recovery in HTTP handlers
- A10 — Error messages sanitized before client responses
Related
| Resource | Use For |
|---|---|
security-review skill | Pre-commit scanning (trivy, zizmor, govulncheck) |
@security-champion-agent | Threat modeling, compliance, Nav security architecture |
@auth-agent | JWT validation, TokenX, ID-porten implementation |
threat-model skill | STRIDE-A analysis for new services |
| OWASP Top 10:2025 | Official category descriptions |
| OWASP Go SCP | Go-specific secure coding guide |
| OWASP CI/CD Top 10 | Pipeline security risks |
| sikkerhet.nav.no | Nav Golden Path |
Boundaries
✅ Always
- Parameterized queries for all SQL
- Resource-level access checks on every data-returning endpoint
- Structured logging without PII
- SHA-pinned GitHub Actions
- Explicit error handling (no ignored errors)
- Dependencies scanned before release
⚠️ Ask First
- Custom cryptographic implementations
- Disabling security features for testing
- Changing authentication or authorizati
Content truncated.