Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: auto-audit-security description: "Use when a codebase needs an adversarial security review or compliance readiness assessment — web UI and API security, authentication and access control, licensing or paywall bypass, secrets exposure, dependency vulnerabilities, and SOC 2 / ISO 27001 / HIPAA / PCI DSS / GDPR readiness; when the user mentions security audit, pen test, red team, blue team, purple team, white team, tabletop or adversary emulation, SOC 2, compliance, Vanta, hardening, or says \"/auto-audit-security\"; or when about to call a system secure or compliant."
/auto-audit-security
An adversarial security review of this codebase, plus an honest compliance-readiness assessment — findings first, fixes only when you approve them.
The boundary, stated once and never blurred: a code audit produces INPUT EVIDENCE to a compliance program. It never produces the compliance artifact. SOC 2 is an attestation engagement performed under SSAE 18 by an independent licensed CPA firm; ISO 27001 is certified by an accredited body. What this skill produces is a readiness assessment: which technical controls are evidenced in the code, which are missing, and what an auditor will ask for that no repository can answer.
Two facts worth getting right, because vendors routinely get them wrong:
- The Trust Services Criteria comprise 61 criteria, of which 18 are the privacy category.
Higher counts in circulation (64, 80+) come from counting points of focus as criteria — they are explicitly not criteria.
- A SOC 2 report is restricted-use. SOC 3 is the general-use report. If the goal is something
publishable on a marketing site, that is SOC 3, and confusing the two misdirects the whole effort.
Everything this skill generates is IPE — information produced by the entity. A service auditor will test its completeness and accuracy before relying on it, so every artifact must record how it was produced, over what population, and at what time.
Step 1 — Scope And Authorization
Establish before running anything:
- What is in scope — repo only, or deployed environments too? Which environments?
- Authorization for active testing, in writing. Static review of code you own needs none. **Any
active scan (ZAP, nuclei, fuzzing, credential testing) requires explicit written authorization naming the specific target, and must never be pointed at production, at shared infrastructure, or at a third party.** Staging, a local instance, or not at all. Third-party-hosted components have their own testing policies and are frequently out of bounds regardless of who owns the account.
- Which frameworks matter — SOC 2, ISO 27001, HIPAA, PCI DSS, GDPR/CCPA, or none. Ask; do not
assume, since the applicable set drives everything downstream.
- Where findings may be written. Not a public issue tracker. See Handling below.
Step 1b — Decide Which Seats You Are Sitting In
An audit run from one seat produces one class of finding, and the classes it cannot produce look exactly like an absence of problems. Name the passes before you start, and record which you are not running and why — that list belongs in the report's methodology section, because an unexercised seat is a coverage gap, not a clean result.
The colours are the InfoSec colour wheel (April C. Wright, 2017): red, blue and yellow are primary; purple, orange and green are their combinations. White is the referee — NIST defines a White Team as the group refereeing an engagement between a Red Team of mock attackers and a Blue Team of defenders, and here it is the seat that owns authorization, scope and evidence. Black is not a standard term; it is used here for the surface that is not the application.
| Pass | Seat | What it establishes | Needs authorization? | |
|---|---|---|---|---|
| Red | attacker | Reach an objective the way an attacker would: chain the medium findings, abuse the intended feature, bypass the paywall, escalate the role. Objective-led, not checklist-led | Yes for anything active | |
| Blue | defender | Would anyone have noticed? Are the auth failures, privilege changes, exports and admin actions logged, retained, alertable and attributable? Can you answer "what did this account do" | No | |
| Yellow | builder | Is the security control implemented where every caller passes through it, or reimplemented per route? Are the primitives the platform's own, or hand-rolled | No | |
| Purple | red + blue | For every Red finding: a detection for it, and a regression test. This is the pass that converts a one-off exercise into standing coverage | No | |
| Orange | yellow + red | For every Red finding: the change to how it is built that removes the class — a middleware, a type, a default, a lint rule | No | |
| Green | yellow + blue | Deployment reality: TLS and headers as actually served, secrets as actually injected, permissions on the running artifact, the database reachable from where it should not be | Read-only unless authorized | |
| White | referee | Authorization in writing, rules of engagement, what is out of bounds, what counts as evidence, where findings may be written, and the severity adjudication when passes disagree | Owns it | |
| Black | outside the app | Physical access, devices and removable media, the supply chain (dependencies, build infrastructure, signing keys, CI credentials), and the human path — support tooling, account recovery, social engineering. Almost always the least-tested surface | Yes, and usually out of scope |
Authorization is per pass, not per audit. White grants Red and the active half of Green. Blue, Yellow, Purple and Orange are review passes over code and configuration you already own and need no new permission. Never let a Red objective drift into a Black one — social engineering and physical entry require separate, explicit, written authorization naming the people and premises involved, and their absence is a hard stop, not a judgement call.
Purple and Orange are derived. They consume Red's findings, so they produce nothing before Red has run, and a report claiming them without a Red pass behind them is describing work that did not happen.
The most common real outcome on a first audit is that Blue finds nothing to look at — no authentication logging, no alerting, no retention. Record that as the finding it is. "We would not know" is a more serious result than most of the mediums the scanners produce.
Step 2 — Map The Attack Surface
You cannot audit what you have not enumerated. Produce the inventory first:
- Every route/endpoint, with method, and whether it is authenticated
- Every trust boundary — where untrusted input enters, where privilege changes
- Every authentication path, including recovery, SSO, API keys, and service-to-service
- Every entitlement check — what gates a paid feature, and where that check executes
- All data stores, and what class of data each holds (PII, credentials, payment, health)
- All third-party integrations and what each is trusted with
- The shipped client bundle and any mobile binary, treated as fully readable by an attacker
This inventory is the denominator for everything after it. A review that samples routes will miss the one unprotected endpoint, and that is the one that matters.
Step 3 — Access Control, Route By Route
This is the class scanners systematically miss, and the class most likely to be exploitable. It cannot be sampled and it cannot be delegated to a tool.
For every route in the inventory, answer explicitly:
- Does it authenticate? Does it authorize, separately?
- Is the authorization check server-side? A check in the UI layer is not a check.
- Does it verify the caller owns the object it operates on, or only that the caller is logged in?
(That gap is IDOR/BOLA, and it is the most common serious finding in real applications.)
- Can a parameter change the identity or scope of the operation (mass assignment, tampered
user_id, tampered price or quantity)?
- Are administrative functions protected by a role check on the server, or only unlinked in the UI?
Produce a table with a row per route and an explicit verdict per column. "Probably fine" is not a verdict — trace the code path to the check, or record it as unverified.
Step 4 — Licensing And Paywall Bypass
Treat the client as fully hostile and fully readable — it is.
- Entitlement evaluated client-side. If the client decides whether the user is premium, the user
decides whether the user is premium. The server must gate the data, not just the UI.
- Feature flags that only hide UI. Hidden is not disabled. Call the endpoint directly and see what
comes back.
- Trial or license state stored on the device — local storage, preferences, a file, a registry key.
All user-writable.
- In-app purchase receipts validated on device. Receipt validation belongs server-side, against the
store's API.
- Secrets in the shipped bundle: API keys, signing keys, admin endpoints, or private hostnames in
JS bundles, source maps, or a mobile binary. Extract and grep the actual artifact rather than the source tree.
- Premium data on unauthenticated endpoints — the paywalled screen calls an API; call it directly
without a session.
- Quota and rate limits enforced only in the client.
- Price, quantity or plan sent from the client and trusted by the server.
For each: the correct design is that the server independently determines entitlement on every request from state the user cannot write.
Step 5 — The Rest Of The Classes
Cover the current OWASP Top 10 and API Security Top 10. Always name the edition — write `A01:2025`, never a bare `A01` or "OWASP Top 10" — and confirm which edition any scanner ruleset actually encodes before citing it as coverage. Supply-chain findings map to A03:2025 Software Supply Chain Failures. Pull the current codes at audit time rather than reciting them.
By class, with the concrete question to answer in this codebase:
- Injection — SQL/NoSQL/command/template/LDAP. Is every query parameterized? Is any shell command
built from input?
- Authentication — session fixation, rotation on privilege change, lifetime, logout invalidation.
JWTs: is the signature actually verified, is alg pinned, are exp/aud/iss checked, is the secret strong? OAuth/OIDC: strict redirect_uri matching, state, PKCE.
- Password storage — a current memory-hard KDF with current parameters. Verify against present-day
guidance rather than a number remembered from years ago.
- Cryptographic failures — legacy algorithms, ECB mode, static or reused IVs, homegrown crypto, keys
in source, missing TLS verification, certificate pinning that was disabled "temporarily".
- SSRF — any server-side fetch of a user-supplied URL, including webhooks, previews and importers.
- XSS and CSP — output encoding by context,
dangerouslySetInnerHTMLand equivalents, and whether
CSP is real or unsafe-inline.
- CSRF and SameSite, on every state-changing endpoint that accepts cookies.
- Deserialization, XXE, file upload — type and size validation, storage location, execution
prevention.
- Logging — are credentials, tokens or PII being logged? Is there enough logging to reconstruct an
incident? Both failures are findings.
- Error handling — stack traces or internal paths returned to clients.
Step 6 — Run The Scanners, Then Distrust Them
Automation is the floor, not the audit. It is good at known-vulnerable dependencies and hardcoded secrets, and poor at logic and access control. State that in the report: the access-control findings in Step 3 have no tool path at all, so a reader must not read a clean scan section as covering them.
Separate the reporting run from the gating run. They need different flags, and combining them is how a gate silently narrows: a filter that reduces noise in a report also reduces what the gate can fail on.
# SAST — --error is required for gating; semgrep exits 0 with findings without itsemgrep --config=<pinned-local-rules> --sarif -o semgrep.sarif --metrics=off . # reportsemgrep --config=<pinned-local-rules> --error --metrics=off . # gatepipx install 'bandit[toml,sarif]' && bandit -r src/ -f sarif -o bandit.sarif # exits 1 on findingsgosec -fmt=json -out=gosec.json ./...# dependencies — prefer reachability where availableosv-scanner scan source -r . # 0 = clean, 1 = findings, 128 = TOOL FAILURE, never a passgovulncheck ./... # Go, reachability-awarepip-audit -r requirements.txt ; cargo audit ; npm audit --omit=devgrype <target> --only-fixed -o table # report: actionable subsetgrype <target> --fail-on high -o json --file out.json # gate: no fix-state filtertrivy fs --scanners vuln,secret,misconfig,license --severity HIGH,CRITICAL --exit-code 1 \--format sarif -o trivy.sarif . # 0 clean, 1 findings, other = tool error# secrets — history, not just the working treegitleaks detect --source . --redact --report-format sarif --report-path gitleaks.sariftrufflehog git file://. --no-verification --fail --json | jq 'del(.Raw, .RawV2)' > /secure/path/th.json# IaC / containercheckov -d . --compact ; trivy config .
Four rules about the tooling:
Exit codes are per-tool, and several default to 0 with findings. Verify each one's documented contract and gate on it explicitly. osv-scanner exit 128 is a hard failure of the audit itself — treating it as a pass means you shipped on a scan that never ran. bandit's -c pyproject.toml errors if the file has no [tool.bandit] table, so a repo without that section needs the flag dropped, not added.
Record what was actually scanned. Every scan's evidence entry needs the count of packages or files examined. A misconfigured scan that examined nothing produces a clean report indistinguishable from a secure codebase.
Scanners exfiltrate. semgrep --config=auto and registry packs fetch rules and send metrics; use pinned local rules and --metrics=off for confidential code, and osv-scanner --offline --offline-vulnerabilities with a pre-downloaded database when the dependency list itself is sensitive.
`trufflehog` verification mode authenticates with the credentials it finds — real outbound requests using live secrets. Use --no-verification for the inventory pass; --results=verified only with written scope naming verification as permitted, and tell whoever watches the logs first. Never write its JSON into the repo tree, and strip .Raw/.RawV2 before any human sees or stores it.
Active scanning (ZAP, nuclei) only against an authorized non-production target, and never as a default step.
Then triage: reachability beats raw severity, and a CVE in a dev-only dependency that never ships is not the same finding as one in the shipped artifact. Say which is which.
Step 7 — Compliance Readiness, Honestly
For each framework in scope, read the current criteria from the authoritative source at audit time rather than reciting identifiers from memory, then classify every control:
| Verdict | Meaning | |
|---|---|---|
| Evidenced in code | The control exists and you can point to it — file, config, or CI job | |
| Partially evidenced | Present but incomplete or inconsistently applied | |
| Not evidenced | Applicable, absent | |
| Not answerable from a repository | Organizational — HR onboarding and offboarding, vendor management, physical security, policy documents, board oversight, incident-response process, background checks, training |
That last row is the honest half, and it is usually the larger half. Say so plainly: a repository can speak to change management, access control, encryption in transit and at rest, logging and monitoring, vulnerability management, and secure development practices. It cannot speak to whether offboarding actually revokes access, whether vendors were assessed, or whether anyone reviewed the policy.
Where the code can provide evidence, name the artifact an auditor would accept: branch protection settings, required reviews, CI gates, the dependency-scanning job, encryption configuration, log retention, access-control tests.
Never output the words "compliant", "certified", or "passing" about a framework. The output is readiness, gaps, and the evidence you can hand an auditor.
Step 8 — The Report
Write to .audit/security-report.md — and treat it as sensitive (see Handling).
Per finding:
id · title · severity (with the reasoning, not just a label) · CWE where it applieslocation: file:line, or endpoint + methodprecondition: what an attacker needs (unauthenticated? a free account? a specific role?)reproduction: the minimal concrete stepsimpact: what is actually reachable — data, funds, privilegeremediation: the specific change, not "validate input"verification: the test that proves it fixed
Order by exploitability and impact, not by scanner severity. An unauthenticated IDOR exposing customer records outranks a dozen medium-severity dependency advisories in code that never executes.
Separate the compliance-readiness section, with the four-way verdict per control and a clear statement of scope and method.
Open with a scope, methodology and coverage section: what was reviewed and how, the population covered (which branches, which commit, which environments), what each tool examined and how many items it actually saw, what was skipped, and what could not be verified without an environment you did not have. Name which classes have no automated coverage.
State which team passes ran, and which did not. A reader cannot calibrate a report without it: an audit with no Red pass has not tested exploitability, one with no Blue pass says nothing about detection, and one with no Black pass has not looked at the supply chain or the human path. List the seats exercised, the seats declined, and for each declined one whether it was out of scope, unauthorized, or simply not done.
Close the loop on lifecycle: every finding needs a state — open, fixed, or risk-accepted with an owner and a date. A finding nobody accepted and nobody fixed is still open, and silently dropping it between runs is how a report becomes fiction.
Step 9 — Then Ask
Present the report and ask what to fix. Do not start editing.
Offer, in order:
- Critical and exploitable now — recommend fixing these immediately, one at a time, with a
regression test per fix that fails before and passes after.
- Structural — an access-control pattern applied in one place should be applied at a chokepoint;
propose the refactor rather than patching each caller.
- Hygiene — dependency bumps, header hardening, lint rules that prevent recurrence.
- Compliance gaps with a code answer — CI gates, branch protection, logging, encryption config.
For each fix applied: verify it, and add the check that prevents recurrence — a test, a CI gate, a lint rule. A security fix without a gate returns.
Handling The Report
A document describing live, exploitable vulnerabilities is one of the most sensitive artifacts a project has.
- Never commit it to a public repository, and never paste findings into a public issue, PR, or chat.
- Keep it out of the working tree if the repo is public — write it somewhere gitignored and say where.
- Do not put reproduction detail for an unfixed vulnerability into any tracker that is not private.
- Redact credentials, tokens and customer data from every excerpt.
- When a finding is fixed and released, the detail can be shared; before that it is a weapon.
If findings involve customer data exposure, note that breach-notification obligations may have clocks attached, and that determination belongs to counsel — flag it, do not adjudicate it.
Rationalization Table
| Excuse | Reality | |
|---|---|---|
| "We ran the audit" | From which seats? An audit with no Blue pass says nothing about whether you would notice | |
| "No detections fired, so nothing happened" | Or nothing is instrumented. Blue's first finding is usually that there is nothing to look at | |
| "Red found it, and we fixed it" | Half done. Purple asks for the detection and the regression test; Orange asks what stops the class | |
| "The scan came back clean" | Scanners barely find access-control bugs. That is the class that gets exploited | |
| "We're SOC 2 compliant now" | An audit firm says that after examining the organization. This is readiness | |
| "The UI hides it from free users" | Hidden is not disabled. Call the endpoint | |
| "The client checks the license" | Then the user controls the license. Gate the data server-side | |
| "The API key is only in the mobile app" | The binary is readable. That key is public | |
| "It's behind login, so it's fine" | Authentication is not authorization. Check object ownership | |
| "It's an internal endpoint" | Verify that claim from the network config, not the name | |
| "I'll run ZAP against prod to be thorough" | Never. Authorized non-production targets only | |
| "That CVE is in a dev dependency" | Then say so and rank it accordingly — but confirm it does not ship | |
| "I'll open an issue with the repro" | Not in a public tracker for an unfixed vulnerability | |
| "Severity is medium, the scanner said so" | Rank by exploitability and impact in this system | |
| "The scan passed" | Check it scanned anything. Record the count of files and packages examined | |
| "Non-zero exit, probably a tool glitch" | osv-scanner 128 means the audit did not run. Fail closed | |
| "I'll cite A01 for this" | Name the edition — A01:2025. A bare code is ambiguous across editions | |
| "We want a SOC 2 report for the website" | SOC 2 is restricted-use. The public one is SOC 3 | |
| "We can fix it later, it's theoretical" | Then demonstrate it is unreachable. Otherwise it is a finding |
Red Flags — Stop
- Using the words "compliant" or "certified" about any framework
- Running an active scan without explicit authorization for that specific target
- Pointing any scanner at production
- Sampling routes for access control instead of enumerating all of them
- Reciting a framework criterion id or OWASP category code without checking the current edition, or
citing an OWASP category without its edition year
- Combining a report-narrowing filter with a gating flag in the same scanner run
- Reporting a clean scan without recording how many files or packages it actually examined
- Committing the findings report to a public repo, or putting a repro in a public issue
- A CI security gate that continues on a non-zero exit
trufflehogwith verification enabled against credentials you did not intend to test- Fixing anything before the user has seen the report and chosen
- Declaring a fix done without a regression test and a gate against recurrence