| name | security-owasp |
| description | OWASP Top 10:2025 kodenivå-mønstre for Kotlin, Go, Java og Node.js — tilgangskontroll, forsyningskjede, injeksjon og feilhåndtering |
| license | MIT |
| metadata | {"domain":"auth","tags":"security owasp kotlin go java nodejs nais supply-chain"} |
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.md in this skill directory.
A01: Broken Access Control (incl. SSRF)
get("/api/vedtak/{id}") {
val vedtak = vedtakRepository.findById(call.parameters["id"]!!.toLong())
call.respond(vedtak)
}
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())
}
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) }
}
- Deny by default — require explicit grants, not explicit denials
- Resource-level checks — not just "is authenticated" but "owns this resource"
- M2M tokens — validate
azp claim against AZURE_APP_PRE_AUTHORIZED_APPS
- SSRF — validate outbound URLs; use Nais
accessPolicy.outbound as defense-in-depth
A02: Security Misconfiguration
install(CORS) { anyHost() }
install(CORS) { allowHost("my-app.intern.nav.no", schemes = listOf("https")) }
mux.HandleFunc("/debug/pprof/", pprof.Index)
internalMux := http.NewServeMux()
internalMux.HandleFunc("/debug/pprof/", pprof.Index)
go http.ListenAndServe(":9090", internalMux)
- CORS restricted to known origins — never
* or anyHost()
- 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)
require (
golang.org/x/crypto v0.31.0
github.com/jackc/pgx/v5 v5.7.2
)
dependencyLocking { lockAllConfigurations() }
dependencyManagement {
imports { mavenBom("org.springframework.boot:spring-boot-dependencies:3.4.1") }
}
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- 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
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
client := &http.Client{}
val hash = MessageDigest.getInstance("MD5").digest(password.toByteArray())
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
session.run(queryOf("SELECT * FROM vedtak WHERE status = '$status'").map { ... }.asList)
session.run(queryOf("SELECT * FROM vedtak WHERE status = ?", status).map { ... }.asList)
exec.Command("sh", "-c", fmt.Sprintf("process %s", userInput)).Run()
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
log.info("Vedtak opprettet", kv("vedtakId", vedtak.id), kv("sakId", sak.id))
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)
func processRequest(data []byte) Result {
var req Request
json.Unmarshal(data, &req)
return handle(req)
}
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)
}
fun process(data: String): Result {
try { return parse(data) }
catch (e: Exception) { return Result.empty() }
}
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
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 authorization logic
- Adding new outbound external hosts
🚫 Never
- String-concatenated SQL queries
InsecureSkipVerify: true in production
- PII in log statements (fnr, name, address)
- Wildcard CORS (
* / anyHost())
- Hardcoded secrets or encryption keys
- Floating tags (
@main, @v3) for GitHub Actions
- Silently swallowing errors without logging