Backend Security Audit
Purpose
Systematically check a backend against the OWASP API Security Top 10, producing findings with a concrete exploit scenario each — so the team understands the attack, not just the rule.
Universal — the OWASP API Top 10 is a vendor-neutral, exploit-oriented taxonomy that maps to any backend; only the fix syntax differs.
Procedure
Audit against the OWASP API Security Top 10 (2023). For each finding, write the exploit scenario (how an attacker abuses it) — this is the differentiator from a generic checklist.
API1 BOLA (Broken Object Level Authorization) — #1, ~40% of API attacks
- Can a user access another's object by changing an ID? → row-level authz missing (see
authorization RLS)
API2 Broken Authentication
- Weak tokens, no rotation, alg confusion (see
authentication)
API3 Broken Object Property Level Authorization (mass assignment / excessive data exposure)
- Does the API accept/return fields the user shouldn't set/see? → explicit allow-lists, DTOs
API5 BFLA (Broken Function Level Authorization)
- Can a regular user hit admin endpoints? → RBAC at every privileged route
API4 Unrestricted Resource Consumption
- Rate limiting, pagination caps, payload size limits, query depth/complexity limits (GraphQL)
API8 Security Misconfiguration
- CORS not wildcard for credentialed endpoints; security headers; verbose errors disabled in prod
Injection (cross-cutting)
- Note: standalone "Injection" was API8:2019 — in the 2023 edition it's folded across categories rather than its own item, but still audit it
- Parameterized queries only (ORM by default); never string-concatenate SQL; validate input (see
data-validation)
Secrets management (cross-cutting)
- Not a numbered Top-10 item but a practical must: no secrets in code/client-shipped env; use a secret manager; rotate; scan with
gitleaks/trufflehog
API7 SSRF (Server-Side Request Forgery) — call this out explicitly
- User-supplied URL fed into
fetch / axios → attacker pivots through the server. Defenses: allowlist hosts; block cloud metadata IPs (169.254.169.254 AWS/GCP/Azure, 100.100.100.200 Alibaba, link-local + private CIDRs); validate the resolved IP, not just the input string (DNS rebinding); set a strict timeout
- Egress firewall is the structural defense; URL validation alone is not enough
API10 Unsafe Consumption of APIs — trust the wire as much as the user
- Treat third-party API responses as untrusted: validate shape (see
data-validation), cap response sizes, time out generously but not infinitely
- JSON-bomb / zip-bomb / oversized-payload defenses on every downstream consumer
- CSRF (cookie-auth web apps) — outside the API Top 10 but on the same backend
- For sessions / cookie tokens:
SameSite=Lax|Strict cookies + origin check on state-changing requests (header-token tokens like Authorization are immune)
- Also review remaining 2023 items not enumerated above (API6 Sensitive Business Flows, API9 Improper Inventory)
- Validate (validation loop)
- For each Critical finding, reproduce the exploit (BOLA: actually fetch another user's record) → fix → re-attempt → verify blocked
- Run
eslint-plugin-security / semgrep --config=auto + gitleaks + dependency audit; triage each hit; re-run until clean or documented
Anti-patterns
| ❌ Anti-pattern |
✅ Correct |
| Object access by ID with no ownership check |
Row-level authz (RLS) verified per request |
| String-concatenated SQL |
Parameterized queries / ORM |
| Accepting all body fields (mass assignment) |
Explicit DTO allow-list |
| Secrets in env vars shipped to client |
Secret manager; server-only; scanned |
| Wildcard CORS on credentialed endpoints |
Explicit origin allow-list |
User-supplied URL fed into fetch (SSRF) |
Allowlist hosts; block metadata IPs (169.254.169.254); validate resolved IP, not just input |
| Trusting third-party API response shape (API10) |
Validate shape + cap response size + bounded timeout |
| Cookie-auth state-changing endpoint with no CSRF defense |
SameSite=Lax/Strict + origin / CSRF-token check |
Severity tiers
| Tier |
Examples |
Action SLA |
| Critical |
BOLA leaking other users' data; SQL injection; secret in repo; admin endpoint reachable by regular user (BFLA); SSRF reachable to cloud metadata; cookie-auth state-change with no CSRF defense |
Block release; fix immediately |
| Major |
Missing rate limits; mass assignment; CORS wildcard; trusting third-party API response without validation/size cap |
Fix this sprint |
| Minor |
Verbose error messages; missing security header; outbound URL allow-list maintained ad-hoc |
Schedule within 2 sprints |
Completion Criteria
Output
- Report:
docs/security-audit-YYYY-MM-DD.md — per finding: OWASP category, file:line, exploit scenario, fix, severity
- Code fixes per finding
- Commit format:
fix(security): <description> [OWASP-API#N, severity]
Stop & Ask (AI must pause for user approval)
- Before changing auth/authz logic to fix a finding — can lock users out if wrong
- Before rotating/removing a secret found in the repo — coordinate rotation with deploys
- Before tightening CORS/CSP in production config — verify no legitimate client breaks
Implementation
TypeScript + NestJS + Supabase (default)
- Injection: Prisma parameterizes by default; never
$queryRawUnsafe with interpolation
- BOLA/BFLA: NestJS guards (RBAC) + Supabase RLS (row) — see
authorization
- Mass assignment: explicit DTOs +
class-validator/Zod (see data-validation)
- Scanning:
eslint-plugin-security, semgrep --config=auto, gitleaks, npm audit
Other stacks
- Python / FastAPI:
bandit static analysis; Pydantic prevents mass assignment; parameterized SQLAlchemy
- Go:
gosec; database/sql parameterized queries
- Universal: OWASP API Top 10 categories are framework-independent;
semgrep, gitleaks, trivy work across stacks
Related skills
authorization — BOLA/BFLA findings are fixed via RBAC + RLS
authentication — broken auth is a top OWASP category
data-validation — input validation is the first injection defense
Reference
- Key insight encoded: BOLA is the #1 API vulnerability — function-level auth without row-level auth is the most common gap. Every finding gets a reproducible exploit scenario.
1---2name: backend-security-audit3description: Audit a backend against the OWASP API Security Top 10 — BOLA/BFLA, injection, secrets, mass assignment — with an exploit scenario per finding. Use when adding auth/external input, before shipping, or on a quarterly security review. Not for implementing auth from scratch (use authentication / authorization) — each finding maps to its sibling skill for the fix.4license: MIT5---67# Backend Security Audit89## Purpose10Systematically check a backend against the OWASP API Security Top 10, producing findings with a concrete exploit scenario each — so the team understands the *attack*, not just the rule.1112**Universal** — the OWASP API Top 10 is a vendor-neutral, exploit-oriented taxonomy that maps to any backend; only the fix syntax differs.1314## Procedure1516Audit against the OWASP API Security Top 10 (2023). For each finding, **write the exploit scenario** (how an attacker abuses it) — this is the differentiator from a generic checklist.17181. **API1 BOLA (Broken Object Level Authorization)** — #1, ~40% of API attacks19 - Can a user access another's object by changing an ID? → row-level authz missing (see `authorization` RLS)202. **API2 Broken Authentication**21 - Weak tokens, no rotation, alg confusion (see `authentication`)223. **API3 Broken Object Property Level Authorization** (mass assignment / excessive data exposure)23 - Does the API accept/return fields the user shouldn't set/see? → explicit allow-lists, DTOs244. **API5 BFLA (Broken Function Level Authorization)**25 - Can a regular user hit admin endpoints? → RBAC at every privileged route265. **API4 Unrestricted Resource Consumption**27 - Rate limiting, pagination caps, payload size limits, query depth/complexity limits (GraphQL)286. **API8 Security Misconfiguration**29 - CORS not wildcard for credentialed endpoints; security headers; verbose errors disabled in prod307. **Injection (cross-cutting)**31 - Note: standalone "Injection" was API8:2019 — in the 2023 edition it's folded across categories rather than its own item, but still audit it32 - Parameterized queries only (ORM by default); never string-concatenate SQL; validate input (see `data-validation`)338. **Secrets management (cross-cutting)**34 - Not a numbered Top-10 item but a practical must: no secrets in code/client-shipped env; use a secret manager; rotate; scan with `gitleaks`/`trufflehog`35369. **API7 SSRF (Server-Side Request Forgery)** — call this out explicitly37 - User-supplied URL fed into `fetch` / `axios` → attacker pivots through the server. Defenses: allowlist hosts; **block cloud metadata IPs** (`169.254.169.254` AWS/GCP/Azure, `100.100.100.200` Alibaba, link-local + private CIDRs); validate the resolved IP, not just the input string (DNS rebinding); set a strict timeout38 - Egress firewall is the structural defense; URL validation alone is not enough394010. **API10 Unsafe Consumption of APIs** — trust the wire as much as the user41 - Treat third-party API responses as untrusted: validate shape (see `data-validation`), cap response sizes, time out generously but not infinitely42 - JSON-bomb / zip-bomb / oversized-payload defenses on every downstream consumer434411. **CSRF (cookie-auth web apps)** — outside the *API* Top 10 but on the same backend45 - For sessions / cookie tokens: `SameSite=Lax|Strict` cookies + origin check on state-changing requests (header-token tokens like `Authorization` are immune)46 - Also review remaining 2023 items not enumerated above (API6 Sensitive Business Flows, API9 Improper Inventory)474812. **Validate (validation loop)**49 - For each Critical finding, reproduce the exploit (BOLA: actually fetch another user's record) → fix → re-attempt → verify blocked50 - Run `eslint-plugin-security` / `semgrep --config=auto` + `gitleaks` + dependency `audit`; triage each hit; re-run until clean or documented5152## Anti-patterns5354| ❌ Anti-pattern | ✅ Correct |55|---|---|56| Object access by ID with no ownership check | Row-level authz (RLS) verified per request |57| String-concatenated SQL | Parameterized queries / ORM |58| Accepting all body fields (mass assignment) | Explicit DTO allow-list |59| Secrets in env vars shipped to client | Secret manager; server-only; scanned |60| Wildcard CORS on credentialed endpoints | Explicit origin allow-list |61| User-supplied URL fed into `fetch` (SSRF) | Allowlist hosts; block metadata IPs (`169.254.169.254`); validate resolved IP, not just input |62| Trusting third-party API response shape (API10) | Validate shape + cap response size + bounded timeout |63| Cookie-auth state-changing endpoint with no CSRF defense | `SameSite=Lax/Strict` + origin / CSRF-token check |6465## Severity tiers6667| Tier | Examples | Action SLA |68|---|---|---|69| **Critical** | BOLA leaking other users' data; SQL injection; secret in repo; admin endpoint reachable by regular user (BFLA); SSRF reachable to cloud metadata; cookie-auth state-change with no CSRF defense | Block release; fix immediately |70| **Major** | Missing rate limits; mass assignment; CORS wildcard; trusting third-party API response without validation/size cap | Fix this sprint |71| **Minor** | Verbose error messages; missing security header; outbound URL allow-list maintained ad-hoc | Schedule within 2 sprints |7273## Completion Criteria74- [ ] Key OWASP API Top 10 (2023) categories checked + cross-cutting concerns (injection, secrets)75- [ ] Each finding has an exploit scenario76- [ ] BOLA reproduced + fixed + re-verified blocked77- [ ] Static analysis + secret scan clean (or documented)78- [ ] All Critical fixed; all Major scheduled7980## Output81- **Report**: `docs/security-audit-YYYY-MM-DD.md` — per finding: OWASP category, file:line, **exploit scenario**, fix, severity82- **Code fixes** per finding83- **Commit format**: `fix(security): <description> [OWASP-API#N, severity]`8485## Stop & Ask (AI must pause for user approval)8687- **Before changing auth/authz logic** to fix a finding — can lock users out if wrong88- **Before rotating/removing a secret** found in the repo — coordinate rotation with deploys89- **Before tightening CORS/CSP** in production config — verify no legitimate client breaks9091## Implementation9293### TypeScript + NestJS + Supabase (default)94- Injection: Prisma parameterizes by default; never `$queryRawUnsafe` with interpolation95- BOLA/BFLA: NestJS guards (RBAC) + Supabase RLS (row) — see `authorization`96- Mass assignment: explicit DTOs + `class-validator`/Zod (see `data-validation`)97- Scanning: `eslint-plugin-security`, `semgrep --config=auto`, `gitleaks`, `npm audit`9899### Other stacks100- **Python / FastAPI**: `bandit` static analysis; Pydantic prevents mass assignment; parameterized SQLAlchemy101- **Go**: `gosec`; `database/sql` parameterized queries102- **Universal**: OWASP API Top 10 categories are framework-independent; `semgrep`, `gitleaks`, `trivy` work across stacks103104## Related skills105- `authorization` — BOLA/BFLA findings are fixed via RBAC + RLS106- `authentication` — broken auth is a top OWASP category107- `data-validation` — input validation is the first injection defense108109## Reference110- **Key insight encoded**: BOLA is the #1 API vulnerability — function-level auth without row-level auth is the most common gap. Every finding gets a reproducible exploit scenario.