Security Audit
Purpose
This skill reads and reports. It never writes. No file edit, no
write, no
GitHub issue, no invocation of another skill on the user's behalf. It detects security gaps
across whichever areas a repository exposes, aggregates them into one severity-ordered
report, and hands off. Every
command it produces is report
text for a human or a
downstream skill to run — the audit never executes one.
is the write half of the pair. It applies the baseline via
: rulesets, Actions token defaults, security features, immutable releases,
branch-restricted environments for deploy secrets. Findings from this skill are its input;
run it after an audit, or standalone on a new repo. If the user wants changes made rather
than gaps listed, that is the skill to use.
Read-only is what makes this skill safe to run anywhere, including on a repo the user does
not own. Keep it that way: a new audit area adds a checklist under
and a
prompt in
references/subagent-prompts.md
, never a mutation.
Audit areas currently implemented:
| Area | Checklist |
|---|
| GitHub Actions workflows under | references/actions-checklist.md
|
| Release configuration (, , , npm publishing) | references/release-checklist.md
|
| Repository settings via the GitHub REST API (Actions defaults, rulesets, security features, immutable releases, deploy environments) | references/repo-settings-checklist.md
|
| Package manager and install-time controls, pnpm and bun only (lifecycle-script allowlists, release-age gates, scope→registry mapping) | references/package-manager-checklist.md
|
| Application source code (injection and unsafe-execution vectors) | references/code-security-checklist.md
|
Not yet implemented: non-GitHub CI providers (CircleCI, GitLab CI), container security
(Dockerfiles, base-image pinning), committed-secret scanning beyond GitHub's native
feature, runtime config (CSP, CORS, security headers), dependency policy (license
allowlist, banned packages).
For dependency CVE scanning specifically, use
,
, or
directly — this skill does not duplicate that.
Boundary with . Both read
, split by consequence. This skill
owns the security surface: action pinning,
,
,
, OIDC, third-party action sources, and cache
poisoning or cross-branch
scope.
owns performance, spend, and gating correctness — the job graph, cache
hit
rate, matrix design,
, artifact retention, and
. Action pinning is
graded here and only here, so the same line of YAML never gets two severities. When
finds
a required check name that no workflow produces, it hands the observation over, because reconciling
rulesets against triggers needs the
calls only this skill makes.
When to Use
Use when the user asks to:
- "Audit security" / "security audit"
- "Check supply chain" / "review for supply-chain attacks"
- "Harden CI" or "harden actions" — audit first, then hand off to
- "Audit release workflow" / "audit npm publishing"
- "Harden settings" / "audit rulesets" / "audit repo settings"
- "Audit install scripts" / "audit lifecycle scripts" / "audit package manager"
Trigger phrases:
,
,
,
,
,
,
,
,
,
,
,
,
,
.
Not for:
- Applying the fixes →
- Bug hunting on a diff → ; style and comment noise →
- CI speed/parallelization →
- Dependency CVE scanning → run / directly
Asking the User
Every question in this skill is written as
options. Use that tool where
the host offers it, or the host's nearest structured-choice equivalent. Where the host has
neither, ask the same question in normal chat as a numbered list of 2–5 options —
recommended first, one short line of description each — and wait for the user to reply
with a number.
Workflow
Step 1: Detect Repo Layout
Run in parallel. Every command here is a read:
bash
ls .github/workflows/ 2>/dev/null
cat .github/dependabot.yml 2>/dev/null
jq '{name, packageManager, trustedDependencies, scripts: (.scripts | keys), publishConfig, private}' package.json 2>/dev/null
ls package-lock.json pnpm-lock.yaml yarn.lock bun.lock bun.lockb 2>/dev/null
cat pnpm-workspace.yaml 2>/dev/null
cat bunfig.toml 2>/dev/null
cat .npmrc 2>/dev/null
gh api repos/<owner>/<repo>/actions/permissions 2>/dev/null
gh api repos/<owner>/<repo>/actions/permissions/workflow 2>/dev/null
gh api repos/<owner>/<repo>/rulesets 2>/dev/null
gh api repos/<owner>/<repo>/immutable-releases 2>/dev/null
gh api repos/<owner>/<repo> --jq '{private, default_branch, security_and_analysis}' 2>/dev/null
Resolve
from
gh repo view --json nameWithOwner --jq .nameWithOwner
. If the API calls fail with
or auth errors, note it — repo-settings findings will be skipped for that area.
Extract org-scope context for Subagent D (compute in the orchestrator, then substitute):
- — derived from . If the name is , the prefix is . If the name is unscoped, leave empty (no org scopes will be flagged).
- — given a non-empty , owned scopes are AND any scope matching (e.g., for : , , ).
- — union of: from (use ),
minimumReleaseAgeExcludes
from , keys from , keys from , and lines from .
These pre-approve scopes the team has already decided to trust. Subagent D treats them as covered and emits no finding for missing config.
Default posture: deny everything. Subagent D flags missing exclude/registry config
only for owned scopes (matching
) that appear in the lockfile and are not in
. All other scopes stay under the release-age and dependency-confusion gates — that is the intended hardening. If
is empty, no scope-specific findings are emitted.
Extract release-target context for Subagent B (compute in the orchestrator, then substitute):
- — if any of these exist (the audit does not care WHICH non-npm publisher is used, only whether npm is involved): a / in containing a literal , , , , or ; a field in ; or a value containing any of those commands.
- — assembled from root file presence: if + any lockfile, if /, if , if , if /, if . is always added when exists. This drives item 7 (dependabot ecosystem coverage) so the audit recommends the right ecosystems regardless of stack.
Routing. Skip the Actions subagent if
is absent. Skip the Release subagent unless one of these holds: a
/
exists,
has
, or
has any of
,
,
. If
is absent or
, note it — release findings about provenance change severity. Dispatch the Package Manager subagent if any lockfile exists — including
or
, where its whole job is the one out-of-scope finding (the audit supports pnpm and bun only) rather than a tuning pass. Dispatch the Repo Settings subagent if
gh api .../actions/permissions
returns HTTP 200; skip only on
or auth error. An empty
from
is success — Subagent C treats the absence of rulesets as a finding, not a reason to skip. Skip the code subagent on a configuration-only or docs-only repo.
Step 2: Dispatch Audit Subagents in Parallel
Read
references/subagent-prompts.md
. It holds one prompt per audit area, the placeholder
substitution table, and the rule that no subagent mutates anything.
Dispatch
one subagent per applicable audit area, concurrently if the host allows it. If
the host has no subagent facility, run each area sequentially inline using the same prompts.
Substitute every placeholder before dispatch —
in particular, since
subagents are spawned with the user's CWD and relative checklist paths will not otherwise
resolve.
As new audit areas are added under
, add the matching prompt to that file. No
change to Step 3 or Step 4 is needed.
Step 3: Aggregate Findings
Wait for all dispatched subagents. Apply the four aggregation rules, then assemble the
report. Severities come from
references/severity-table.md
— use it to reconcile any
disagreement between subagents on the same finding shape.
Aggregation rule 1 — Pin-status cross-check (A ↔ C). Reconcile Subagent A's pin findings with Subagent C's
report:
- A reports zero pin findings (every is SHA-pinned) AND C reports
sha_pinning_required: false
→ emit high: "All actions are SHA-pinned but is off. Enable to prevent regression: gh api -X PUT /repos/<owner>/<repo>/actions/permissions -F sha_pinning_required=true
."
- A has pin findings AND C reports
sha_pinning_required: false
→ do NOT additionally flag the setting. The issue is the unpinned actions (already in A's report). In "Already hardened" add: " cannot be enabled until A's pin findings are fixed — re-run the audit afterward."
- C reports
sha_pinning_required: true
→ "Already hardened: enforced."
Aggregation rule 2 — Required-status-check coverage (A ↔ C). For each
{ruleset_id, protected_branches, required_checks}
entry in C's
map, for each
, for each
:
- Find workflow(s) in A's whose matches .
- If no workflow matches the name, OR none of the matching workflows trigger on (check both and ; treat as matching any branch), emit high: "Required check on branch (ruleset ) is not produced by any workflow triggering on that branch. PRs to will be permanently BLOCKED. Either add the branch to the workflow's / , or remove the check from the ruleset's ."
Aggregation rule 3 — Pass collation. Each subagent's report ends with a "passes" / "already hardened" section. Concatenate these into
, prefixed with the source subagent:
[Actions] persist-credentials: false on all checkouts
,
[Repo Settings] secret_scanning enabled
,
[Package Manager] minimumReleaseAge: 4320 with Strict: true
. For each subagent skipped per Step 1 routing, add instead:
[<area>] Skipped — <reason>
.
Aggregation rule 4 — Sequencing hints. If any finding pair has a "fix A before C is actionable" relationship (rule 1's second case is canonical; the rule-2 BLOCKED-PR finding depends on the ruleset existing at all; D's
fix may break installs until paired with
), surface the ordering in a final
section:
1. Apply [Actions] findings → 2. Re-run audit → 3. Apply [Repo Settings] sha_pinning_required recommendation.
Render the section only if at least one relationship exists.
Weighting. is the privileged target — weight findings there above equivalent findings in ordinary CI.
Report skeleton:
markdown
## Security Audit Report
<one-line summary: areas audited, areas skipped>
### Critical
### High
### Medium
### Low / Informational
### Already hardened
- [Actions] ...
- [Release] Skipped — <reason>
### Sequencing
1. ...
Sort findings within each severity bin worst-first (most exploitable first; policy findings after CVE-rated findings of the same severity). Keep file:line / API-path references intact from each subagent's report.
Step 4: Hand Off Findings
The audit stops here. Step 4 produces two artifacts side by side — a task queue for
downstream work, and a Planned Changes report for the user to read. Do not edit files, run
writes, create issues, or invoke
,
,
, or any other skill on the user's behalf.
Hosts differ in whether they have a task tracker; where none exists, the Planned Changes
report is the handoff and the options below collapse to option 4.
Ask, per Asking the User:
Emit tasks + planned-changes report
(Recommended) — one task per finding, plus the report below
Emit tasks flagged for issue filing
— same as 1, with intended_action: file_issue
metadata
Emit a single summary task
— one task summarising all findings, suitable for a single tracking issue
All four options render the Planned Changes report (so the user always sees the change plan and revert commands). Options 1–3 additionally emit tasks.
Task emission rules (options 1, 2, 3)
Skip this subsection entirely when the host has no task tracker.
- One task per finding from the Step 3 report (options 1, 2). For option 3, emit a single task whose description is a numbered list of all findings.
- : short finding title (e.g., "Pin to SHA").
- : severity (including any qualifier), source subagent (, , , , ), file:line or API path, the exact remediation snippet from the report.
- : minimum set — , , ( for file edits / for writes), ( / / ). Keep metadata light — the task queue is a handoff, not the audit's persistence layer.
- Sequencing: for any pair from Step 3's section, record the downstream task as blocked by the upstream one, using whatever dependency field the host's tracker exposes. If it has none, state the ordering in the task description.
- Emit tasks worst-first within each severity, matching the report's order.
Planned Changes report
Render directly in the chat output, grouped by source area. It is the audit's plan-of-record,
not an execution transcript — phrasing matters. Use
and
(never
), because the audit does not observe what actually runs.
Code edits get one line; server-state changes get three.
markdown
## Security Audit — Planned Changes
Plan-of-record (not an execution transcript). Render of the Step 3 findings as code edits and `gh api` calls a downstream skill or human will apply.
### [Actions] (N planned changes)
- `.github/workflows/ci.yml` — pin 6 `uses:` lines via `pinact run`
- `.github/workflows/release.yml` — add workflow-level `permissions: contents: read`
### [Release] (N planned changes)
- (or: "No changes planned — audit area skipped per Step 1 routing")
### [Repo Settings] (N planned changes)
- Set `default_workflow_permissions: read` on <owner>/<repo>
- Recommended command: `gh api -X PUT /repos/<owner>/<repo>/actions/permissions/workflow -f default_workflow_permissions=read -F can_approve_pull_request_reviews=false`
- Revert command: `gh api -X PUT /repos/<owner>/<repo>/actions/permissions/workflow -f default_workflow_permissions=write -F can_approve_pull_request_reviews=true`
- Enable immutable releases on <owner>/<repo>
- Recommended command: `gh api -X PUT /repos/<owner>/<repo>/immutable-releases`
- Revert command: `gh api -X DELETE /repos/<owner>/<repo>/immutable-releases`
### [Package Manager] (N planned changes)
- `pnpm-workspace.yaml` — add `minimumReleaseAge: 4320`, `minimumReleaseAgeStrict: true`
### Deferred or skipped (informational)
- [Actions] `pull_request_target` finding in `ci-fork.yml` — flagged as `informational`, no fix planned
- [Repo Settings] `sha_pinning_required` — depends on `[Actions]` pin changes per Step 3 sequencing
Format rules:
- Every area gets a section, even if empty, so the user can see which areas had nothing to apply or were skipped.
- Code edits are one line: file path + short description. No block — the edit content lives in the task description.
- Server-state changes are three lines: title, , . Both are the literal invocations from the Step 3 finding.
- A section captures anything not fixed (informational items, sequencing-blocked items). Nothing is silently dropped.
Handoff line
After rendering the report and emitting tasks, print one of:
- Option 1: "N tasks emitted. Run for the changes, or to walk through the file edits."
- Option 2: "N tasks emitted with flag. Run per task when you're ready."
- Option 3: "Single summary task emitted. Run to file it as a tracking issue."
- Option 4: "No tasks emitted; the report above is the full audit output."
Then stop. Do not invoke any of those skills yourself.
References
| File | Contents |
|---|
references/subagent-prompts.md
| Dispatch prompt per audit area, plus the placeholder substitution table |
references/severity-table.md
| Severity per finding shape; the release-age gate's four units |
references/actions-checklist.md
| Detection, severity, and fix for each workflow check |
references/release-checklist.md
| Release trigger, provenance, staged publishing, artifact identity |
references/repo-settings-checklist.md
| Exact detection and remediation commands, bypass-actor patterns |
references/package-manager-checklist.md
| Per-manager field names, units, defaults, and fix examples |
references/install-flags.md
| Frozen-lockfile and flags per manager, multi-stage Docker |
references/code-security-checklist.md
| Injection and unsafe-execution vectors by stack |