updating-security
Walk open Dependabot security alerts on the current repo and fix
them via the cheapest principled mechanism. Discovers the alert set
inline, then runs a Workflow that pipelines each alert through
classify → fix → validate → commit independently. Invoked directly
via /update-security or as Phase 5 of the updating umbrella.
When to use
- A
gh dependabot alerts listing shows open advisories.
- The GitHub web UI security tab is non-empty after a push (
gh
warns "Dependabot found N vulnerabilities" on push completion).
- As part of weekly maintenance (the
updating umbrella invokes
this automatically when alerts are present).
What it does NOT do
- Disable alerts at the repo level. Suppressing the security
tab via repo settings is a separate (heavier) decision; this
skill resolves the underlying CVEs.
- Touch
dependabot.yml. The fleet ships a no-op
dependabot.yml (open-pull-requests-limit: 0) so Dependabot
doesn't open version-update PRs; security alerts are independent
and surface regardless.
- Auto-dismiss without evidence. Dismissals require a reason
matching one of GitHub's documented values
(
fix_started / inaccurate / no_bandwidth / not_used /
tolerable_risk) and a one-line justification. The skill asks
before dismissing.
Phases
Phase 1 (Discover) runs inline — one gh api call to build the work-list. The per-alert work (Phases 2–5) is independent fan-out where each alert classifies → fixes → validates → commits on its own timeline, so it runs as a Workflow pipeline(). Phases 6–8 (push / verify / report) run inline after the pipeline returns, because push and verify need the full committed set at once.
| # |
Phase |
Outcome |
| 1 |
Discover (inline) |
gh api repos/{owner}/{repo}/dependabot/alerts?state=open. Group by package + relationship (direct / transitive). This is the pipeline work-list. |
| 2 |
Classify (pipeline) |
Each alert → one of: direct-fix (bump the catalog / package.json pin), override-fix (pnpm override for transitive), dismiss-with-reason. Resolve the PIN TARGET = highest soaked release sharing first_patched's major (see reference.md "Pin target"). |
| 3 |
Apply fix (pipeline) |
Direct: bump to the resolved exact pin. Transitive: add an EXACT pin to overrides: in pnpm-workspace.yaml (not package.json); pnpm install. Commit per alert. |
| 4 |
Validate (pipeline) |
pnpm run check --all (interactive) or pnpm run check --staged (CI). Roll back this alert's commit if its check fails; the failed item drops out of the pipeline. |
| 5 |
Push (inline) |
After the pipeline returns: per CLAUDE.md push policy, git push origin <branch>, fall back to PR on rejection. NEVER force-push. |
| 6 |
Verify resolution |
gh api .../dependabot/alerts should show each fixed alert as auto_dismissed or fixed. Log remaining. |
| 7 |
Report |
Per-alert table: alert # / pkg / severity / action taken / state. Roll the pipeline's per-item RESULT_SCHEMA rows into this table. |
The per-alert pipeline: author a Workflow
The skill invoking Workflow is a sanctioned opt-in. Pass the discovered alert list as args. Author the script inline (don't pre-Write it). Shape:
pipeline(alerts, classify, applyAndValidate)
classify stage — one agent() per alert returning CLASSIFY_SCHEMA: { alertNumber, package, relationship: direct|transitive, action: direct-fix|override-fix|dismiss-with-reason|awaiting-soak, pinTarget, crossesMajor, dismissReason? }. Resolve the pin deterministically with npm view <pkg> time --json > /tmp/<pkg>-time.json then node scripts/fleet/resolve-security-pin.mts --first-patched <ver> --versions-file /tmp/<pkg>-time.json — it returns { outcome: resolved|awaiting-soak|cross-major|no-candidate, pinTarget, reason } (highest STABLE release in first_patched's major that has cleared the 7-day soak; the semver work uses socket-lib's versions/* helpers, never hand-rolled). Map: resolved → pinTarget + crossesMajor:false; awaiting-soak → action:awaiting-soak; cross-major → crossesMajor:true + pinTarget — the cross-major candidate the benignity gate below judges; no-candidate → dismiss/escalate. The script does the math; the cross-major benignity call + dismissal reason stay your judgment.
applyAndValidate stage — receives the classification, applies the fix (direct-fix → bump pin; override-fix → pnpm-workspace.yaml overrides: + pnpm install; dismiss-with-reason → record the dismissal), commits chore(security): …, runs pnpm run check, and returns RESULT_SCHEMA: { alertNumber, package, severity, actionTaken, committed: boolean, state: fixed|awaiting-soak|dismissed|check-failed }. A check failure rolls back that commit and the stage throws, dropping the item to null (filter before reporting).
- Major-cross gate — when
crossesMajor is true, the applyAndValidate stage first spawns a benignity-check agent() (the socket-lib spawnAiAgent equivalent) returning { verdict: BENIGN|BREAKING|UNAVAILABLE, why }. BENIGN auto-applies with a Phase-7 notice; BREAKING/UNAVAILABLE skips the fix and flags the alert for AskUserQuestion signoff (interactive) or awaiting-review (non-interactive). Never auto-cross a major without a BENIGN verdict.
awaiting-soak alerts (patched version inside the 7-day window) return from classify with no fix stage work — the pipeline records them and moves on; the soak guard is never bypassed.
Hard requirements
- Clean tree on entry: same rule as
updating umbrella.
- One commit per alert:
chore(security): bump <pkg> to <ver> (GHSA-XXXX) or chore(security): override <pkg> to <ver> (GHSA-XXXX). <ver> is an exact version, never a ^/>=/~ range.
- Exact pins, highest-soaked-in-major: pin to the highest release sharing
first_patched_version's major that's past the 7-day soak — never a range, never an auto major-cross. Crossing a major requires an AI benignity check (socket-lib spawnAiAgent) that returns BENIGN (ESM-only / Node-floor / dropped deep-imports), and is then auto-applied with a notice in the Phase-8 report; a BREAKING or unavailable verdict requires AskUserQuestion signoff. See reference.md "Pin target".
- No
--no-verify: the soak / cooldown guard (minimum-release-age-guard) MUST be honored. If a patched version is inside the 7-day soak, the skill notes the alert as awaiting-soak and returns without modification.
- Conventional Commits:
chore(security): <action> (per CLAUDE.md Commits & PRs).
- Default-branch fallback: never hard-code
main (per CLAUDE.md Default branch fallback).
- GitHub auth: assumes
gh auth status returns OK. Token must have security_events:read + repo scopes. Personal gh login satisfies both.
Success criteria
- Every alert that has a
first_patched_version is either fixed,
awaiting-soak, or has an explicit dismissal request.
- Working tree clean after the commit chain.
pnpm run check passes against the fix set.
Safety: every commit is atomic and the skill can be interrupted at any phase. Resume by re-running. Already-applied fixes show up as auto_dismissed and are skipped.
Full bash, alert-shape reference, dismissal-reason taxonomy, and
recovery procedures in reference.md.
1---2name: updating-security3description: Resolve Dependabot alerts by bumping, overriding, patching, or dismissing with evidence.4---56# updating-security78Walk open Dependabot security alerts on the current repo and fix9them via the cheapest principled mechanism. Discovers the alert set10inline, then runs a `Workflow` that pipelines each alert through11classify → fix → validate → commit independently. Invoked directly12via `/update-security` or as Phase 5 of the `updating` umbrella.1314## When to use1516- A `gh dependabot alerts` listing shows open advisories.17- The GitHub web UI security tab is non-empty after a push (`gh`18 warns "Dependabot found N vulnerabilities" on push completion).19- As part of weekly maintenance (the `updating` umbrella invokes20 this automatically when alerts are present).2122## What it does NOT do2324- **Disable alerts at the repo level.** Suppressing the security25 tab via repo settings is a separate (heavier) decision; this26 skill resolves the underlying CVEs.27- **Touch `dependabot.yml`.** The fleet ships a no-op28 `dependabot.yml` (`open-pull-requests-limit: 0`) so Dependabot29 doesn't open version-update PRs; security alerts are independent30 and surface regardless.31- **Auto-dismiss without evidence.** Dismissals require a reason32 matching one of GitHub's documented values33 (`fix_started` / `inaccurate` / `no_bandwidth` / `not_used` /34 `tolerable_risk`) and a one-line justification. The skill asks35 before dismissing.3637## Phases3839Phase 1 (Discover) runs inline — one `gh api` call to build the work-list. The per-alert work (Phases 2–5) is independent fan-out where each alert classifies → fixes → validates → commits on its own timeline, so it runs as a **`Workflow`** `pipeline()`. Phases 6–8 (push / verify / report) run inline after the pipeline returns, because push and verify need the full committed set at once.4041| # | Phase | Outcome |42| --- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |43| 1 | Discover (inline) | `gh api repos/{owner}/{repo}/dependabot/alerts?state=open`. Group by package + relationship (direct / transitive). This is the pipeline work-list. |44| 2 | Classify (pipeline) | Each alert → one of: `direct-fix` (bump the catalog / `package.json` pin), `override-fix` (pnpm override for transitive), `dismiss-with-reason`. Resolve the PIN TARGET = highest soaked release sharing `first_patched`'s major (see reference.md "Pin target"). |45| 3 | Apply fix (pipeline) | Direct: bump to the resolved exact pin. Transitive: add an EXACT pin to `overrides:` in **pnpm-workspace.yaml** (not `package.json`); `pnpm install`. Commit per alert. |46| 4 | Validate (pipeline) | `pnpm run check --all` (interactive) or `pnpm run check --staged` (CI). Roll back this alert's commit if its check fails; the failed item drops out of the pipeline. |47| 5 | Push (inline) | After the pipeline returns: per CLAUDE.md push policy, `git push origin <branch>`, fall back to PR on rejection. NEVER force-push. |48| 6 | Verify resolution | `gh api .../dependabot/alerts` should show each fixed alert as `auto_dismissed` or `fixed`. Log remaining. |49| 7 | Report | Per-alert table: alert # / pkg / severity / action taken / state. Roll the pipeline's per-item `RESULT_SCHEMA` rows into this table. |5051### The per-alert pipeline: author a `Workflow`5253The skill invoking `Workflow` is a sanctioned opt-in. Pass the discovered alert list as `args`. Author the script inline (don't pre-`Write` it). Shape:5455```56pipeline(alerts, classify, applyAndValidate)57```58591. **`classify` stage** — one `agent()` per alert returning `CLASSIFY_SCHEMA`: `{ alertNumber, package, relationship: direct|transitive, action: direct-fix|override-fix|dismiss-with-reason|awaiting-soak, pinTarget, crossesMajor, dismissReason? }`. Resolve the pin deterministically with `npm view <pkg> time --json > /tmp/<pkg>-time.json` then `node scripts/fleet/resolve-security-pin.mts --first-patched <ver> --versions-file /tmp/<pkg>-time.json` — it returns `{ outcome: resolved|awaiting-soak|cross-major|no-candidate, pinTarget, reason }` (highest STABLE release in `first_patched`'s major that has cleared the 7-day soak; the semver work uses socket-lib's `versions/*` helpers, never hand-rolled). Map: `resolved` → `pinTarget` + `crossesMajor:false`; `awaiting-soak` → `action:awaiting-soak`; `cross-major` → `crossesMajor:true` + `pinTarget` — the cross-major candidate the benignity gate below judges; `no-candidate` → dismiss/escalate. The script does the math; the cross-major benignity call + dismissal reason stay your judgment.602. **`applyAndValidate` stage** — receives the classification, applies the fix (`direct-fix` → bump pin; `override-fix` → `pnpm-workspace.yaml` `overrides:` + `pnpm install`; `dismiss-with-reason` → record the dismissal), commits `chore(security): …`, runs `pnpm run check`, and returns `RESULT_SCHEMA`: `{ alertNumber, package, severity, actionTaken, committed: boolean, state: fixed|awaiting-soak|dismissed|check-failed }`. A check failure rolls back that commit and the stage throws, dropping the item to `null` (filter before reporting).613. **Major-cross gate** — when `crossesMajor` is true, the `applyAndValidate` stage first spawns a benignity-check `agent()` (the socket-lib `spawnAiAgent` equivalent) returning `{ verdict: BENIGN|BREAKING|UNAVAILABLE, why }`. `BENIGN` auto-applies with a Phase-7 notice; `BREAKING`/`UNAVAILABLE` skips the fix and flags the alert for `AskUserQuestion` signoff (interactive) or `awaiting-review` (non-interactive). Never auto-cross a major without a `BENIGN` verdict.6263`awaiting-soak` alerts (patched version inside the 7-day window) return from `classify` with no fix stage work — the pipeline records them and moves on; the soak guard is never bypassed.6465## Hard requirements6667- **Clean tree on entry**: same rule as `updating` umbrella.68- **One commit per alert**: `chore(security): bump <pkg> to <ver> (GHSA-XXXX)` or `chore(security): override <pkg> to <ver> (GHSA-XXXX)`. `<ver>` is an exact version, never a `^`/`>=`/`~` range.69- **Exact pins, highest-soaked-in-major**: pin to the highest release sharing `first_patched_version`'s major that's past the 7-day soak — never a range, never an auto major-cross. Crossing a major requires an AI benignity check (socket-lib `spawnAiAgent`) that returns BENIGN (ESM-only / Node-floor / dropped deep-imports), and is then auto-applied **with a notice in the Phase-8 report**; a BREAKING or unavailable verdict requires `AskUserQuestion` signoff. See reference.md "Pin target".70- **No `--no-verify`**: the soak / cooldown guard (`minimum-release-age-guard`) MUST be honored. If a patched version is inside the 7-day soak, the skill notes the alert as `awaiting-soak` and returns without modification.71- **Conventional Commits**: `chore(security): <action>` (per CLAUDE.md _Commits & PRs_).72- **Default-branch fallback**: never hard-code `main` (per CLAUDE.md _Default branch fallback_).73- **GitHub auth**: assumes `gh auth status` returns OK. Token must have `security_events:read` + `repo` scopes. Personal `gh` login satisfies both.7475## Success criteria7677- Every alert that has a `first_patched_version` is either fixed,78 awaiting-soak, or has an explicit dismissal request.79- Working tree clean after the commit chain.80- `pnpm run check` passes against the fix set.8182**Safety:** every commit is atomic and the skill can be interrupted at any phase. Resume by re-running. Already-applied fixes show up as `auto_dismissed` and are skipped.8384Full bash, alert-shape reference, dismissal-reason taxonomy, and85recovery procedures in [`reference.md`](reference.md).