diff --git a/.changeset/2119-secure-phase-single-writer.md b/.changeset/2119-secure-phase-single-writer.md new file mode 100644 index 000000000..172b29463 --- /dev/null +++ b/.changeset/2119-secure-phase-single-writer.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2154 +--- +**`/gsd-secure-phase` now has a single SECURITY.md writer** — the `gsd-security-auditor` subagent previously held `Write`/`Edit` tools and was instructed to "write SECURITY.md" with no padded `-` prefix and no template frontmatter, while the orchestrator's Step 6 also wrote the phase-scoped `-SECURITY.md` from `templates/SECURITY.md`. The auditor is now return-only (drops `Write`/`Edit`, returns a structured verdict with `threats_open`); the orchestrator is the sole file writer. The workflow's Step 5 spawn constraints explicitly forbid the auditor from writing SECURITY.md. (#2119) diff --git a/agents/gsd-security-auditor.md b/agents/gsd-security-auditor.md index 234573fa0..1ceae604d 100644 --- a/agents/gsd-security-auditor.md +++ b/agents/gsd-security-auditor.md @@ -1,10 +1,8 @@ --- name: gsd-security-auditor -description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Produces SECURITY.md. Spawned by /gsd:secure-phase. +description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Returns structured security verdict (SECURED / OPEN_THREATS / ESCALATE). Spawned by /gsd:secure-phase. tools: - Read - - Write - - Edit - Bash - Glob - Grep @@ -15,11 +13,11 @@ color: red An implemented phase has been submitted for security audit. Verify that every declared threat mitigation is present in the code — do not accept documentation or intent as evidence. -Does NOT scan blindly for new vulnerabilities. Verifies each threat in `` by its declared disposition (mitigate / accept / transfer). Reports gaps. Writes SECURITY.md. +Does NOT scan blindly for new vulnerabilities. Verifies each threat in `` by its declared disposition (mitigate / accept / transfer). Reports gaps. Returns a structured verdict — the orchestrator owns the SECURITY.md file write (#2119: single-writer contract). **Mandatory Initial Read:** If prompt contains ``, load ALL listed files before any action. -**Implementation files are READ-ONLY.** Only create/modify: SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation. +**Implementation files are READ-ONLY.** The auditor does NOT write any files — it returns a structured verdict (SECURED / OPEN_THREATS / ESCALATE). The orchestrator persists SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation. @@ -79,20 +77,20 @@ Classify each threat before verification. Record classification for every threat - L3: deep trace — follow the data flow end-to-end, check edge cases and ordering, confirm no bypass path exists. - + 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 `accept` threats: check existing SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`. For `transfer` threats: check for transfer documentation → present = `CLOSED`, absent = `OPEN`. -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). +For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in the structured return (not a blocker). **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`. +Open threats BELOW the block threshold are recorded in the return 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. +Return the structured result (SECURED / OPEN_THREATS / ESCALATE) with `threats_open` set to the severity-filtered count. The orchestrator writes SECURITY.md from this data — the auditor does NOT write any files (#2119). @@ -116,7 +114,7 @@ Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return str ### Unregistered Flags {none / list from SUMMARY.md ## Threat Flags with no threat mapping} -SECURITY.md: {path} +**threats_open:** {count} ``` ## OPEN_THREATS @@ -145,9 +143,9 @@ SECURITY.md: {path} *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. +Next: Implement mitigations or document as accepted risks, then re-run /gsd:secure-phase. -SECURITY.md: {path} +**threats_open:** {count} ``` ## ESCALATE @@ -172,6 +170,6 @@ SECURITY.md: {path} - [ ] Each threat verified by disposition type (mitigate / accept / transfer) - [ ] Threat flags from SUMMARY.md `## Threat Flags` incorporated - [ ] Implementation files never modified -- [ ] SECURITY.md written to correct path -- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE +- [ ] No files written — structured verdict returned only (orchestrator writes SECURITY.md) +- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE with `threats_open` count diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 62f12c955..7040a6a42 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -479,10 +479,10 @@ Communication style, decision patterns, debugging approach, UX preferences, vend |----------|-------| | **Spawned by** | `/gsd-secure-phase` | | **Parallelism** | Single instance | -| **Tools** | Read, Write, Edit, Bash, Glob, Grep | +| **Tools** | Read, Bash, Glob, Grep | | **Model (balanced)** | Sonnet | | **Color** | Red | -| **Produces** | `{phase}-SECURITY.md` | +| **Produces** | Structured verdict (SECURED / OPEN_THREATS / ESCALATE) — orchestrator writes `{phase}-SECURITY.md` (#2119) | **Key behaviors:** - Verifies each threat by its declared disposition (mitigate / accept / transfer) diff --git a/gsd-core/workflows/secure-phase.md b/gsd-core/workflows/secure-phase.md index f3831818d..266e35e1c 100644 --- a/gsd-core/workflows/secure-phase.md +++ b/gsd-core/workflows/secure-phase.md @@ -108,7 +108,7 @@ Agent( "{PLAN, SUMMARY, impl files, SECURITY.md}" + "{threat register}" + "asvs_level: {SECURITY_ASVS}, block_on: {SECURITY_BLOCK_ON}" + - "Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps." + + "Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps. Return a structured verdict only — do NOT write SECURITY.md (the orchestrator owns the file write)." + "${AGENT_SKILLS_AUDITOR}", subagent_type="gsd-security-auditor", model="{AUDITOR_MODEL}", diff --git a/tests/agent-size-baseline.json b/tests/agent-size-baseline.json index 3d68c1af2..5044c3856 100644 --- a/tests/agent-size-baseline.json +++ b/tests/agent-size-baseline.json @@ -27,7 +27,7 @@ "gsd-project-researcher.md": 22242, "gsd-research-synthesizer.md": 13847, "gsd-roadmapper.md": 22273, - "gsd-security-auditor.md": 8981, + "gsd-security-auditor.md": 9431, "gsd-ui-auditor.md": 17249, "gsd-ui-checker.md": 14118, "gsd-ui-researcher.md": 19557, diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 29f737cda..ee69a5669 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "85de7f562872ee9b", "agents/gsd-research-synthesizer.md": "18a2e1b30ff7ae3a", "agents/gsd-roadmapper.md": "7a8465ac6d4dd29e", - "agents/gsd-security-auditor.md": "2ce13af25179dd10", + "agents/gsd-security-auditor.md": "7730a026680cb821", "agents/gsd-ui-auditor.md": "f777f9c7bf62788c", "agents/gsd-ui-checker.md": "216af34b01e277aa", "agents/gsd-ui-researcher.md": "5f86de1decbd16d2", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "98e2cf8908e73a52", "gsd-core/workflows/review.md": "43c052bba1cbd4ac", "gsd-core/workflows/scan.md": "a7fecd67e5cd655f", - "gsd-core/workflows/secure-phase.md": "96b199dfac00e60f", + "gsd-core/workflows/secure-phase.md": "52ddc46233e8fa66", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "6d25100a9de15b31", "gsd-core/workflows/settings-integrations.md": "4d3001ad2b5dad8f", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index b0b96d3f7..61d2c8ad0 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "4531b7cc8f5e5f7d", "agents/gsd-research-synthesizer.md": "4a4f68e6c75b133a", "agents/gsd-roadmapper.md": "bb2f57695dbab32c", - "agents/gsd-security-auditor.md": "f22374cc1db28dca", + "agents/gsd-security-auditor.md": "4db2c41181ad1f97", "agents/gsd-ui-auditor.md": "dcd5712e6b160a53", "agents/gsd-ui-checker.md": "5c27ec0d88ef87c2", "agents/gsd-ui-researcher.md": "bcc591f2dfebdb60", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index 97dce43f5..f660e3b97 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -28,7 +28,7 @@ "agents/gsd-project-researcher.md": "d7f355894519f9fe", "agents/gsd-research-synthesizer.md": "1c738df9932d325a", "agents/gsd-roadmapper.md": "453e9471ad27c7ea", - "agents/gsd-security-auditor.md": "45bd98918cd3a004", + "agents/gsd-security-auditor.md": "4551e5f621cf13ff", "agents/gsd-ui-auditor.md": "a0b09cc8e4645956", "agents/gsd-ui-checker.md": "33ffdc73d2105a24", "agents/gsd-ui-researcher.md": "4b36852c839f1134", @@ -357,7 +357,7 @@ "gsd-core/workflows/resume-project.md": "af9761bcec0f6fe9", "gsd-core/workflows/review.md": "eec3a15bebb7fcf0", "gsd-core/workflows/scan.md": "75c670d08cee8680", - "gsd-core/workflows/secure-phase.md": "64ec4d06ca85720a", + "gsd-core/workflows/secure-phase.md": "8030d2b2a5bfdf07", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "94d61da368e9f85b", "gsd-core/workflows/settings-integrations.md": "dfe3672c4fabf139", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 7b8d9c988..7e0d512eb 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -28,7 +28,7 @@ "agents/gsd-project-researcher.md": "f468e96f8339d1e0", "agents/gsd-research-synthesizer.md": "7be02e47f4fd901b", "agents/gsd-roadmapper.md": "8a7f1f1256a6aed5", - "agents/gsd-security-auditor.md": "4f9fc3f654af4944", + "agents/gsd-security-auditor.md": "757f72894f79f11b", "agents/gsd-ui-auditor.md": "40c0dcc15bcfb9fb", "agents/gsd-ui-checker.md": "8126405043f99cb6", "agents/gsd-ui-researcher.md": "9e3ac030767167e0", @@ -286,7 +286,7 @@ "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", "gsd-core/workflows/review.md": "b0baf1dafebe3821", "gsd-core/workflows/scan.md": "47371c2073d6c0be", - "gsd-core/workflows/secure-phase.md": "59d3c50aba8c9a6c", + "gsd-core/workflows/secure-phase.md": "d6ac1f4db6a5da75", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "339def28c34b0797", "gsd-core/workflows/settings-integrations.md": "53649313d20694ae", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index cd803c896..598d068aa 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -32,7 +32,7 @@ "agents/gsd-project-researcher.md": "049f816c6caa4316", "agents/gsd-research-synthesizer.md": "2f7dcbff50371d4c", "agents/gsd-roadmapper.md": "bbb23d3097911516", - "agents/gsd-security-auditor.md": "c35c8ec2f85b331f", + "agents/gsd-security-auditor.md": "38935b2d0c532c29", "agents/gsd-ui-auditor.md": "9338efa31b9b7b99", "agents/gsd-ui-checker.md": "151d12b4e007cbbf", "agents/gsd-ui-researcher.md": "1bb303f3a3c4dfc9", @@ -290,7 +290,7 @@ "gsd-core/workflows/resume-project.md": "e23981178fa37b3d", "gsd-core/workflows/review.md": "6c689f4ff8146d28", "gsd-core/workflows/scan.md": "dfd92717caea0ce7", - "gsd-core/workflows/secure-phase.md": "cf78183f06a02582", + "gsd-core/workflows/secure-phase.md": "00de56d6d993bb2c", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "69f3a19bf2c61160", "gsd-core/workflows/settings-integrations.md": "76eee76d6eb57657", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index b2dec1387..aa369e9c0 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "e43c59f7f1f2f37a", "agents/gsd-research-synthesizer.md": "87955470c3c129b2", "agents/gsd-roadmapper.md": "20b69eff61a7a9fa", - "agents/gsd-security-auditor.md": "d3b8f44034a76c9d", + "agents/gsd-security-auditor.md": "bcd9c4859eb5b448", "agents/gsd-ui-auditor.md": "a26bbc733817959b", "agents/gsd-ui-checker.md": "25822359044cd708", "agents/gsd-ui-researcher.md": "e37d9f53ade25d1f", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 847fe6493..36c21e16f 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -128,8 +128,8 @@ "agents/gsd-research-synthesizer.toml": "053616d4941a1458", "agents/gsd-roadmapper.md": "e15d82d7781dbff3", "agents/gsd-roadmapper.toml": "0b17f618eafe9be8", - "agents/gsd-security-auditor.md": "a4008a6b1d01833b", - "agents/gsd-security-auditor.toml": "42f529475bd28c22", + "agents/gsd-security-auditor.md": "d7b62f9a93cfbf78", + "agents/gsd-security-auditor.toml": "e991aa6e7c70f813", "agents/gsd-ui-auditor.md": "abbf560bc8d5069c", "agents/gsd-ui-auditor.toml": "5e9dd62a12a16a1e", "agents/gsd-ui-checker.md": "b65d350a4e0564e9", @@ -140,7 +140,7 @@ "agents/gsd-user-profiler.toml": "b9c244bb8fbf8140", "agents/gsd-verifier.md": "4ac4b860e2504374", "agents/gsd-verifier.toml": "8ed9fb961409e894", - "config.toml": "fa48d84b92174890", + "config.toml": "b5f627b42f060910", "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", @@ -393,7 +393,7 @@ "gsd-core/workflows/resume-project.md": "9965f87eb278f7f8", "gsd-core/workflows/review.md": "5faef3f4feb45c99", "gsd-core/workflows/scan.md": "1a3caa5d724d39e9", - "gsd-core/workflows/secure-phase.md": "db91810d16964b1e", + "gsd-core/workflows/secure-phase.md": "ab387a4bca381c18", "gsd-core/workflows/session-report.md": "dd8fa011c9394075", "gsd-core/workflows/settings-advanced.md": "2431433811616f76", "gsd-core/workflows/settings-integrations.md": "77730321d3d6d317", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index a18cc11ee..27c773c07 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.agent.md": "d73bdbe986ffa8a6", "agents/gsd-research-synthesizer.agent.md": "f03eed4aa89e47c5", "agents/gsd-roadmapper.agent.md": "322048cf8ddcb4e5", - "agents/gsd-security-auditor.agent.md": "6f6a88b35dc2a24b", + "agents/gsd-security-auditor.agent.md": "464cfbe9aafcd5af", "agents/gsd-ui-auditor.agent.md": "92f50549e84ef482", "agents/gsd-ui-checker.agent.md": "47d8cf3486009e11", "agents/gsd-ui-researcher.agent.md": "0746daeb54f83008", @@ -288,7 +288,7 @@ "gsd-core/workflows/resume-project.md": "40db7f350f5866d8", "gsd-core/workflows/review.md": "4b649f31865a1785", "gsd-core/workflows/scan.md": "dcc2f76d0850e2fb", - "gsd-core/workflows/secure-phase.md": "d87bd706f85bcad6", + "gsd-core/workflows/secure-phase.md": "9bec6635ee1cbaed", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "230a658de9c017a6", "gsd-core/workflows/settings-integrations.md": "a1d146d6bfd14db5", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index a3f0a49f0..870503ea7 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "beeac940d3a10e76", "agents/gsd-research-synthesizer.md": "6315f016d55176f4", "agents/gsd-roadmapper.md": "d28e7d4bac46dde2", - "agents/gsd-security-auditor.md": "5ff44ee432387d93", + "agents/gsd-security-auditor.md": "1e6a10833f556e4c", "agents/gsd-ui-auditor.md": "d824acc3b2a18c53", "agents/gsd-ui-checker.md": "c6c24e8066470830", "agents/gsd-ui-researcher.md": "0f5be5e55501f7e8", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", "gsd-core/workflows/review.md": "3ba8bb85c8ede5b8", "gsd-core/workflows/scan.md": "47371c2073d6c0be", - "gsd-core/workflows/secure-phase.md": "c55975672c4e1895", + "gsd-core/workflows/secure-phase.md": "3063b0b6f7b56d46", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "bf8ea69c8f7ae019", "gsd-core/workflows/settings-integrations.md": "166ca51b1f33c2a3", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 85281cc08..5eb2530ec 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "425a7df7f37a5c06", "agents/gsd-research-synthesizer.md": "9d31c87fc2c87ffa", "agents/gsd-roadmapper.md": "64dce5d5f9fa5654", - "agents/gsd-security-auditor.md": "e4d35ada4ea67d7f", + "agents/gsd-security-auditor.md": "ed330ecbd9dbc377", "agents/gsd-ui-auditor.md": "86797e85f718dfac", "agents/gsd-ui-checker.md": "cfc8a3bac0a0ef5b", "agents/gsd-ui-researcher.md": "8799b6013e06ae49", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "a0443839f1f83c2d", "gsd-core/workflows/review.md": "761dd1ae5be40613", "gsd-core/workflows/scan.md": "b28f65d88c522767", - "gsd-core/workflows/secure-phase.md": "f2957d4b88fb3746", + "gsd-core/workflows/secure-phase.md": "a503dc469fd7a252", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "49be159144d7f426", "gsd-core/workflows/settings-integrations.md": "1dce76db0aca08a5", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 3b5f4959c..c274dadf3 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "60573a38d3dfd9fe", "agents/gsd-research-synthesizer.md": "1f7cd286c5783c86", "agents/gsd-roadmapper.md": "277e0a3252553ab7", - "agents/gsd-security-auditor.md": "0113435969e869c4", + "agents/gsd-security-auditor.md": "30b1f87cdfc05de1", "agents/gsd-ui-auditor.md": "9b988b95d28e56ed", "agents/gsd-ui-checker.md": "e078ea5a07313976", "agents/gsd-ui-researcher.md": "cc9578c4f686d926", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", "gsd-core/workflows/review.md": "f8109b9ec1f56962", "gsd-core/workflows/scan.md": "47371c2073d6c0be", - "gsd-core/workflows/secure-phase.md": "e8855104c1e0417c", + "gsd-core/workflows/secure-phase.md": "4977cf9e0462745b", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "edd858cd6cfddaf1", "gsd-core/workflows/settings-integrations.md": "3ea8095d5fad891b", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 427d2c300..cd7ac3baa 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -30,7 +30,7 @@ ".kimi/hooks/managed-hooks-registry.cjs": "08ec2585a3f8f132", ".kimi/package.json": "dbf8353f77358bc1", "agents/gsd.md": "60fee7782ae4f2c6", - "agents/gsd.yaml": "253a23ddda06c6c2", + "agents/gsd.yaml": "b5f16c9fcf92cbff", "agents/subagents/gsd-advisor-researcher.md": "81bdc6cbd8fcde40", "agents/subagents/gsd-advisor-researcher.yaml": "662ced4837207406", "agents/subagents/gsd-ai-researcher.md": "f4cd2f17d2c3e35f", @@ -87,8 +87,8 @@ "agents/subagents/gsd-research-synthesizer.yaml": "898104ab3bb0b81a", "agents/subagents/gsd-roadmapper.md": "62359d6876022b48", "agents/subagents/gsd-roadmapper.yaml": "679772cb14f3a015", - "agents/subagents/gsd-security-auditor.md": "e621d1ee6b7aee6c", - "agents/subagents/gsd-security-auditor.yaml": "fe8a4345cc13571d", + "agents/subagents/gsd-security-auditor.md": "c6108af63b5f481d", + "agents/subagents/gsd-security-auditor.yaml": "924783f1da6777b4", "agents/subagents/gsd-ui-auditor.md": "e4a319070959ebbc", "agents/subagents/gsd-ui-auditor.yaml": "3fc98c1d9e8f10fd", "agents/subagents/gsd-ui-checker.md": "07cd4e382ca55994", @@ -351,7 +351,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 75bec1848..0e3348de8 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "dae210ae0b3c6e2b", "agents/gsd-research-synthesizer.md": "e02c6ad5d1b74171", "agents/gsd-roadmapper.md": "1658a40b20d8b575", - "agents/gsd-security-auditor.md": "e36e436c0d5c26d5", + "agents/gsd-security-auditor.md": "64aa435793af52ec", "agents/gsd-ui-auditor.md": "e810012e685b2466", "agents/gsd-ui-checker.md": "4f88fd4c9d4c56a3", "agents/gsd-ui-researcher.md": "ecb617901cb7ad06", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "ad9f06a10bab8cc0", "gsd-core/workflows/review.md": "2d28a6683ff587de", "gsd-core/workflows/scan.md": "ad8ebcad4626d4a8", - "gsd-core/workflows/secure-phase.md": "e9a488cec3b4efdc", + "gsd-core/workflows/secure-phase.md": "71e6e689e80288ec", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "252b0d3edc315339", "gsd-core/workflows/settings-integrations.md": "d1711a95f44fdbde", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index 438874363..15e4a4e56 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -254,7 +254,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index c549ba993..256b73ba0 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "b5baac64a15c85e2", "agents/gsd-research-synthesizer.md": "6cd9b501dc97bd50", "agents/gsd-roadmapper.md": "c357a77ab919e9e5", - "agents/gsd-security-auditor.md": "d6d8f82501f10b92", + "agents/gsd-security-auditor.md": "9e3bc9a62036352c", "agents/gsd-ui-auditor.md": "47937e784c9ae541", "agents/gsd-ui-checker.md": "7d708c53a106f748", "agents/gsd-ui-researcher.md": "e3768304c1c77753", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "7f20769f302e5427", "gsd-core/workflows/review.md": "bcbc20cb8df021cc", "gsd-core/workflows/scan.md": "949692db4834dd27", - "gsd-core/workflows/secure-phase.md": "6758f1acf4113e9e", + "gsd-core/workflows/secure-phase.md": "ef7b5ad194b687bf", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "5e05212fb5cba531", "gsd-core/workflows/settings-integrations.md": "29c5de27fbbb18e9", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 14b49c301..4a5148a3f 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "ddf7794e81300032", "agents/gsd-research-synthesizer.md": "a124b00271748d07", "agents/gsd-roadmapper.md": "493ef92b42b12cf4", - "agents/gsd-security-auditor.md": "fbd3798eec23651b", + "agents/gsd-security-auditor.md": "1e2eea5b2ab16d6e", "agents/gsd-ui-auditor.md": "771ae08260534d5c", "agents/gsd-ui-checker.md": "7ecd910efa6eb00e", "agents/gsd-ui-researcher.md": "9e1a84a55a4cac99", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "f242e4c8aba18ea2", "gsd-core/workflows/review.md": "835cf8c9594f17c1", "gsd-core/workflows/scan.md": "63631467651d9ca8", - "gsd-core/workflows/secure-phase.md": "6cc236e53c2e7d56", + "gsd-core/workflows/secure-phase.md": "4a647aec1e4d2dfe", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "39e66386f6c48025", "gsd-core/workflows/settings-integrations.md": "f8f756709ec02363", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 6a8fe3225..625774ee5 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "f6697b316b5995ba", "agents/gsd-research-synthesizer.md": "04036f38c1d373ea", "agents/gsd-roadmapper.md": "fb62e1e3de84b5f9", - "agents/gsd-security-auditor.md": "13b660e2d336ed8e", + "agents/gsd-security-auditor.md": "cacf711cb835300b", "agents/gsd-ui-auditor.md": "20cc99872e9b998b", "agents/gsd-ui-checker.md": "56698909c270b130", "agents/gsd-ui-researcher.md": "c348aa3ff8412ecb", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "82cfe1b8cb17c085", "gsd-core/workflows/review.md": "3e72508a5dccd45a", "gsd-core/workflows/scan.md": "12c11b2edc165df9", - "gsd-core/workflows/secure-phase.md": "7bf923689bf58288", + "gsd-core/workflows/secure-phase.md": "185a15d389951e6e", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "2f86ec7b998f9485", "gsd-core/workflows/settings-integrations.md": "b082fc518b484c07", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 5d1665ac8..fe60606c9 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "f572892f138734ff", "agents/gsd-research-synthesizer.md": "29949bf3f049a8f1", "agents/gsd-roadmapper.md": "840ac933e3b094f9", - "agents/gsd-security-auditor.md": "b7202c44366697dd", + "agents/gsd-security-auditor.md": "4b86fa11ebda981e", "agents/gsd-ui-auditor.md": "76f446c50edf81f8", "agents/gsd-ui-checker.md": "15949ccab982b71c", "agents/gsd-ui-researcher.md": "0e8ec8509d476904", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", diff --git a/tests/secure-phase-single-writer.test.cjs b/tests/secure-phase-single-writer.test.cjs new file mode 100644 index 000000000..0122c2598 --- /dev/null +++ b/tests/secure-phase-single-writer.test.cjs @@ -0,0 +1,73 @@ +/** + * Regression test for #2119: /gsd-secure-phase dual SECURITY.md writers. + * + * The gsd-security-auditor agent must NOT have Write/Edit tools — the + * orchestrator (secure-phase.md Step 6) is the sole SECURITY.md writer. + * The auditor returns a structured verdict; it never writes files. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const AGENT_PATH = path.join(__dirname, '..', 'agents', 'gsd-security-auditor.md'); + +function parseYamlTools(content) { + const lines = content.split(/\r?\n/); + let inFrontmatter = false; + let inTools = false; + const tools = []; + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed === '---') { + inFrontmatter = !inFrontmatter; + if (!inFrontmatter) break; + continue; + } + if (!inFrontmatter) continue; + if (trimmed.startsWith('tools:')) { + inTools = true; + continue; + } + if (inTools) { + if (trimmed.startsWith('- ')) { + tools.push(trimmed.slice(2).trim()); + } else if (trimmed && !trimmed.startsWith('#')) { + inTools = false; + } + } + } + return tools; +} + +describe('#2119 — security auditor is return-only (no file writes)', () => { + const content = fs.readFileSync(AGENT_PATH, 'utf-8'); + + test('auditor tools do not include Write or Edit', () => { + const tools = parseYamlTools(content); + assert.ok(tools.length > 0, 'tools list should be non-empty'); + assert.ok( + !tools.includes('Write'), + `Write must not be in auditor tools (got: ${tools.join(', ')}) — orchestrator is the sole SECURITY.md writer (#2119)`, + ); + assert.ok( + !tools.includes('Edit'), + `Edit must not be in auditor tools (got: ${tools.join(', ')}) — orchestrator is the sole SECURITY.md writer (#2119)`, + ); + }); + + test('auditor description does not claim to produce SECURITY.md', () => { + const lines = content.split(/\r?\n/); + const descLine = lines.find((l) => l.startsWith('description:')); + assert.ok(descLine, 'description field must exist'); + assert.ok( + !descLine.includes('Produces SECURITY.md'), + 'description must not claim to produce SECURITY.md — auditor returns a verdict, orchestrator writes (#2119)', + ); + assert.ok( + descLine.includes('Returns structured') || descLine.includes('returns'), + 'description should state the auditor returns a structured verdict', + ); + }); +}); diff --git a/tests/secure-phase.test.cjs b/tests/secure-phase.test.cjs index b676e0795..526f249d2 100644 --- a/tests/secure-phase.test.cjs +++ b/tests/secure-phase.test.cjs @@ -57,15 +57,18 @@ describe('SECURE: gsd-security-auditor agent', () => { ); }); - test('tools include Read, Write, Bash, Glob, Grep', () => { + test('tools include Read, Bash, Glob, Grep but NOT Write or Edit (#2119)', () => { const content = fs.readFileSync(agentPath, 'utf-8'); - const requiredTools = ['Read', 'Write', 'Bash', 'Glob', 'Grep']; + const requiredTools = ['Read', 'Bash', 'Glob', 'Grep']; for (const tool of requiredTools) { assert.ok( content.includes(`- ${tool}`), `tools must include ${tool}` ); } + // #2119: auditor is return-only — orchestrator is the sole SECURITY.md writer + assert.ok(!content.includes('- Write'), 'tools must NOT include Write (#2119)'); + assert.ok(!content.includes('- Edit'), 'tools must NOT include Edit (#2119)'); }); test('has section', () => { diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 6a4cd4e5b..b91272241 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -66,7 +66,7 @@ "resume-project.md": 17270, "review.md": 47168, "scan.md": 7732, - "secure-phase.md": 13520, + "secure-phase.md": 13622, "session-report.md": 4044, "settings-advanced.md": 40019, "settings-integrations.md": 15892,