workflow.security_block_on was documented as the minimum threat severity that blocks advancement, but threats carried no severity and the auditor's threats_open count (the SECURITY.md gate field) counted every open threat regardless of severity — so the threshold had no effect, and the auditor's block_on vocabulary (open/unregistered/none) did not even match the config enum (critical/high/medium/low/none). - planner: add a Severity column to the STRIDE threat register; assign severity per threat. - auditor: read severity; reconcile the <config> block_on domain to the severity enum; redefine threats_open as the count of OPEN threats whose severity is at or above block_on (none => 0). Below-threshold opens are reported as non-blocking and excluded from threats_open. - SECURITY.md template + planning-config.md reconciled. No gate-check site changed: threats_open == 0 stays the gate everywhere; only its computation is now severity-filtered. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -380,11 +380,11 @@ Output: [Artifacts created]
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-{phase}-01 | {S/T/R/I/D/E} | {function/endpoint/file} | mitigate | {specific: e.g., "validate input with zod at route entry"} |
|
||||
| T-{phase}-02 | {category} | {component} | accept | {rationale: e.g., "no PII, low-value target"} |
|
||||
| T-{phase}-SC | Tampering | npm/pip/cargo installs | mitigate | slopcheck + blocking human checkpoint for [ASSUMED]/[SUS] |
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-{phase}-01 | {S/T/R/I/D/E} | {function/endpoint/file} | {critical\|high\|medium\|low} | mitigate | {specific mitigation action} |
|
||||
| T-{phase}-02 | {category} | {component} | low | accept | {rationale for acceptance} |
|
||||
| T-{phase}-SC | Tampering | npm/pip/cargo installs | high | mitigate | slopcheck + blocking human checkpoint for [ASSUMED]/[SUS] |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
@@ -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`/`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.
|
||||
**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 a **severity** (critical|high|medium|low) based on impact × likelihood, and a 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.
|
||||
@@ -987,6 +987,7 @@ Phase planning complete when:
|
||||
- [ ] User knows next steps and wave structure
|
||||
- [ ] `<threat_model>` present with STRIDE register (when `security_enforcement` enabled)
|
||||
- [ ] Every threat has a disposition (mitigate / accept / transfer)
|
||||
- [ ] Every threat has a Severity (critical|high|medium|low)
|
||||
- [ ] Mitigations reference specific implementation (not generic advice)
|
||||
|
||||
## Gap Closure Mode
|
||||
|
||||
@@ -33,18 +33,19 @@ Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_
|
||||
- Marking CLOSED based on code structure ("looks like it validates input") without finding the actual validation call
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — `OPEN_THREATS`: a declared mitigation is absent in implemented code; phase must not ship
|
||||
- **BLOCKER** — `OPEN_THREATS`: a declared mitigation is absent in implemented code AND the threat's severity ≥ `block_on` threshold; phase must not ship until resolved
|
||||
- **OPEN — non-blocking** — mitigation absent BUT the threat's severity is below the `block_on` threshold; tracked in SECURITY.md, does NOT count toward `threats_open`, does not block ship
|
||||
- **WARNING** — `unregistered_flag`: new attack surface appeared during implementation with no threat mapping
|
||||
Every threat must resolve to CLOSED, OPEN (BLOCKER), or documented accepted risk.
|
||||
Every threat must resolve to CLOSED, OPEN-blocking (severity ≥ block_on), OPEN-non-blocking (severity below block_on), or documented accepted risk.
|
||||
</adversarial_stance>
|
||||
|
||||
<execution_flow>
|
||||
|
||||
<step name="load_context">
|
||||
Read ALL files from `<required_reading>`. Extract:
|
||||
- PLAN.md `<threat_model>` block: full threat register with IDs, categories, dispositions, mitigation plans
|
||||
- PLAN.md `<threat_model>` block: full threat register with IDs, categories, severities, dispositions, mitigation plans
|
||||
- SUMMARY.md `## Threat Flags` section: new attack surface detected by executor during implementation
|
||||
- `<config>` block: `asvs_level` (1/2/3), `block_on` (open / unregistered / none)
|
||||
- `<config>` block: `asvs_level` (1/2/3), `block_on` (critical | high | medium | low | none) — severity ordering: critical > high > medium > low; none = never block
|
||||
- Implementation files: exports, auth patterns, input handling, data flows
|
||||
|
||||
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
|
||||
@@ -60,7 +61,7 @@ This ensures project-specific patterns, conventions, and best practices are appl
|
||||
</step>
|
||||
|
||||
<step name="analyze_threats">
|
||||
For each threat in `<threat_model>`, determine verification method by disposition:
|
||||
For each threat in `<threat_model>`, read its `severity` field (critical|high|medium|low). If building the register retroactively (no `<threat_model>` in PLAN.md), assign a severity to each threat you construct based on impact × likelihood. Determine verification method by disposition:
|
||||
|
||||
| Disposition | Verification Method |
|
||||
|-------------|---------------------|
|
||||
@@ -83,7 +84,13 @@ For `transfer` threats: check for transfer documentation → present = `CLOSED`,
|
||||
|
||||
For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in SECURITY.md (not a blocker).
|
||||
|
||||
Write SECURITY.md. Set `threats_open` count. Return structured result.
|
||||
**Severity-aware `threats_open` computation (severity order: critical > high > medium > low):**
|
||||
`threats_open` (the SECURITY.md frontmatter gate field) = the count of threats whose status is OPEN AND whose severity rank ≥ the `block_on` rank. `block_on: none` ⇒ 0 (nothing ever blocks). `block_on: low` ⇒ all open threats block. `block_on: high` (default) ⇒ only high and critical open threats block.
|
||||
Open threats BELOW the block threshold are recorded in SECURITY.md as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`.
|
||||
|
||||
**Fail-closed for missing severity:** if an OPEN threat has no severity or an unparseable severity (e.g. a legacy register predating the Severity column), treat it as `critical` for this computation — it COUNTS toward `threats_open` (blocking). Never silently drop an unranked open threat.
|
||||
|
||||
Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return structured result.
|
||||
</step>
|
||||
|
||||
</execution_flow>
|
||||
@@ -100,9 +107,9 @@ Write SECURITY.md. Set `threats_open` count. Return structured result.
|
||||
**ASVS Level:** {1/2/3}
|
||||
|
||||
### Threat Verification
|
||||
| Threat ID | Category | Disposition | Evidence |
|
||||
|-----------|----------|-------------|----------|
|
||||
| {id} | {category} | {mitigate/accept/transfer} | {file:line or doc reference} |
|
||||
| Threat ID | Category | Severity | Disposition | Evidence |
|
||||
|-----------|----------|----------|-------------|----------|
|
||||
| {id} | {category} | {critical\|high\|medium\|low} | {mitigate/accept/transfer} | {file:line or doc reference} |
|
||||
|
||||
### Unregistered Flags
|
||||
{none / list from SUMMARY.md ## Threat Flags with no threat mapping}
|
||||
@@ -120,14 +127,21 @@ SECURITY.md: {path}
|
||||
**ASVS Level:** {1/2/3}
|
||||
|
||||
### Closed
|
||||
| Threat ID | Category | Disposition | Evidence |
|
||||
|-----------|----------|-------------|----------|
|
||||
| {id} | {category} | {disposition} | {evidence} |
|
||||
| Threat ID | Category | Severity | Disposition | Evidence |
|
||||
|-----------|----------|----------|-------------|----------|
|
||||
| {id} | {category} | {critical\|high\|medium\|low} | {disposition} | {evidence} |
|
||||
|
||||
### Open
|
||||
| Threat ID | Category | Mitigation Expected | Files Searched |
|
||||
|-----------|----------|---------------------|----------------|
|
||||
| {id} | {category} | {pattern not found} | {file paths} |
|
||||
### Open (blocking — severity ≥ block_on threshold)
|
||||
| Threat ID | Category | Severity | Mitigation Expected | Files Searched |
|
||||
|-----------|----------|----------|---------------------|----------------|
|
||||
| {id} | {category} | {critical\|high\|medium\|low} | {pattern not found} | {file paths} |
|
||||
|
||||
### Open (non-blocking — severity below block_on threshold)
|
||||
| Threat ID | Category | Severity | Mitigation Expected | Files Searched |
|
||||
|-----------|----------|----------|---------------------|----------------|
|
||||
| {id} | {category} | {critical\|high\|medium\|low} | {pattern not found} | {file paths} |
|
||||
|
||||
*Only blocking-open threats count toward `threats_open` in SECURITY.md frontmatter.*
|
||||
|
||||
Next: Implement mitigations or document as accepted in SECURITY.md accepted risks log, then re-run /gsd:secure-phase.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user