Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions .claude/skills/audit-to-tickets/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `## <N>. [<SEV>] <area>:<tag> <title>`
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.
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions .claude/skills/implement-spec/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
9 changes: 6 additions & 3 deletions .claude/skills/implement/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
14 changes: 9 additions & 5 deletions .claude/skills/project-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -195,16 +196,19 @@ 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

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
Expand Down Expand Up @@ -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.
Expand Down
Loading