The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY in this skill are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals.
A defensive, read-only audit of a repository's security hygiene posture — the deterministic layer of the repo's trust family. It answers one question: which posture defects can a checklist prove from the tree, and how severe is each one in this repo's actual context?
The division of labor is the point: assets/audit_posture.py finds candidate defects
deterministically (same tree in, same findings out), and the agent adjudicates each one with the
code open — a flagged debug=True in a test fixture is INFO; the same line in a production
entrypoint is HIGH. The tool never guesses context and you never grep by hand, because each side
does only what it is reliable at: the tool finds the same candidates every run, and you judge
each one in context.
Locating this skill's helpers (do this first). The steps below run bundled scripts. You execute from the target repo, not from this skill's directory, so a path written relative to this skill will not resolve. Resolve the base directory once and use it everywhere — including in any subagent prompt, which MUST receive the literal absolute path:
SKILL_DIR="<this skill's base directory>" # your harness provides it when the skill loads
# If you don't have it, discover it:
SKILL_DIR=$(find ~/.claude ~/.config ~/.agents -type d -name 'security-posture-audit' 2>/dev/null | head -1)
test -d "$SKILL_DIR/assets" || test -d "$SKILL_DIR/scripts" # verify before proceeding
Boundaries — what this is not
You MUST state these in the report so it cannot be over-read:
- Not a CVE scanner. No advisory database, no network, no version-vulnerability matching.
- Not SAST. No dataflow, taint, or injection analysis.
- Not a secrets-content scanner. It flags credential-shaped files by name
(
.env,*.pem,id_rsa); it never reads for secret values — pair it with a dedicated secrets scanner (this repo's own scan-leaks gate does that per-skill). - Not norms/claims verification (that is
base-in-reality) and not an agent-readiness audit (that isagent-ready-rails).
You MUST audit only repositories the user owns or is explicitly authorized to review; if authorization is unclear, you MUST ask before running.
Workflow
Python interpreter. Most commands in this skill and its references are written with python3.
Find the interpreter once, before the first of them. If $SKILL_PYTHON is set, use it. Otherwise,
on macOS, if command -v python3 prints /usr/bin/python3 and xcode-select -p fails, that
python3 is the install stub: skip it without running it. Use the first of python3, python and
py -3 that is not skipped and for which
<interpreter> -c 'import sys; sys.exit(sys.version_info < (3, 10))' exits 0. Write the interpreter
you found in place of python3 in every command, and in any Bash(...) permission rule you
suggest. Commands that start with uv run or {python} keep their own form. In a session resumed
under a Bash(...) allowlist, skip this lookup and use the interpreter that the allowlist names. If
none passes, follow this skill's own fallback for running without Python where it gives one;
otherwise stop and tell the user that this skill needs Python 3.10 or newer.
-
Scope with the user. Confirm the repo path and that they are authorized to audit it. Agree what is in and out of scope — vendored or generated trees go to
--skip-dir(.git,node_modules,vendor, build dirs are skipped by default) — and pick the--fail-onthreshold if the exit code will gate anything. -
Run the deterministic sweep.
python3 "$SKILL_DIR/assets/audit_posture.py" <repo> --format json --fail-on neverThis emits every draft finding as
{check, severity, path, line, evidence, remediation}plus a summary and thenot_coveredlist. Run it once more with--format textif a human-readable copy helps. Do not add findings the tool did not emit and do not drop findings silently — everything it found appears in the report, even if adjudicated down to INFO. -
Adjudicate each finding in context. Open the flagged file at the flagged line and judge the draft severity against
references/checks.md(each check's section says what it proves, what it cannot prove, and when to move the grade). Adjust up or down only with a stated reason, e.g. "debug-flag intests/fixtures/app.py— fixture, not an entrypoint ⇒ INFO" or "credential-filecerts/server.pemcontains only a public cert chain ⇒ INFO". Prefer keeping the draft when the context is ambiguous. -
Write the posture report (the deliverable — template below): findings grouped HIGH → MEDIUM → LOW → INFO, each with
file:lineevidence, its adjudication note, and a concrete remediation; then the Not covered section copied from the tool'snot_coveredoutput so nobody mistakes this for a CVE scan or pen test. -
Verify and repair. For each accepted finding the user wants fixed, apply the remediation, then re-run step 2 and confirm that specific finding is gone (and no new ones appeared). Quote the before/after summary counts in the report. A fix without a clean re-run is not done.
Deliverable — the posture report
# Security posture report: <repo> (<n> findings: H/M/L/I after adjudication)
## HIGH
- [check-id] path:line — evidence
adjudication: <kept draft | raised/lowered from X because ...>
fix: <concrete remediation>
## MEDIUM / LOW / INFO
(same shape)
## Not covered by this audit
<the tool's not_covered list, verbatim: CVEs, SAST, secret values, runtime/cloud, norms>
## Verification
<re-run summary for each applied fix: finding gone, before/after counts>
Output style. Write reports and explanations for the user in about 80% ASD-STE100 Simplified Technical English. Use short sentences, common words, active voice and one action per step.
The checks
Thirteen check ids, each documented in references/checks.md (what it proves, what it
cannot prove, severity rationale, remediation pattern):
| Check | Draft severity |
|---|---|
dep-unpinned-python, dep-unpinned-node, docker-unpinned-base |
MEDIUM |
credential-file (.env*/.pem/id_* presence, content never read) |
HIGH |
debug-flag, permissive-cors, insecure-transport |
MEDIUM |
workflow-prt-checkout, workflow-curl-pipe-sh |
HIGH |
world-writable / setuid-file |
MEDIUM / HIGH |
parse-error (unauditable manifest) |
LOW |
missing-security-md |
INFO |
Assets and verification
assets/audit_posture.py— the deterministic scanner (stdlib-only, offline; exit code reflects highest severity via--fail-on high|medium|low|info|never).assets/test_audit_posture.py— unit suite; run standalone withcd assets && python3 test_audit_posture.py.eval/run_eval.py— outcome eval: builds hardened and leaky fixture repos in a tempdir and grades the audit against both, including false-positive negatives.references/checks.md— the adjudication rubric for step 3.
Edge cases
- Monorepos / workspaces: a workspace-root lockfile can legitimately silence
dep-unpinned-nodefor nested packages — verify before accepting the finding. - Windows trees: file-mode checks are skipped (POSIX-only); say so in Not covered.
- Huge files: content checks skip files over ~1 MB; name/mode checks still apply.
- Unreadable/malformed manifests surface as
parse-errorentries, never crashes — report them as audit blind spots rather than dropping them.