Skip to content

Commit 0eb0fda

Browse files
nedtwiggclaude
andcommitted
Make AUDIT_PAT required, fail loudly when missing
The audit's gh queries against repo administration endpoints (/rulesets, /actions/secrets, /environments/*) require AUDIT_PAT. Previously the workflow degraded gracefully when AUDIT_PAT was absent, recording those checks as UNVERIFIABLE. That left half of the FAIL IFs in SECURITY.md unenforceable, which defeats the purpose of having the audit at all. New pre-check step "Verify AUDIT_PAT is provisioned" runs before Claude. If the secret is empty it writes a FAIL status, a self-explanatory audit-report.md (so the issue body explains the problem instead of pointing at logs), and exits non-zero. The Surface step then files the issue as usual. Claude's prompt now treats 403 from gh api as a real FAIL ("PAT's scope has drifted") rather than UNVERIFIABLE — since AUDIT_PAT is guaranteed present by the time Claude runs, 403 means the token's permissions are wrong. SECURITY.md: the AUDIT_PAT FAIL IF now asserts presence in the env (not just placement); the audit-weakening FAIL IF covers removing the pre-check; the CI Validation Contract prose calls the token required rather than optional. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c48230e commit 0eb0fda

2 files changed

Lines changed: 46 additions & 21 deletions

File tree

.github/workflows/security-audit.yaml

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,41 @@ jobs:
4545
- name: Install workspace dependencies
4646
run: pnpm install --frozen-lockfile
4747

48+
- name: Verify AUDIT_PAT is provisioned
49+
env:
50+
AUDIT_PAT: ${{ secrets.AUDIT_PAT }}
51+
run: |
52+
if [ -n "$AUDIT_PAT" ]; then
53+
echo "AUDIT_PAT present."
54+
exit 0
55+
fi
56+
echo "FAIL" > audit-status.txt
57+
cat > audit-report.md <<'EOF'
58+
## Summary
59+
60+
**Overall status: FAIL.** `AUDIT_PAT` is not present in the
61+
`security-audit` environment.
62+
63+
The audit refuses to run without `AUDIT_PAT`. It needs read-only
64+
`Administration`, `Secrets`, and `Environments` access to
65+
verify the ruleset, secret, and environment FAIL IFs.
66+
67+
Provision per `SECURITY.md` > CI Validation Contract: mint a
68+
fine-grained PAT scoped to this repository only, then run
69+
`gh secret set AUDIT_PAT --env security-audit --repo
70+
diffplug/dormouse`.
71+
EOF
72+
echo "::error::AUDIT_PAT secret is not set in the security-audit environment."
73+
exit 1
74+
4875
- name: Audit against SECURITY.md
4976
uses: anthropics/claude-code-action@4481e6d3c7bbb88db2a928ca3444c536f589c7c1 # v1
5077
env:
51-
# Prefer AUDIT_PAT (fine-grained, read-only Administration +
52-
# Secrets + Environments) so checks against /rulesets,
53-
# /actions/secrets, and /environments succeed; fall back to
54-
# the default workflow token so the audit still runs (with
55-
# UNVERIFIABLE results on those endpoints) if AUDIT_PAT is
56-
# absent.
57-
GH_TOKEN: ${{ secrets.AUDIT_PAT || github.token }}
78+
# AUDIT_PAT is fine-grained, read-only Administration +
79+
# Secrets + Environments. Required (the previous step
80+
# refuses to proceed without it), so no fallback to
81+
# github.token.
82+
GH_TOKEN: ${{ secrets.AUDIT_PAT }}
5883
with:
5984
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
6085
# Without an explicit allowlist the action defaults to a
@@ -83,17 +108,17 @@ jobs:
83108
references secrets, for security holes the specs don't
84109
cover.
85110
86-
`$GH_TOKEN` is either an `AUDIT_PAT` (fine-grained, read-only
87-
Administration + Secrets + Environments) when the repo has
88-
that secret configured, or a default `GITHUB_TOKEN` otherwise.
89-
If `gh api` returns 403 / "Resource not accessible by
90-
integration" on a check (typically `/rulesets`,
91-
`/environments/*`, or `/actions/secrets`), the deployment is
92-
running without `AUDIT_PAT`; record the check as
93-
`UNVERIFIABLE` (not `FAIL`) with a note that minting
94-
`AUDIT_PAT` (Administration + Secrets + Environments, all
95-
read-only) would cover it. `UNVERIFIABLE` checks do not flip
96-
the overall status to FAIL on their own.
111+
`$GH_TOKEN` is the `AUDIT_PAT` — a fine-grained,
112+
read-only PAT scoped to Administration + Secrets +
113+
Environments. A previous workflow step has already verified
114+
the token is present, so you can assume it is. If `gh api`
115+
returns 403 / "Resource not accessible by integration" on
116+
any check, record that check as `FAIL` with a note that
117+
`AUDIT_PAT`'s scope has drifted from what SECURITY.md
118+
specifies — do not treat it as `UNVERIFIABLE`. Reserve
119+
`UNVERIFIABLE` for the rare case where an endpoint is
120+
genuinely indeterminable (e.g. a transient network error),
121+
and note explicitly why in the report.
97122
98123
Produce a Markdown report with three sections:
99124
- `## FAIL IF results` — one line per check with PASS/FAIL

