Least Privilege
OWASP’s “Top 10” isn’t a checklist — it’s shorthand for security principles that make the bugs less likely in the first place. Internalise these — least privilege, defense in depth, fail securely, defaults that are safe — and most of the Top 10 stop happening on your codebase.
Principles + how they map to code
EXAMPLE
// 1) LEAST PRIVILEGE
// Every actor (user, service, function, DB user) gets exactly the permissions it needs.
//
// Application:
// • Per-service AWS IAM role, not a god role
// • Per-service DB user (read-only where possible)
// • Workload identity (Kubernetes service accounts) instead of shared secrets
// • Feature-flag dangerous actions behind explicit role checks
//
// People:
// • Production access via JIT (just-in-time) approvals
// • Engineers can deploy, not edit prod DB directly
// • Quarterly access review — remove every role nobody used in 90 days
// 2) DEFENSE IN DEPTH
// A single control will fail one day. Layered controls turn 'one failure' into 'partial failure'.
//
// client validation → server validation → DB constraints → app-level audit
// CSP → input sanitisation → output encoding → cookie HttpOnly
// WAF → rate limiter → auth → authz → query parameterisation → DB user privileges
//
// Each layer accepts that others might fail. The system stays secure if any TWO layers hold.
// 3) FAIL SECURELY
// When a check errors, choose 'deny' over 'allow'.
//
function can(user, action, resource) {
try {
return policyEngine.check(user, action, resource);
} catch (e) {
log.error('policy check failed', e);
return false; // deny on error, not allow
}
}
// • try/catch in authz code returns DENIED on any exception
// • Default routes 404 instead of revealing existence
// • Healthchecks that can't reach dependencies report unhealthy, not healthy
// • Configuration that can't be parsed crashes startup, not silently falls back to insecure defaults
// 4) SECURE BY DEFAULT
// New features should be safe even if the engineer added nothing.
//
// • Cookies default to HttpOnly + Secure + SameSite=Lax
// • Database client throws on string concatenation; only takes bind parameters
// • Web framework escapes templates by default; raw-HTML escape hatch is loud
// • Production env disables debug pages; dev env enables them
// • API routes require auth; opt-in 'public' decorator for the few that don't
// • Audit log enabled for every state change
//
// Code-review heuristic: if the safer behaviour requires an extra annotation, the default is wrong.
// 5) MINIMISE ATTACK SURFACE
// Every endpoint, port, page, package, and parameter is a thing an attacker can poke.
//
// • Remove unused endpoints
// • Decommission unused services
// • npm prune dependencies (depcheck, knip)
// • Block unused HTTP methods (PUT on a GET-only endpoint)
// • Tight CORS allowlist; no wildcard with credentials
// • Robots.txt is NOT security — but disabling unused admin paths helps
// • Run only the ports you need; firewall everything else
// 6) ZERO TRUST
// Don't trust network location; authenticate every request.
//
// • mTLS or signed requests between internal services
// • Strong service-to-service auth (SPIFFE / SPIRE)
// • API gateways that re-authenticate, not just route
// • Database connections use TLS even inside the VPC
// • Secrets fetched per-pod from a vault, not baked into images
// 7) SEPARATION OF DUTIES / CONCERNS
//
// • Different code path for 'create account' vs 'admin impersonate'
// • Production releases require an approval from someone OTHER than the developer
// • Audit logs can be read by ops + security, but only admins can DELETE — and there's a chain of custody
// • Per-service / per-tenant secret rotation; one compromise doesn't unlock everything
// 8) ENCRYPT EVERYWHERE — DATA AT REST + IN TRANSIT
//
// • TLS 1.2+ between every client and server, including internal
// • HSTS with includeSubDomains + preload
// • Database storage encrypted (transparently — managed services usually do this)
// • Backups encrypted with rotated keys
// • Per-tenant data encrypted with tenant-specific keys derived from a master
// • Secrets stored in a vault, never in code or env files committed to git
// 9) IDENTIFY + LOG
// You can't respond to what you don't see.
//
// • Structured logs (JSON) so you can query: 'who did what, to what, from where, when'
// • Audit log for every state change (immutable retention)
// • Alert on auth.failure, csrf.failure, idor.attempt, rate.limit.exceeded
// • Correlate logs by request id across services
// • Send security signals to a SIEM; engineering logs and security logs are different problems
// 10) ASSUME BREACH
// Plan as if defenses have already failed.
//
// • Network segmentation — a popped pod can't reach the prod database
// • Secrets rotate frequently enough that yesterday's leak is useless
// • Backups offline / immutable for the ransomware case
// • Tabletop exercises ('what if /etc/passwd leaked?')
// • Pre-written runbooks for the top 5 incident scenarios
// • A clear incident command structure — who decides, who communicates, who fixes
// 11) PRINCIPLE OF NO-DEFAULT-CREDENTIALS
//
// • Never ship a service with hard-coded admin/admin
// • Force a strong credential on first run
// • Make credential rotation a one-line operation; if it's hard, no one will do it
// • Disable test accounts in prod build steps
// 12) USE WELL-TESTED LIBRARIES, NOT YOUR OWN CRYPTO
// • Argon2id / scrypt / bcrypt for password hashing
// • libsodium / NaCl / Web Crypto for primitives
// • TLS via the platform (let nginx/OpenSSL handle it)
// • JWTs via vetted libs — and pin the accepted algorithm list
// • DON'T: custom hash chains, home-made 'encryption', RSA with PKCS#1 v1.5 padding, MD5 / SHA-1 for new code
// 13) FAIL EXPLICIT, NOT IMPLICIT
// • Throw on unsupported configuration, don't fall back
// • Don't 'just continue' when an authz check returns an unexpected shape
// • Don't decode JWTs with alg=none accepted
// • Don't accept the input shape 'we'll handle that later' — refuse it now
// 14) ECONOMY OF MECHANISM
// • Smaller security surface = easier to audit
// • Centralise authentication in one library / middleware
// • Centralise authorisation in a policy layer
// • Centralise audit logging
// • Avoid clever, opaque tricks — boring, readable code is auditable code
// 15) MAP TO OWASP TOP 10 (2021)
// A01 Broken Access Control → Least privilege, central policy layer, 404 not 403
// A02 Cryptographic Failures → Use vetted crypto libraries, modern algorithms, real KMS
// A03 Injection → Parameterised queries, schema validation, input shape checks
// A04 Insecure Design → Threat model BEFORE coding; security in design reviews
// A05 Security Misconfiguration → Secure-by-default, infra as code, automated config audits
// A06 Vulnerable Components → SBOM + scanning + patch SLAs (see owasp/a06)
// A07 Identification + Auth Failures → MFA, strong KDF, account-lockout backoff, session invalidation
// A08 Software + Data Integrity → Signed releases, CSP, SRI, supply chain SLSA
// A09 Logging + Monitoring Failures → Structured logs, alerts, retention, SIEM
// A10 Server-Side Request Forgery → Allowlist outbound destinations, block private ranges, validate URLs
// 16) MAKING THESE PRINCIPLES OPERATIONAL
// Translate principles into:
// • CI checks (linter rules, dependency scans, secret detectors)
// • Default templates (cookie config, CSP, fetch wrappers)
// • Code review checklists
// • Quarterly drills (incident response, key rotation)
// • Onboarding curricula so every engineer learns them on day one
//
// A principle on a slide deck doesn't protect anything; a principle baked into the build does.
Why it matters
The OWASP Top 10 changes; principles don’t. Least privilege, defense in depth, fail securely, secure-by-default, assume breach — once those are how your codebase is built and operated, the specific bug categories take care of themselves. Translate every principle into a default template, a CI check, or a code-review heuristic so they show up in real engineering work, not slide decks.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Least privilege everywhere: DB users, IAM roles, K8s ServiceAccounts, // CI/CD tokens, OAuth scopes. Default deny → grant the minimum needed.Try it Yourself »
Discussion
Loading…