diff --git a/.github/security-fixes-prompt.md b/.github/security-fixes-prompt.md index d72adbf51..c454e68be 100644 --- a/.github/security-fixes-prompt.md +++ b/.github/security-fixes-prompt.md @@ -41,7 +41,7 @@ A gem is a **direct** dependency if the owning package's gemspec (or its Gemfile Use bundler-generated resolution only for verification — no `Gemfile.lock` is ever committed in this repo. -For **npm-ecosystem** alerts (the tooling `package.json`/`yarn.lock` at the root): fix with yarn (`yarn why `, bump or root-`resolutions` pin bounded to the parent-compatible major, `yarn install` to update the committed `yarn.lock`). For **github-actions-ecosystem** alerts: bump the `uses:` reference in the affected `.github/workflows/*.yml`; `SECURITY_GH_PAT` carries the `workflows:write` permission this push requires — if the push is nevertheless rejected citing workflow permissions, unstage those changes, move the alerts to "Could not auto-fix (token lacks Workflows permission)", and ship the rest. +For **npm-ecosystem** alerts (the tooling `package.json`/`yarn.lock` at the root): fix with yarn (`yarn why `, bump or root-`resolutions` pin bounded to the parent-compatible major, `yarn install` to update the committed `yarn.lock`). For **github-actions-ecosystem** alerts: bump the `uses:` reference in the affected `.github/workflows/*.yml`; `SECURITY_GH_PAT` carries the `workflows:write` permission this push requires. If the push is nevertheless rejected citing workflow permissions, the files are already **inside the commit** — unstaging alone changes nothing. Rewrite the commit without them (`git reset --soft HEAD~1`, `git restore --staged --worktree .github/workflows/`, re-commit with the same message), then push again, move those alerts to "Could not auto-fix (token lacks Workflows permission)", adjust the Summary counts, and ship the rest. **5. Audit existing security pins.** Sweep **every** `Gemfile` (root and `packages/*/Gemfile`) for entries carrying a `# security pin` comment (or clearly pin-only entries not required by the code): - **Stale** — the gem no longer appears in that package's dependency tree at all. Remove the entry, re-resolve in that package (delete its local lockfile, `bundle lock`), confirm. @@ -102,11 +102,18 @@ The PR description must include the sections listed below. Mark the **Validation **Alert references in the body must be full Markdown links, never bare `#`.** GitHub auto-links `#` to issues/PRs in the same repo, which sends readers to the wrong page. Format every alert reference as `[#](https://github.com/$REPO/security/dependabot/)` — in every table AND in any inline mentions. -**Tables that require human action must start with a checkbox column** so first-level support can tick off each alert as they handle it. Use cell content `- [ ]` (GitHub renders it as an interactive checkbox in PR bodies, even inside table cells). Apply this to: -- **Fixed** table: add a leading `Done` column (ticked once the reviewer has verified the fix landed) -- **Ignored** table: add a leading `Dismissed` column (ticked once the reviewer has dismissed the alert in the repo's Security tab) +**Actionable checkboxes go in a task list, never in table cells.** GitHub renders `- [ ]` as an interactive checkbox only when it starts a list item; inside a table cell it stays literal text, and a raw `` is stripped by GitHub's sanitizer. So the tables carry **no** checkbox column, and the body ends with a **Review checklist** section built from real task lists: -Deferred / Security pins added / Security pins removed / Could not auto-fix are informational — no checkbox column. +``` +## Review checklist + +**Fixes to verify** — tick once you have confirmed the bump landed: +- [ ] [#](https://github.com/$REPO/security/dependabot/) — `` + +**Alerts to dismiss** — tick once dismissed in the repo's Security tab (reason in the Ignored section): +- [ ] [#](https://github.com/$REPO/security/dependabot/) — `` +``` +Omit either list if it would be empty. Deferred / Security pins added / Security pins removed / Could not auto-fix need no checkboxes — they are informational. The very first line of the PR description must be the following blockquote so first-level support knows how to review it: @@ -123,6 +130,7 @@ The very first line of the PR description must be the following blockquote so fi - **Risks**: per bump, from the upstream CHANGELOG — breaking changes touching APIs we use, tests likely to need updating. If no behavior change beyond the patched vuln, say so. - **Manual testing**: only if automated CI doesn't cover the affected paths — give concrete reproduction steps. Otherwise write "Covered by CI." - **Validation**: `⏳ Awaiting CI` for now. +- **Review checklist**: the task lists described above (this is where the checkboxes live). **8. Monitor CI and fix failures.** Poll **inside a single Bash loop per tool call** (about 10 minutes of `sleep 60` iterations per call — do NOT spend one tool call per poll, that would exhaust the turn budget). Each iteration fetches the workflow runs and the combined commit status for the PR head SHA (Actions + Commit statuses APIs only — the Checks API is not accessible to fine-grained PATs; check runs posted by GitHub Apps such as coverage bots are therefore a blind spot of this monitoring): ``` diff --git a/.github/workflows/security-fixes.yml b/.github/workflows/security-fixes.yml index be3b6f53d..613cb248e 100644 --- a/.github/workflows/security-fixes.yml +++ b/.github/workflows/security-fixes.yml @@ -13,7 +13,9 @@ name: Security fixes on: schedule: - - cron: "0 11 * * 4" # Thursdays 11:00 UTC — same slot as the former routine + # Staggered per repo: 5 concurrent Opus sessions on one API key + # tripped a rate limit on 2026-07-30 (first request never billed). + - cron: "0 13 * * 4" # Thursdays 13:00 UTC workflow_dispatch: {} permissions: @@ -34,7 +36,7 @@ jobs: BASH_DEFAULT_TIMEOUT_MS: "900000" BASH_MAX_TIMEOUT_MS: "3600000" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: token: ${{ secrets.SECURITY_GH_PAT }} @@ -61,3 +63,18 @@ jobs: --max-turns 250 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch" --append-system-prompt "This is an unattended scheduled maintenance workflow. You are explicitly authorized to create and switch to the security/ branch, commit, push it to origin, and create, update and label the pull request yourself via the GitHub REST API with the token in the GH_PAT environment variable, exactly as the instructions file specifies. Never defer pushing or PR creation to a human, never stop to ask for confirmation, and do not use any built-in branch or PR helper that would override these instructions." + + # The action redacts the agent transcript on purpose (it can contain + # secrets read from files or command output). Instead of enabling + # show_full_output, surface only the terminal error fields on failure — + # enough to tell an API/auth error from an application one. + - name: Surface the agent's terminal error (no transcript) + if: failure() + run: | + F=/home/runner/work/_temp/claude-execution-output.json + [ -f "$F" ] || { echo "No execution log at $F"; exit 0; } + jq -r '[.. | objects | select(.type == "result")] | last + | {subtype, is_error, terminal_reason, api_error_status, + num_turns, total_cost_usd, + result: ((.result // "") | tostring | .[0:400])}' "$F" \ + || echo "Could not parse the execution log"