owasp-audit

Audit application source code against the OWASP Top 10 (2021) vulnerability categories — broken access control, cryptographic failures, injection, insecure design, security misconfiguration, vulnerable components, authentication failures, data integrity, logging failures, SSRF. Use when the user men

By briiirussell · 392 installs

npx skills add briiirussell/cybersecurity-skills --skill owasp-audit

Source repository · Upstream listing

OWASP Audit — Source Code Security Review Perform a systematic security audit of application source code against the OWASP Top 10:2025. Every candidate is traced, then independently challenged, before it becomes a finding. Coverage is recorded per surface and trust boundary, so "clean" means someone actually read the code. Cross references: api audit for a surface driven pass over every API endpoint (shares the recon, verification, and orchestration references) web pentest "Combined white box + black box mode" when you have both the source and a running instance you're authorized to test business logic testing to prove race conditions and logic flaws dynamically (A06) dependency audit for the full A03 supply chain picture; secrets audit for leaked credentials threat modeling for the principal / trust boundary vocabulary used in recon finding triage to take a single finding from this report to a defensible disposition Authorization and Mode Only audit code the user owns or is authorized to review. Any live check (for example curl I against a deployed origin) needs the same authorization for that host and stays non destructive. Pick the mode before starting: Guidance (default): a question, a single file, or "how do I secure X". Answer directly. Findings still follow the verdict rules below. No ledger required. Full audit : the user explicitly asks to audit or security review a codebase, a release, or a PR. Run the full workflow below. Diff / PR scoped : the user wants a branch, PR, or commit range reviewed, or a pre merge gate. Run the full workflow limited to the change, using [references/diff review.md](references/diff review.md). If it's ambiguous, ask one question: guidance pass or full audit (and which profile)? Workflow (full audit) 1. Scope : what's in, what's out, mode, and run profile 2. Recon : write architecture.md and coverage.json before hunting 3. Hunt : work the category checklist unit by unit (subagents for large codebases) 4. Verify every finding : an independent refutation pass gives each candidate one verdict 5. Coverage critic : look for unmapped surfaces and thin coverage 6. Write and validate findings.json 7. Render the report from the JSON Write everything to security audit/ at the repo root (ask whether to commit or gitignore it). Re runs read the prior files from there. Step 1 — Scope Language, framework, runtimes, deploy target In scope / out of scope, with a reason for each exclusion Mode (full or diff) and run profile : quick , standard (default), or deep . Profiles change breadth and redundancy, never the evidence bar. See [references/orchestration.md](references/orchestration.md). Prior run? Load the previous findings.json and coverage.json and carry forward by fingerprint (orchestration.md "Re runs and carry forward"). Never re raise a rejected claim whose control is unchanged. Step 2 — Recon (read only) Before grepping for vulnerabilities, map the system ([references/recon and coverage.md](references/recon and coverage.md)): Every lower trust principal (anonymous, user, other tenant, webhook sender, job runner) and the file:line where its controls live Entry surfaces: routes, server actions, loaders, tRPC procedures, GraphQL resolvers, webhooks, jobs, cron, uploads Sinks: DB writes/raw queries, exec, filesystem, outbound HTTP, HTML rendering, email, paid third party calls, redirects What's deployed vs what's in the repo (proxy, CDN, IdP, env). These become needs validation blockers later Frameworks and BaaS. Match package.json dependencies and config files against the detection table in [references/frameworks/README.md](references/frameworks/README.md) (for example next + next.config. → Next.js, @supabase/ or supabase/config.toml → Supabase). Load only the matching playbooks, add their surfaces to the ledger, and work their checks in Step 3. Their findings use the same categories, verdicts, and format as the rest Write architecture.md (≤ ~1,000 words) and seed coverage.json with one planned unit per surface × boundary × OWASP category. Step 3 — Hunt Work through the [Audit Checklist]( audit checklist) per ledger unit , not per category in the abstract. For each unit, read the entrypoint, follow data to every sink, and record what you checked even when nothing is wrong. Large codebase: split units across isolated hunter subagents, unauthenticated surfaces first. Only the coordinator writes coverage.json / findings.json . Hunter prompt, result shape, and the sequential fallback are in [references/orchestration.md](references/orchestration.md). Source and a running app available (and authorized): use web pentest "Combined white box + black box mode" so code findings guide live tests and live anomalies prioritize code review. A candidate needs a lower trust principal, the input they control, the control that should stop them, the boundary crossed, the affected resource, and a concrete result. Missing any of these, it's a hardening note or needs validation, not a finding. Step 4 — Verify every finding Before anything reaches the report, run the per finding refutation pass in [references/verification.md](references/verification.md). One fresh verifier per candidate (a subagent where supported; otherwise a separate step that doesn't re read the hunting notes). It re reads every cited file:line , names the strongest control on the path, and returns exactly one verdict. Apply the fixed exclusion rules (test, generated, vendored, docs) first. In Claude Code, dispatch the plugin's read only finding verifier subagent per candidate. Verdict Bar Severity confirmed Complete entrypoint → control → sink → impact trace, or a PoC Yes, per [references/severity calibration.md](references/severity calibration.md) needs validation Code supports it; one decisive fact lives outside the code. Requires blockers[] + an owner run validation plan No rejected A named control at a named location runs on every reachable path and fails closed No; kept so re runs don't re raise it Rules that matter most: "Potential" is retired. Use Needs validation , and only with a concrete blocker and plan. It's not a parking place for a hunch. Library trust, a control on a different path, a control at the wrong time, a fail open control, a safe sibling, missing information, difficulty, "operator configurable", and "internal" do not rule a candidate out. Record counterevidence on every verdict. A static only trace caps confidence at medium. An upgrade (higher verdict or severity) needs a new verifier. Gaps with no traced impact close as hardening notes (not findings, no severity). Step 5 — Coverage critic Before writing the report, run the critic in [references/recon and coverage.md](references/recon and coverage.md) over architecture.md + coverage.json : unmapped entry points, unchecked sibling paths, covered units with thin checks, unjustified exclusions, candidates without verdicts. Add missing units and review them (or mark them deferred with a reason). covered requires non empty reviewed paths and at least one check. Step 6 — Write and validate findings.json Write every verdict record to security audit/findings.json (sorted by fingerprint; format in [references/findings format.md](references/findings format.md)), then validate both files: Fix every validation error before writing prose. Fingerprints name the root cause and never include line numbers, severity, or verdict. Optional SARIF export for GitHub code scanning: references/to sarif.mjs (see findings format.md). Step 7 — Render the report Render report.md from findings.json , coverage.json , and hardening notes.md using the [Output Format]( output format). The JSON is the source of truth: prose never changes a verdict, severity, blocker, or demonstrated impact. If they disagree, fix the JSON, re validate, and re render. Audit Checklist For each unit, grep for the patterns below, then read the flagged files and trace them. Categories follow the OWASP Top 10:2025 . Tag findings and coverage units A01:2025 … A10:2025 . Mapping for reports that used the 2021 list: A01 → A01 (SSRF, 2021's A10, is now part of A01); Cryptographic Failures A02 → A04; Injection A03 → A05; Insecure Design A04 → A06; Security Misconfiguration A05 → A02; Vulnerable Components A06 → A03 Software Supply Chain Failures (broader); A07 and A08 keep their numbers; A09 is now Logging and Alerting Failures; A10 Mishandling of Exceptional Conditions is new. A01: Broken Access Control Missing authorization checks on endpoints or routes IDOR — user controlled IDs without ownership verification Auth check ordering. Verify the authorization check runs before any branch that can reveal whether the resource exists, what state it's in, or any other resource specific metadata. Returning 404 for "not found", 400 for "wrong state", and 401 for "not authenticated" is itself a leak — an attacker enumerates resource IDs and learns states without ever passing the auth gate. Recommended response shape: uniform 404 for everything an unprivileged caller should not see. Framework RPC surfaces that don't appear as routes. Server actions and equivalents are publicly exposed RPCs that file scans miss. Enumerate and audit each one for auth + ownership: Next.js: every exported function in a file with 'use server' Remix / React Router: every action / loader export tRPC: every procedure GraphQL: every resolver Rails: non resource controller actions IDOR via foreign keys in mutation payloads. Form posts a foreign key UUID ( categoryId , projectId , teamId , organizationId ) → server validates ownership of the primary record but blindly accepts the FK → ORM relation join later surfaces another tenant's data. Look for formData.get("<id ") / body.<id passed straight to insert/update without a preceding findFirst({ where: { id, userId } }) . For ORM relation joins (Drizzle with: , Prisma include , ActiveRecord includes ), trace whether the join target is filtered by the same tenant/ownership predicate as the parent query. Inbound webhooks authenticate the caller before DB or paid work. Email, SMS, payment, and git webhooks that identify the account from the payload ( to address, slug) but never verify the provider's signature over the raw body let anyone trigger paid work and create records in another account. Provider signature table and raw body capture: [references/webhooks and parsers.md](references/webhooks and parsers.md 2 inbound webhook triggers paid work without authenticating the caller a01 a07). Grep: inbound , parse , sendgrid , mailgun , postmark , /api/. email , multer( , bodyParser: false . Missing CSRF protections on state changing requests Role checks only on the frontend, not enforced server side Open redirect via post auth return to parameter — ?from= , ?next= , ?returnTo= , ?continue= , ?redirect= passed unsanitized to redirect() / Response.redirect() . Restrict to same origin paths under the expected scope, normalize ( new URL(target, "http://localhost").pathname ) to defeat traversal like /admin/../foo . Also reject control bytes in the path before redirect: tab/newline/null ( \t , \n , \0 ) — URL parsers strip these and collapse /\tevil into protocol relative //evil ; null bytes can turn the redirect into a 500. Reject any byte in [\x00 \x1F\x7F] , any backslash, and any percent encoded slash/backslash ( %2f , %5c ). Grep for: direct object references, missing auth middleware, user ID from request params, redirect(. from , redirect(. next , redirect(. returnTo SSRF (A10 in 2021; part of A01 since 2025) User controlled URLs passed to server side HTTP requests Missing