Application Security: OWASP Top 10:2025
Sign in to save your progress across devices. Until then it is kept in this browser.
Why this matters
Security is where AI-generated code fails most often and most expensively. Veracode's 2026 data puts the security pass rate of AI code at about 56%. This is a two-week module and the hardest gate in the programme.
Learning outcomes
- Apply the OWASP Top 10:2025 to your own application.
- Implement authentication and authorization correctly (OAuth/OIDC, sessions, JWT rotation, IAM, RLS).
- Manage secrets so none ever reach code, frontend bundles or logs.
- Prevent injection (SQL, XSS, SSRF) and browser attacks (CSRF, CORS misconfiguration).
- Defend the perimeter with rate limiting, WAF and DDoS protection.
- Run SAST, DAST, secret and dependency scanning in CI.
The OWASP Top 10:2025
| ID | Risk | Typical AI-code failure |
|---|---|---|
| A01 | Broken Access Control | Endpoint checks login but not ownership; RLS off; IDOR via sequential IDs |
| A02 | Security Misconfiguration | Debug on, default creds, permissive CORS *, verbose errors, open storage buckets |
| A03 | Software Supply Chain Failures | Hallucinated or unpinned packages, compromised dependencies, unsigned builds |
| A04 | Cryptographic Failures | Homemade crypto, MD5/SHA1 passwords, secrets in plain text |
| A05 | Injection | String-concatenated SQL, dangerouslySetInnerHTML, shell commands with user input |
| A06 | Insecure Design | No threat model; business logic abuse (negative quantities, coupon stacking) |
| A07 | Authentication Failures | No rate limit on login, weak reset flows, long-lived tokens, no MFA |
| A08 | Software or Data Integrity Failures | Unverified webhooks, unsigned updates, insecure deserialisation |
| A09 | Security Logging and Alerting Failures | No audit log; no alert on brute force; secrets in logs |
| A10 | Mishandling of Exceptional Conditions | Fail-open on errors, swallowed exceptions, stack traces to users |
New in 2025: Supply Chain became its own category at A03, and Mishandling of Exceptional Conditions entered at A10. Both are exactly where AI tools are weakest.
Lessons
5.1 Access control (A01, 68)
- Authentication = who you are. Authorization = what you may do. AI code usually does the first and forgets the second.
- Check authorization on every request, server-side, for every object (
WHERE id = $1 AND org_id = $currentOrg). - Deny by default. Roles and permissions in one place (policy module or RLS), not scattered
ifstatements. - IAM for infrastructure: each service gets its own identity with least privilege; no shared admin keys; separate dev/staging/prod accounts.
- Never expose service-role / admin keys to the client. In Supabase, the anon key is public by design, so RLS is your only wall.
5.2 Authentication, OAuth and tokens (69, 70)
- Do not build auth from scratch. Use a proven provider (Supabase Auth, Clerk, Auth0, WorkOS for enterprise SSO).
- OAuth 2.0 / OIDC: Authorization Code flow with PKCE for browsers and mobile. Validate
state,nonce, issuer, audience. - Sessions:
HttpOnly,Secure,SameSite=LaxorStrictcookies. Do not keep tokens in localStorage. - JWT rotation: short-lived access tokens (5 to 15 minutes), rotating refresh tokens with reuse detection, signing keys rotated via JWKS with key IDs (
kid), neveralg: none, always verify signature, expiry and audience. - Enterprise buyers expect: MFA, SSO (SAML/OIDC), SCIM provisioning, session revocation.
5.3 Secrets management (67)
- Secrets live in a secrets manager or platform env vars, never in git, never in
NEXT_PUBLIC_*/VITE_*variables (those ship to the browser). - Pre-commit hook + CI scanning (gitleaks, GitHub secret scanning with push protection).
- A leaked secret is rotated, not just deleted: git history and forks keep it forever.
- Separate keys per environment; least-scope API keys (restricted Stripe keys, scoped tokens).
- Spend caps on every paid API key (LLM, SMS, maps). A leaked key is otherwise a blank cheque.
5.4 Cryptography and encryption (72, A04)
- Passwords: argon2id or bcrypt via your auth provider. Never hash yourself with SHA.
- Encryption at rest: managed DB/storage encryption on, backups encrypted. Field-level encryption for high-sensitivity data (national ID numbers, bank details), with keys in a KMS.
- Use libraries (libsodium, platform KMS); never invent crypto.
5.5 Injection (78, 79, 80)
- SQL injection: parameterised queries or a query builder/ORM. Never interpolate strings into SQL, including in "raw" escape hatches.
- XSS: frameworks escape by default; danger zones are
dangerouslySetInnerHTML,v-html, rendering markdown or LLM output as HTML. Sanitise with DOMPurify; set a Content-Security-Policy. - SSRF: any feature that fetches a user-supplied URL (link previews, webhooks, "import from URL", AI agents browsing) can be pointed at
169.254.169.254(cloud metadata) or internal services. Allowlist domains, resolve and block private IP ranges, disable redirects or re-check after each, run fetchers in an isolated network.
5.6 Browser security (76, 77)
- CORS is not a security boundary for your server; it controls which origins browsers let read responses. Never reflect arbitrary
OriginwithAccess-Control-Allow-Credentials: true. Allowlist exact origins. - CSRF:
SameSitecookies plus CSRF tokens or origin checks for state-changing requests when using cookie auth. - Security headers: CSP, HSTS,
X-Content-Type-Options: nosniff,Referrer-Policy,frame-ancestors.
5.7 Perimeter: rate limiting, WAF, DDoS (1, 74, 75)
- Rate limit by IP, user and API key, with tighter limits on login, sign-up, password reset, OTP, and every endpoint that costs money (LLM, SMS, email).
- Algorithms: token bucket or sliding window, stored in Redis or at the edge. Return
429withRetry-After. - WAF (Cloudflare, AWS WAF) with managed OWASP rules; bot protection on sign-up.
- DDoS protection comes largely from your CDN; make sure the origin is not directly reachable and bypassable.
5.8 Exceptional conditions (A10)
- Fail closed: if the auth check throws, deny. If the fraud check times out, hold the transaction, do not approve it.
- Never show stack traces to users; log them with a request ID.
- Catch specific errors; do not swallow all exceptions.
5.9 Threat modelling (A06)
STRIDE in 30 minutes per feature: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege. Draw trust boundaries; list abuse cases ("what if quantity is -1?").
5.10 Security in the pipeline
| Tool type | Example | Catches |
|---|---|---|
| Secret scan | gitleaks, GitHub push protection | Keys in code |
| SAST | Semgrep, CodeQL | Injection, unsafe patterns |
| SCA | Dependabot, Snyk, OSV-Scanner | Vulnerable dependencies |
| DAST | OWASP ZAP | Runtime misconfig, headers, XSS |
| Container/IaC | Trivy, Checkov | Bad images and infra config |
Lab 5 (two weeks): Attack your own app
Week A, Break: use a second account and a browser proxy (ZAP or Burp Community).
- Try to read/edit another user's records by changing IDs (IDOR).
- Call the database directly with the public anon key.
- Inject
<script>into every text field and markdown/LLM output. - Point any URL-fetching feature at
http://169.254.169.254/andhttp://localhost. - Brute force login and password reset; hammer the most expensive endpoint.
- Search the production JS bundle for secrets.
Week B, Fix and prove: fix every finding, add a regression test per finding, wire scanners into CI.
Definition of Done
AI prompt pack
- "Act as a penetration tester. For each route in this app, list how an authenticated user could access another user's data. Then write tests for each."
- "Find every place secrets or env vars are read. Flag any that could reach the client bundle."
- "Find every place we fetch a URL derived from user input. Add SSRF protection that blocks private, loopback and link-local ranges after DNS resolution."
- "Find every catch block. Flag any that fail open or swallow errors."