fix(#1627): scale security rigor by ASVS level (planner disposition + auditor depth) (#1636)

workflow.security_asvs_level was display-only — the planner hardcoded
'mitigate if ASVS L1 requires it' and the auditor only echoed the level,
so L2/L3 behaved identically to L1.

- New reference gsd-core/references/security-asvs-levels.md defines L1
  (opportunistic), L2 (standard), L3 (comprehensive) for both planner
  threat disposition and auditor verification depth (higher = superset).
- planner: disposition now scales with the configured ASVS level (no
  hardcoded L1) + @-pointer to the reference.
- auditor: verification depth scales with asvs_level (L1 grep-presence,
  L2 boundary/vector check, L3 end-to-end trace + bypass check).
- planning-config.md + INVENTORY updated; planner kept under its 48K cap
  by extracting the goal-backward worked example to planner-guidance.md.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-23 18:48:58 -04:00
committed by GitHub
parent 94be6d5b60
commit f9d9dfb4bc
12 changed files with 349 additions and 59 deletions

View File

@@ -459,7 +459,7 @@ Only include what Claude literally cannot do.
**Step 0: Extract Requirement IDs**
Read ROADMAP.md `**Requirements:**` line for this phase. Strip brackets if present (e.g., `[AUTH-01, AUTH-02]` → `AUTH-01, AUTH-02`). Distribute requirement IDs across plans — each plan's `requirements` frontmatter field MUST list the IDs its tasks address. **CRITICAL:** Every requirement ID MUST appear in at least one plan. Plans with an empty `requirements` field are invalid.
**Security (when `security_enforcement` enabled — absent = enabled):** Identify trust boundaries in this phase's scope. Map STRIDE categories to applicable tech stack from RESEARCH.md security domain. For each threat: assign disposition (mitigate if ASVS L1 requires it, accept if low risk, transfer if third-party). Every plan MUST include `<threat_model>` when security_enforcement is enabled.
**Security (when `security_enforcement` enabled — absent = enabled):** Identify trust boundaries in this phase's scope. Map STRIDE categories to applicable tech stack from RESEARCH.md security domain. For each threat: assign disposition (`mitigate`/`accept`/`transfer`) per the configured OWASP ASVS level — see @~/.claude/gsd-core/references/security-asvs-levels.md. Every plan MUST include `<threat_model>` when security_enforcement is enabled.
**Package legitimacy gate (npm/pip/cargo only):**
- Require RESEARCH.md `## Package Legitimacy Audit` before package-manager install tasks.
@@ -477,66 +477,16 @@ Take phase goal from ROADMAP.md. Must be outcome-shaped, not task-shaped.
**Step 2: Derive Observable Truths**
"What must be TRUE for this goal to be achieved?" List 3-7 truths from USER's perspective.
For "working chat interface":
- User can see existing messages
- User can type a new message
- User can send the message
- Sent message appears in the list
- Messages persist across page refresh
**Test:** Each truth verifiable by a human using the application.
**Step 3: Derive Required Artifacts**
For each truth: "What must EXIST for this to be true?"
"User can see existing messages" requires:
- Message list component (renders Message[])
- Messages state (loaded from somewhere)
- API route or data source (provides messages)
- Message type definition (shapes the data)
**Test:** Each artifact = a specific file or database object.
**Step 4: Derive Required Wiring**
For each artifact: "What must be CONNECTED for this to function?"
Message list component wiring:
- Imports Message type (not using `any`)
- Receives messages prop or fetches from API
- Maps over messages to render (not hardcoded)
- Handles empty state (not just crashes)
**Step 5: Identify Key Links**
"Where is this most likely to break?" Key links = critical connections where breakage causes cascading failures.
## Must-Haves Output Format
```yaml
must_haves:
truths:
- "User can see existing messages"
- "User can send a message"
- "Messages persist across refresh"
artifacts:
- path: "src/components/Chat.tsx"
provides: "Message list rendering"
min_lines: 30
- path: "src/app/api/chat/route.ts"
provides: "Message CRUD operations"
exports: ["GET", "POST"]
- path: "prisma/schema.prisma"
provides: "Message model"
contains: "model Message"
key_links:
- from: "src/components/Chat.tsx"
to: "src/app/api/chat/route.ts"
via: "fetch in useEffect — calls /api/chat endpoint"
pattern: "fetch.*api/chat"
- from: "src/app/api/chat/route.ts"
to: "prisma/schema.prisma"
via: "database query via prisma.message"
pattern: "prisma\\.message\\.(find|create)"
```
See @~/.claude/gsd-core/references/planner-guidance.md for a worked example and the `must_haves` YAML format.
</goal_backward>

View File

@@ -69,10 +69,15 @@ For each threat in `<threat_model>`, determine verification method by dispositio
| `transfer` | Verify transfer documentation present (insurance, vendor SLA, etc.) |
Classify each threat before verification. Record classification for every threat — no threat skipped.
**Verification depth scales with `asvs_level`** (see @~/.claude/gsd-core/references/security-asvs-levels.md for full definitions):
- L1: verify mitigation is PRESENT in the cited file (grep-level — pattern exists).
- L2: verify the mitigation ADDRESSES the threat vector and is placed at the correct boundary (a check in the wrong layer does not close the threat).
- L3: deep trace — follow the data flow end-to-end, check edge cases and ordering, confirm no bypass path exists.
</step>
<step name="verify_and_write">
For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`.
For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`. Apply depth per `asvs_level` (see analyze_threats step).
For `accept` threats: check SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`.
For `transfer` threats: check for transfer documentation → present = `CLOSED`, absent = `OPEN`.