| name | review-security |
| description | Use when the user asks for a security review, security audit, or "is this safe to deploy?" Also use proactively before any push to production or before adding endpoints that touch authentication, payments, or user data. Hunts the H1-H8 hard stops from the technical due diligence audit. |
Skill: Security Review
This skill runs the security-relevant subset of the technical due
diligence audit. It is NOT a substitute for a real penetration test or a
human security engineer for code that handles money, health data, or
PII at scale.
Hard stops — find any of these, raise the alarm immediately
These are the H1-H8 conditions from the audit prompt. Each is a CRITICAL
STOP — fix before anything else ships.
H1 — Row-level access control disabled on user-data tables
For Supabase / Postgres:
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname IN ('public', 'auth')
AND rowsecurity = false;
Any row returned with PII or user data is a CRITICAL STOP.
For other DBs, check whether your access control happens at the DB layer
(good), the app layer with proper enforcement (acceptable), or "we trust
the queries" (CRITICAL).
H2 — Service-role / admin keys reachable from the client
grep -rn "SERVICE_ROLE\|SERVICE_KEY\|ADMIN_KEY\|MASTER_KEY\|PRIVATE_KEY" \
src/client/ src/components/ src/pages/ public/ static/ 2>/dev/null
grep -rn "NEXT_PUBLIC_.*\(SECRET\|KEY\|TOKEN\)" src/
grep -rn "VITE_.*\(SECRET\|KEY\|TOKEN\)" src/
H3 — Any data-mutation endpoint with no auth check
For each POST/PUT/PATCH/DELETE handler, trace whether an auth dependency
is actually wired. The pattern "auth middleware exists but isn't called
on this route" is one of the most common AI-generated bugs (B1 from the
audit's blind-spot list).
grep -rn "@app\.\(post\|put\|patch\|delete\)\|router\.\(post\|put\|patch\|delete\)" src/routes/
For each match, open the file and verify the handler has either:
- A
Depends(get_current_user) parameter (FastAPI)
- A middleware in the router chain that requires auth
- An explicit comment stating "this endpoint is intentionally public"
If none, that's a CRITICAL STOP.
H4 — .env in git history
git log --all --full-history -- .env 2>/dev/null
git log --all --full-history -- .env.local 2>/dev/null
git log --all --full-history -- .env.production 2>/dev/null
git log --all --diff-filter=A --name-only \
| grep -iE "\.env|secrets?\.|credentials?\." \
| sort -u
If anything returns, even one historical commit, that's a CRITICAL STOP.
The secrets are exposed forever — git history cannot be unwound on a
public repo. Recommendation: rotate every secret, then git filter-repo
to scrub history.
H5 — Webhooks with no signature verification
grep -rn "webhook\|callback" src/routes/ src/handlers/
grep -rn "verify_signature\|verify_webhook\|stripe\.Webhook\|mercadopago.*signature" src/
If a webhook handler accesses request.body or processes events before
verifying the signature, that's a CRITICAL STOP.
H6 — User-controlled HTML rendering
grep -rn "dangerouslySetInnerHTML\|v-html=\|\.innerHTML\s*=\|\.outerHTML\s*=" src/
For each match, trace whether the rendered content can come from user
input. If yes, and there's no DOMPurify / sanitize-html / equivalent,
that's a CRITICAL STOP.
H7 — SQL with string concatenation or f-strings using user input
grep -rn 'f".*SELECT\|f".*INSERT\|f".*UPDATE\|f".*DELETE' src/
grep -rn 'execute(.*\+\|execute(.*%' src/
grep -rn 'query.*\${.*}\|query.*\+.*request\|query.*\+.*req\.\|query.*\+.*params' src/
Every match is a potential SQL injection. Verify the variable in the
query is NOT user-controlled. If it is, CRITICAL STOP.
H8 — Hardcoded admin checks / temporary auth bypasses
grep -rn "admin@\|hardcoded\|TEMP\|TODO.*auth\|bypass.*auth\|skip.*auth" src/
grep -rn "if.*email.*=.*['\"].*@.*['\"]" src/
grep -rn "if.*role.*==.*['\"]admin['\"]" src/
Each match needs eyeballs. Hardcoded admin emails, "temporary" bypasses
left in code, and if (user.email === 'me@company.com') patterns are
all CRITICAL stops.
Standard checks (after hard stops)
Once hard stops are clean, run through:
Authentication / authorization
- ☐ Every protected route enforces auth at the server, not just hides UI
- ☐ Tokens are httpOnly cookies, not
localStorage
- ☐ Sessions invalidate on logout (server-side, not just clearing the cookie)
- ☐ Password reset tokens expire and are single-use
- ☐ No IDOR — users cannot access other users' resources by changing IDs
Input validation
- ☐ Every endpoint validates input with a schema (Pydantic / Zod / Joi)
- ☐ Server-side validation matches or exceeds client-side
- ☐ File uploads check type AND size AND scan content
- ☐ User-supplied URLs in redirects are on an allowlist
CORS / CSRF
- ☐ CORS is not
* if credentials: true
- ☐ CSRF protection is in place for cookie-based auth (SameSite=lax minimum)
- ☐ State-changing GETs do not exist (every mutation is POST/PUT/PATCH/DELETE)
Rate limiting / abuse
- ☐ Login endpoint rate-limits brute-force
- ☐ Password-reset endpoint rate-limits enumeration
- ☐ AI/LLM endpoints rate-limit per user (cost protection)
- ☐ Webhooks rate-limit replay attempts
Data exposure
- ☐ API responses don't include
password_hash, internal_id, or raw DB rows
- ☐ Error responses don't leak stack traces to clients in production
- ☐ Logs don't include secrets (search for the word "password" in logs)
Output format
For each finding:
SECURITY FINDING #N
─────────────────────────────────────
Severity: Critical | High | Medium | Low
Exploitability: EXPLOITABLE-NOW | EXPLOITABLE-LOW-EFFORT | BAD-PRACTICE | UNKNOWN
Hard-stop class: H1-H8 if applicable, otherwise "none"
Evidence:
path/file.ext:start-end
What's wrong:
[one paragraph, plain English]
Why it matters:
[one sentence — what breaks, for whom, when]
Fix:
[concrete change with before/after pattern]
Verification:
[exact command to confirm the fix worked]
Anti-patterns to refuse
- ❌ Do not produce a security review without running the actual greps
shown above. A security review without evidence is theater.
- ❌ Do not rate-limit findings as "Medium" if they're H1-H8. Hard stops
are Critical, full stop.
- ❌ Do not claim "looks secure" without listing what you actually checked.
If you didn't check something, say so.
- ❌ Do not propose a fix you haven't reasoned through. Saying "add a
signature verification" is not a fix — show the code.
When this skill is done
You should have produced:
- A list of any H1-H8 hard stops with evidence
- Standard-check findings, severity-tagged
- Concrete fix recommendations for each finding
- The verification commands the user can run after applying fixes