SECURITY.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ This repository runs the [tend](https://github.com/max-sixty/tend) agent harness
4747
- FAIL IF `dormouse-bot` holds a permission higher than `push` on this repository.
4848
- FAIL IF `OVSX_PAT` or `VSCE_PAT` appears as a repo-level secret. They must live only in the `vscode-extension-publish` environment.
4949
- FAIL IF any GitHub environment's deployment-branch-policies admit a ref that is not admin-gated by the `Tag operations` or `Merge access` rulesets. Today this covers `vscode-extension-publish` (`v*` tag, admin-only via `Tag operations`) and `security-audit` (`main` admin-only via `Merge access`, plus `v*` tag).
50-
- FAIL IF `AUDIT_PAT` exists at the repo level. If the secret is provisioned at all, it must live in the `security-audit` environment so a bot-pushed feature branch cannot reach it.
50+
- FAIL IF `AUDIT_PAT` is missing from the `security-audit` environment, or is present at the repo level instead. The audit refuses to run without it, and it must be env-scoped so a bot-pushed feature branch cannot reach it.
5151
- FAIL IF `CHROMATIC_PROJECT_TOKEN` is missing from `secrets.allowed` in `.config/tend.yaml`. The allowlist entry is an explicit acknowledgment that the bot can read this token.
5252
- FAIL IF `.github/workflows/workflow-audit.yaml` is missing, disabled, or has not produced a successful run in the last 48 hours.
5353
- FAIL IF any `tend-*.yaml` workflow uses an unpinned action reference (e.g. `@main`, no version). Inside `tend-*.yaml`, both tag pins (`@v6`, `@0.0.25`) and SHA pins are accepted because the file is owned by the upstream generator (`max-sixty/tend`), which currently uses tag pins. All actions in every other workflow — including `workflow-audit.yaml` and `security-audit.yaml` — must follow the SHA-pin rule in "GitHub Actions Policies".
@@ -78,11 +78,11 @@ The audit job declares `environment: security-audit`, whose deployment-branch-po
7878

7979
As a consequence of that env-gating, audit changes are iterated on `main` directly. A `workflow_dispatch` from any other ref is rejected by the environment's deployment-policy before any step runs. To experiment on a branch, widen the env's policy temporarily and revert after.
8080

81-
`AUDIT_PAT` itself is optional. Without it, the audit's `gh` queries against `/rulesets`, `/actions/secrets`, and `/environments/*` return 403 and the affected checks are recorded as `UNVERIFIABLE` rather than `FAIL`. To upgrade them to `PASS`, mint a fine-grained PAT on an admin's account with read-only `Administration` + `Secrets` + `Environments` scoped to `diffplug/dormouse` only, then store it env-scoped:
81+
`AUDIT_PAT` is **required**. The audit's first step verifies the secret is present and refuses to run otherwise — without it the audit cannot read the administration endpoints needed to verify ruleset bypass actors, repo-level secret listing, and environment policies, so the spec it claims to enforce would be unenforceable in its key sections. Mint a fine-grained PAT on an admin's account with read-only `Administration` + `Secrets` + `Environments` scoped to `diffplug/dormouse` only, then store it env-scoped:
8282

8383
```bash
8484
gh secret set AUDIT_PAT --env security-audit --repo diffplug/dormouse --body 'github_pat_…'
8585
```
8686

8787
- FAIL IF `.github/workflows/security-audit.yaml` is missing, disabled, or no longer invoked from `release.yml`'s publish path.
88-
- FAIL IF the audit has been weakened — e.g. the prompt no longer requires the qualitative pass, a `FAIL IF` can be ignored, or the failure-reporting step that opens a `security-audit-failure` issue and exits non-zero has been removed.
88+
- FAIL IF the audit has been weakened — e.g. the prompt no longer requires the qualitative pass, a `FAIL IF` can be ignored, the failure-reporting step that opens a `security-audit-failure` issue and exits non-zero has been removed, or the `AUDIT_PAT` pre-check is removed or bypassed.

0 commit comments

Comments
 (0)