From f1a1345f3bb71bdcf45016eeab1b363c13f186b2 Mon Sep 17 00:00:00 2001 From: Matthew McConnell Date: Mon, 19 Oct 2026 22:44:03 +0100 Subject: [PATCH] Skills: sec findings private only when they could yield a cert, CA key, login or session --- .claude/skills/audit-to-tickets/SKILL.md | 25 +++++++++++++----------- .claude/skills/implement-spec/SKILL.md | 7 ++++--- .claude/skills/implement/SKILL.md | 9 ++++++--- .claude/skills/project-audit/SKILL.md | 14 ++++++++----- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/.claude/skills/audit-to-tickets/SKILL.md b/.claude/skills/audit-to-tickets/SKILL.md index 8c00ccd..1df9d8b 100644 --- a/.claude/skills/audit-to-tickets/SKILL.md +++ b/.claude/skills/audit-to-tickets/SKILL.md @@ -4,24 +4,27 @@ description: > Files the findings from the latest `/project-audit` report as GitHub issues: a parent issue per audit with one sub-issue per ticket. Use when the user says "audit-to-tickets", "/audit-to-tickets", or "file the audit - findings". Security findings are never filed; they're written to local - ticket files in `.audit/` instead. + findings". Private security findings are never filed; they're written to + local ticket files in `.audit/` instead. disable-model-invocation: true --- Turn the latest `.audit/*.md` report into GitHub issues on this repo. -The repo is **public** and SECURITY.md says security bugs never go in public -issues. **Never file a `sec:` finding**, nor put its details in any issue. -They go to local files (step 8) for the user to file as security advisories. +The repo is **public**. A `sec:` finding with `Disclosure: private` +(knowing about it could get someone a cert, the CA key, a login or a +session) is **never filed**, nor its details put in any issue: it goes to a local file +(step 8) for the user to file as a security advisory. A `sec:` finding with +no `Disclosure:` line counts as private. `Disclosure: public` ones are filed +like any other finding. ## Steps 1. **Read** the newest `.audit/*.md`. Parse each `## . [] : ` - heading with its `Status:`, `Issue:` and `Where:` lines. + heading with its `Status:`, `Issue:`, `Disclosure:` and `Where:` lines. 2. **Select** findings with `Status:` `confirmed`, `spec` or `cut` and - `Issue: none`. Set aside every `sec:` finding. If the report has no - statuses yet, stop: the user hasn't finished the `/project-audit` grill. + `Issue: none`. Set aside every private `sec:` finding. If the report + has no statuses yet, stop: the user hasn't finished the `/project-audit` grill. 3. **Dedupe.** `gh issue list --state open --label audit --limit 200` and `gh issue list --state open --search "<path>"` per finding. A finding already covered by an open issue gets that number, not a new issue. @@ -54,9 +57,9 @@ They go to local files (step 8) for the user to file as security advisories. - Blocking: `gh api -X POST repos/{owner}/{repo}/issues/<n>/dependencies/blocked_by -F issue_id=<blocker database id>`; if that fails, keep the `Blocked by:` line in the body. - Get a database id with `gh api repos/{owner}/{repo}/issues/<n> --jq .id`. -8. **Sec tickets.** For each set-aside `sec:` finding, write the ticket body - below to `.audit/<report-ts>/<NN>-<slug>.md` (NN = finding number, slug = - kebab title). Never overwrite an existing file: the user may have edited it. +8. **Sec tickets.** For each set-aside private `sec:` finding, write the + ticket body below to `.audit/<report-ts>/<NN>-<slug>.md` (NN = finding + number, slug = kebab title). Never overwrite an existing file: the user may have edited it. 9. **Write back** each issue number, or sec file path, into the matching finding's `Issue:` line (`Issue: #<n>` / `Issue: .audit/<report-ts>/<NN>-<slug>.md`), so a re-run doesn't double-file. diff --git a/.claude/skills/implement-spec/SKILL.md b/.claude/skills/implement-spec/SKILL.md index 4ea5df9..0ab0321 100644 --- a/.claude/skills/implement-spec/SKILL.md +++ b/.claude/skills/implement-spec/SKILL.md @@ -61,7 +61,8 @@ the notes dir. Don't paste what they can read. - commits per logical change, no attribution lines; - merges the integration tip into its branch, runs `bun run verify`; - reports: branch, commits, verify result, `vuln: yes|no` (does the fix - reveal a vulnerability?), anything it couldn't do. + reveal a vulnerability where knowing about it could get someone a + cert, the CA key, a login or a session?), anything it couldn't do. 5. **Merge**, one at a time: a single merger sub-agent; reports that arrive while it runs wait in a queue. `vuln: yes` → don't merge; stop and ask the user (sec-ticket rules: nothing about it is pushed). Otherwise it merges @@ -101,5 +102,5 @@ skipped / risk: <what wasn't done or checked> | none ## Boundaries Doesn't merge the PR, close issues by hand, or edit the tickets. A ticket -whose fix turns out to be a vulnerability → stop and ask before anything -about it is pushed. +whose fix turns out to be a private vulnerability (`vuln: yes`) → stop and +ask before anything about it is pushed. diff --git a/.claude/skills/implement/SKILL.md b/.claude/skills/implement/SKILL.md index 1ea4cf8..872e157 100644 --- a/.claude/skills/implement/SKILL.md +++ b/.claude/skills/implement/SKILL.md @@ -27,7 +27,9 @@ a checklist item) → ask which. **Sec tickets never leave this machine** until the user says so: local branch, no push, no PR (SECURITY.md: the fix would disclose the bug before -release). Same for any issue whose fix turns out to be a vulnerability. +release). Same for any issue whose fix turns out to be a vulnerability +that should stay private: knowing about it could get someone a cert, the +CA key, a login or a session. ## Steps @@ -52,8 +54,9 @@ release). Same for any issue whose fix turns out to be a vulnerability. 6. **Verify.** `bun run verify` must pass. After 3 failed fix attempts, stop before pushing: `verify: fail`, and report what fails. 7. **Vuln gate.** If the review, tests or your own reading show the bug is - a vulnerability, switch to sec-ticket rules (local only) and ask the - user before anything is pushed. + a vulnerability that should stay private (see above), switch to + sec-ticket rules (local only) and ask the user before anything is + pushed. 8. **PR** (not for sec tickets): `git push -u origin HEAD`, then `gh pr create` with a body: what changed, how it was tested, and `Closes #N` when the input was an issue. Never push to `main`. A PR for diff --git a/.claude/skills/project-audit/SKILL.md b/.claude/skills/project-audit/SKILL.md index d92ba5d..058e79d 100644 --- a/.claude/skills/project-audit/SKILL.md +++ b/.claude/skills/project-audit/SKILL.md @@ -186,6 +186,7 @@ same header and footer, and per finding: Status: open Issue: none +Disclosure: private|public Where: path:line[, path:line…] **What this is:** <the code and what it's for, one or two sentences> @@ -195,8 +196,8 @@ Where: path:line[, path:line…] **Variants:** <other locations, or none> ``` -Keep the heading, `Status:` and `Issue:` lines exactly in this shape: -`/audit-to-tickets` parses them. +`Disclosure:` only on `sec:` findings. Keep the heading, `Status:`, `Issue:` +and `Disclosure:` lines exactly in this shape: `/audit-to-tickets` parses them. ## Grill @@ -204,7 +205,10 @@ After the report, grill the user on the findings. The decisions are the user's calls on findings: real bug or §3.3 accepted risk, severity, fix the code or update SPEC.md, cut it or keep it, which of -several fixes, whether a cut is worth the churn. +several fixes, whether a cut is worth the churn, and for each `sec:` finding +whether it's private (advisory) or public (issue). Recommend **private** when +knowing about it could get someone a cert, the CA key, a login or a +session; otherwise public. One question per finding, never a bundle of findings. Each question stands alone: its body restates the finding (what, where, the concrete impact) so @@ -237,8 +241,8 @@ Finding _facts_ is your job, never the user's. When a frontier question needs a The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding. -Then set every finding's `Status:` in the report file and re-emit the chat -list with each marked: +Then set every finding's `Status:` (and each `sec:` finding's `Disclosure:`) +in the report file and re-emit the chat list with each marked: - `confirmed`: real, fix it. - `accepted`: real, accepted risk (§3.3) or not worth fixing.