@hegemonart/get-design-done 1.33.0 → 1.33.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +25 -0
- package/package.json +3 -1
- package/reference/gdd-runtime-audit.md +111 -0
- package/reference/gdd-threat-model.md +336 -0
- package/reference/registry.json +14 -0
- package/scripts/lib/peer-cli/acp-client.cjs +9 -1
- package/scripts/lib/peer-cli/asp-client.cjs +10 -1
- package/scripts/lib/peer-cli/sanitize-env.cjs +198 -0
- package/scripts/lib/redact.cjs +20 -1
- package/scripts/lib/transports/ws.cjs +67 -3
- package/sdk/mcp/gdd-state/schemas/add_blocker.schema.json +2 -0
- package/sdk/mcp/gdd-state/schemas/add_decision.schema.json +1 -0
- package/sdk/mcp/gdd-state/schemas/add_must_have.schema.json +1 -0
- package/sdk/mcp/gdd-state/schemas/checkpoint.schema.json +1 -0
- package/sdk/mcp/gdd-state/schemas/frontmatter_update.schema.json +1 -1
- package/sdk/mcp/gdd-state/schemas/get.schema.json +2 -1
- package/sdk/mcp/gdd-state/schemas/probe_connections.schema.json +2 -0
- package/sdk/mcp/gdd-state/schemas/resolve_blocker.schema.json +1 -0
- package/sdk/mcp/gdd-state/server.js +137 -48
- package/sdk/mcp/gdd-state/tools/add_blocker.ts +2 -0
- package/sdk/mcp/gdd-state/tools/add_decision.ts +2 -0
- package/sdk/mcp/gdd-state/tools/add_must_have.ts +2 -0
- package/sdk/mcp/gdd-state/tools/checkpoint.ts +2 -0
- package/sdk/mcp/gdd-state/tools/frontmatter_update.ts +2 -0
- package/sdk/mcp/gdd-state/tools/get.ts +2 -0
- package/sdk/mcp/gdd-state/tools/probe_connections.ts +2 -0
- package/sdk/mcp/gdd-state/tools/resolve_blocker.ts +2 -0
- package/sdk/mcp/gdd-state/tools/set_status.ts +2 -0
- package/sdk/mcp/gdd-state/tools/shared.ts +117 -7
- package/sdk/mcp/gdd-state/tools/transition_stage.ts +2 -0
- package/sdk/mcp/gdd-state/tools/update_progress.ts +2 -0
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Get Design Done — 5-stage agent-orchestrated design pipeline with 9 connections, handoff-first workflow, bidirectional Figma write-back, 22+ specialized agents, queryable knowledge layer (intel store, dependency analysis, learnings extraction), and a self-improvement loop (reflector, frontmatter + budget feedback, global-skills layer). v1.20.0 ships the SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream, and resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) for rate-limit + 429 + context-overflow recovery. Full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation (auto-tag + GitHub Release + release-time smoke test).",
|
|
8
|
-
"version": "1.33.
|
|
8
|
+
"version": "1.33.5"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "get-design-done",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), Claude Design handoff, bidirectional Figma write-back, and a queryable intel store (.design/intel/) for dependency and learnings queries. Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation. Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain.",
|
|
15
|
-
"version": "1.33.
|
|
15
|
+
"version": "1.33.5",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "hegemonart"
|
|
18
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get-design-done",
|
|
3
3
|
"short_name": "gdd",
|
|
4
|
-
"version": "1.33.
|
|
4
|
+
"version": "1.33.5",
|
|
5
5
|
"description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), handoff-first workflow via Claude Design bundles, bidirectional Figma write-back (annotations, Code Connect), queryable intel store (`.design/intel/`) for O(1) design surface lookups, and self-improvement loop (reflector agent, frontmatter + budget feedback, global-skills layer at `~/.claude/gdd/global-skills/`). Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings, reflect, apply-reflections. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows, lint + schema + frontmatter + stale-ref + shellcheck + gitleaks + injection-scan + blocking size-budget) and release automation (auto-tag + GitHub Release + release-time smoke test). Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain. v1.27.7 ships gdd-mcp (Phase 27.7): 12 read-only MCP tools for sub-3s priming. v1.28.0 (Phase 28): Foundational References Tier 2 — 5 new reference files (color-theory, composition, proportion-systems, i18n, contrast-advanced), 2 verifier i18n probes + 1 explore i18n-readiness probe, 12 additive cross-link insertions across 10 existing references, 2 orthogonal audit-scoring lens-tags (composition_alignment + i18n_readiness).",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "hegemonart",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to get-design-done are documented here. Versions follow [sem
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [1.33.5] - 2026-05-31
|
|
8
|
+
|
|
9
|
+
### Phase 33.5 — GDD Runtime Security Hardening
|
|
10
|
+
|
|
11
|
+
Audits and hardens GDD's **own** runtime attack surface — the multi-MCP-server, peer-CLI-spawning, WebSocket-transport-emitting SDK that grew across Phases 20–27 without a formalized security model. Phase 14.5 ships a safety floor for *user code being audited*; this phase is the equivalent for GDD's *own* runtime. Ships a STRIDE threat model + static runtime audit, an outbound-network CI gate, WebSocket bind hardening, gdd-state MCP input validation, peer-CLI env sandboxing, a secret-scan extension + fuzz, a published disclosure policy, and a regression baseline. A decimal release on the v1.33.x arc (CHANGELOG-only, D-01); no new runtime dependency (built-ins only, D-12). 6 plans across Waves A–C.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **STRIDE threat model + runtime audit.** `reference/gdd-threat-model.md` models the five in-scope runtime components (hooks, the `gdd-state` MCP server, the peer-CLI broker, the WebSocket transport, the issue-reporter outbound path) with a per-component Assets / Entry-points / STRIDE / Mitigations / Residual treatment and a residual-risk → closing-plan map; `reference/gdd-runtime-audit.md` records the static audit that backs it. Both are registry-tracked.
|
|
16
|
+
- **Outbound-network CI gate.** `scripts/scan-outbound-network.cjs` + `npm run scan:outbound`, wired into the CI `security` job, statically gates **active egress** (require/import of `node:http(s)`/`ws`/`node-fetch`/`axios`/`undici`, `fetch(`, `http(s).get/.request`, `new WebSocket(Server)`, and child-process spawn of `gh`/`curl`/`wget`/`nc`/`ssh`/`scp`) under `hooks/ scripts/ sdk/ bin/`, failing on any active-egress site not under an allowlisted glob in `scripts/security/outbound-allowlist.json` (D-06). Comment-only matches and bare-URL literals are skipped (a URL cannot exfiltrate without a call). This forces Phase 33.6's OpenRouter REST client to explicitly allowlist its new egress.
|
|
17
|
+
- **WebSocket localhost-default bind + timing-safe token.** `scripts/lib/transports/ws.cjs` now defaults its bind host to **127.0.0.1** (was the implicit `0.0.0.0` all-interfaces bind); a remote bind is opt-in via `.design/config.json` `event_stream.bind_host` or the `GDD_WS_BIND_HOST` env override, and `scripts/scan-ws-bind.cjs` gates that the default config would never bind `0.0.0.0`. The Bearer-token compare is upgraded to `crypto.timingSafeEqual` (was `!==`, timing-unsafe); the existing ≥8-char-token rule is retained (D-04).
|
|
18
|
+
- **gdd-state MCP input validation.** `sdk/mcp/gdd-state/tools/shared.ts` gains a `resolveStatePath()` path-traversal guard (rejects `..`-escape / absolute-outside / best-effort symlink-escape on the `GDD_STATE_PATH` override) and an `assertInputWithinLimits()` payload cap (64 KiB input / 8192-char string / depth-32 JSON-bomb guard) wired into all 11 tool handlers; the 11 tool input schemas are tightened with `maxLength`/`maxItems` (and already carried `additionalProperties:false`) (D-08).
|
|
19
|
+
- **Peer-CLI env sandbox (allowlist-forward / default-deny).** A shared `scripts/lib/peer-cli/sanitize-env.cjs` helper builds the spawned child's environment from an OS-essential baseline **plus** an explicit allowlist read from `.design/config.json` `peer_cli.env_allowlist`, applied to both the acp and asp clients. GDD's `ANTHROPIC_API_KEY` / `GH_TOKEN` / `GDD_*` and anything secret-shaped are never forwarded to a spawned peer unless explicitly allowlisted (D-03).
|
|
20
|
+
- **Secret-scan extension + fuzz.** `scripts/lib/redact.cjs` adds three modern token formats — Gemini/GCP `AIza…`, GitHub fine-grained `github_pat_…`, and GitHub server/oauth/user/refresh `gh[sour]_…` — bringing the redaction set to **11 patterns**, with a synthetic-secret fuzz test asserting zero leak per provider format (D-07). The existing PEM/JWT/anthropic/stripe/slack/`ghp_`/AWS/`sk-` patterns are retained.
|
|
21
|
+
- **`SECURITY.md` disclosure policy.** A repo-root `SECURITY.md` documents the supported-versions stance and routes vulnerability reports through **GitHub private security advisories** (the repo Security tab → "Report a vulnerability"); it publishes no email / no PII (D-02) and notes that enabling private vulnerability reporting is a one-line repo setting the maintainer must toggle (D-11).
|
|
22
|
+
- **Regression baseline.** `test/fixtures/baselines/phase-33-5/` freezes the hardened surface — a STRIDE-checklist snapshot, a hardening-surface invariant manifest (ws default host, redact pattern count, sanitize-env module + allowlist key, 11 gdd-state schemas, allowlist/threat-model/audit paths), and a synthetic secret-fuzz corpus — pinned by `test/suite/phase-33-5-baseline.test.cjs` so a future change cannot silently undo a hardened surface.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
|
|
26
|
+
- **WebSocket event-stream now binds `127.0.0.1` by default (was `0.0.0.0`).** Opt into a remote bind via `.design/config.json` `event_stream.bind_host` or the `GDD_WS_BIND_HOST` env override. This is **not marked BREAKING** — the event stream is a token-gated observability surface (Bearer auth ships on every upgrade), and the safe-by-default localhost bind only restricts an unintended off-box exposure; the opt-in escape hatch preserves the remote-bind workflow for anyone who relied on it.
|
|
27
|
+
- All Phase 33.5 tests are hermetic (no network, no live peer; the WS test binds ephemeral localhost; the outbound + bind gates are static scans), so the default `npm test` stays green (D-10).
|
|
28
|
+
- 6-manifest lockstep at **v1.33.5** (`package.json` + `package-lock.json` (root + `packages.""`) + `.claude-plugin/plugin.json` + `.claude-plugin/marketplace.json` (metadata.version + plugins[0].version) + `.cursor-plugin/plugin.json` + `.codex-plugin/plugin.json`). Version-sync hygiene done upfront (D-09): `OFF_CADENCE_VERSIONS.add('1.33.5')` + the 13 live-pinned `manifests-version.txt` baselines forward-propagated 1.33.0 → 1.33.5.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
7
32
|
## [1.33.0] - 2026-05-30
|
|
8
33
|
|
|
9
34
|
### Phase 33 — Skill Behavior Tests (Pressure-Scenario Harness)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hegemonart/get-design-done",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.5",
|
|
4
4
|
"description": "A design-quality pipeline for AI coding agents: brief, plan, implement, and verify UI work against your design system.",
|
|
5
5
|
"author": "Hegemon",
|
|
6
6
|
"homepage": "https://github.com/hegemonart/get-design-done",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"validate:frontmatter": "node --experimental-strip-types scripts/validate-frontmatter.ts agents/",
|
|
61
61
|
"detect:stale-refs": "node scripts/detect-stale-refs.cjs",
|
|
62
62
|
"scan:injection": "node scripts/run-injection-scanner-ci.cjs",
|
|
63
|
+
"scan:outbound": "node scripts/scan-outbound-network.cjs",
|
|
64
|
+
"scan:ws-bind": "node scripts/scan-ws-bind.cjs",
|
|
63
65
|
"test:size-budget": "node --test test/suite/agent-size-budget.test.cjs",
|
|
64
66
|
"release:extract-changelog": "node scripts/extract-changelog-section.cjs",
|
|
65
67
|
"verify:version-sync": "node scripts/verify-version-sync.cjs",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# GDD Runtime Static Security Audit
|
|
2
|
+
|
|
3
|
+
**Phase:** 33.5 — GDD Runtime Security Hardening · **Plan:** 33.5-02 (Wave A) · **Date:** 2026-05-31
|
|
4
|
+
**Reference:** branch `phase/33-5-runtime-security`
|
|
5
|
+
**Scope:** the SHIPPED runtime surface — `hooks/`, `scripts/`, `sdk/`, `bin/`.
|
|
6
|
+
**Method:** static enumeration (read-only source inspection, no execution, no network).
|
|
7
|
+
**Companions:**
|
|
8
|
+
|
|
9
|
+
- `scripts/security/outbound-allowlist.json` — the machine-readable, CANONICAL active-egress
|
|
10
|
+
allowlist that Phase 33.5-04's `scripts/scan-outbound-network.cjs` gate `JSON.parse`s (this
|
|
11
|
+
report is its human-readable rationale).
|
|
12
|
+
- `reference/gdd-threat-model.md` — the STRIDE threat model (Phase 33.5-01).
|
|
13
|
+
|
|
14
|
+
This audit freezes the egress / secret / external-input picture so any *future* surface that is
|
|
15
|
+
not pre-approved trips the 33.5-04 gate at CI time. It is grounded in the CONTEXT real-tree sweep
|
|
16
|
+
("no raw unexpected egress found"), re-verified against HEAD by reading each cited file.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Outbound-network call sites
|
|
21
|
+
|
|
22
|
+
GDD's runtime makes outbound calls (or local IPC/server binds) from exactly the sites below. Each
|
|
23
|
+
maps to an `outbound-allowlist.json` directory glob (see the **Allowlisted** column). The 33.5-04
|
|
24
|
+
gate is **ACTIVE-egress-only** — it matches `require`/`import` of `node:http(s)`/`http(s)`/`ws`/
|
|
25
|
+
`node-fetch`/`axios`/`undici`, `fetch(`, `http(s).get/.request(`, `new WebSocket`/`new
|
|
26
|
+
WebSocketServer`/`WebSocketServer(`, and child-process `spawn`/`spawnSync`/`exec*` of
|
|
27
|
+
`gh|curl|wget|nc|ssh|scp` — so a bare URL string alone never trips it.
|
|
28
|
+
|
|
29
|
+
| File | What it calls | Transport | Legitimacy | Allowlisted by |
|
|
30
|
+
| --- | --- | --- | --- | --- |
|
|
31
|
+
| `scripts/lib/figma-extract/pull.cjs` | `fetch` → `https://api.figma.com/v1` (read-only) | REST (global `fetch`, injectable) | User-initiated Figma extract; token from `FIGMA_TOKEN`/`FIGMA_PERSONAL_ACCESS_TOKEN`, never persisted/logged | `scripts/lib/figma-extract/**` |
|
|
32
|
+
| `scripts/lib/figma-extract/styles-resolver.cjs` | Figma REST styles lookup (shares `pull.cjs` fetch path) | REST | Same extract flow; read-only | `scripts/lib/figma-extract/**` |
|
|
33
|
+
| `scripts/lib/figma-extract/receiver.cjs` | `require('node:http')` → `http.createServer` + `server.listen(RECEIVER_PORT, RECEIVER_HOST)` | local HTTP server | **EPHEMERAL** handshake server on `127.0.0.1`; lives for one extract run then exits (timeout-armed) | `scripts/lib/figma-extract/**` |
|
|
34
|
+
| `scripts/lib/transports/ws.cjs` | `require('node:http')` + `new WebSocketServer({ noServer })` + `httpServer.listen(opts.port)` | WebSocket over HTTP upgrade | Event-stream transport; **hardened in 33.5-03** (127.0.0.1 default bind, Bearer token ≥8 chars → timing-safe compare) | `scripts/lib/transports/ws.cjs` |
|
|
35
|
+
| `scripts/lib/issue-reporter/gh-submit.cjs` | `spawn('gh', …)` / `spawnSync` | `gh` CLI spawn | Rides `gh`'s own logged-in auth; **no raw HTTP**; frozen destination + kill-switch | `scripts/lib/issue-reporter/**` |
|
|
36
|
+
| `scripts/lib/issue-reporter/dedup.cjs` | `spawn('gh', …)` (3 call sites) | `gh` CLI spawn | Duplicate-issue lookup before submit; same frozen destination | `scripts/lib/issue-reporter/**` |
|
|
37
|
+
| `scripts/lib/issue-reporter/gh-absent-fallback.cjs` | `spawnSync('gh', ['gh'], {stdio:'ignore'})` (presence probe) | `gh` CLI spawn | Detects whether `gh` is installed; no payload | `scripts/lib/issue-reporter/**` |
|
|
38
|
+
| `scripts/lib/peer-cli/acp-client.cjs` | `spawn(command, args, …)` (no shell) | child-process / stdio IPC | Spawns a LOCAL peer binary over stdio (JSON-RPC); **IPC, not network**; env sandboxed (33.5-04) | `scripts/lib/peer-cli/**` |
|
|
39
|
+
| `scripts/lib/peer-cli/asp-client.cjs` | `spawn(...)` (no shell) | child-process / stdio IPC | Local peer over stdio; same env-allowlist sandbox | `scripts/lib/peer-cli/**` |
|
|
40
|
+
| `scripts/e2e/run-headless.ts` | live Anthropic API run | REST (key-gated) | Test infrastructure only; gated on `ANTHROPIC_API_KEY` + main-branch; never in default `npm test` | `scripts/e2e/**` |
|
|
41
|
+
| `scripts/lib/authority-watcher/index.cjs` | authority-feed classification of already-fetched records | (delegated) | The live article fetch is delegated to `agents/design-authority-watcher.md`; `index.cjs` is the pure-CommonJS classifier. Allowlisted so any direct feed fetch added here stays pre-approved | `scripts/lib/authority-watcher/**` |
|
|
42
|
+
|
|
43
|
+
**Documented but NOT hard-gated** (the scanner scope is `.js`-family + active-egress only —
|
|
44
|
+
these cannot exfiltrate on their own and are covered by gitleaks + the threat model):
|
|
45
|
+
|
|
46
|
+
| File / surface | What it is | Why not gated |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `scripts/lib/easings.cjs` (line 6) | React-Native `Easing.js` spec link in a comment | bare URL string, no call |
|
|
49
|
+
| `scripts/lib/spring.cjs` (line 6) | React-Native `SpringConfig.js` spec link in a comment | bare URL string, no call |
|
|
50
|
+
| `scripts/lib/install/merge.cjs` (line 85) | "Plugin repository: https://github.com/hegemonart/get-design-done" printed string | bare URL string, no call |
|
|
51
|
+
| `scripts/lib/issue-reporter/destination.cjs` (lines 29–30) | frozen `DESTINATION_URL` / `ISSUE_TEMPLATE_URL` constants | constants consumed only via the `gh` spawn above |
|
|
52
|
+
| `scripts/lint-agentskills-spec.cjs` (line 76) | spec-example URL in a comment | bare URL string, no call |
|
|
53
|
+
| `hooks/update-check.sh` | shell update-check egress | shell script — outside the `.{js,cjs,mjs,ts}` scanner scope |
|
|
54
|
+
| `scripts/bootstrap.sh` | shell bootstrap egress | shell script — outside the `.{js,cjs,mjs,ts}` scanner scope |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Secret-handling sites
|
|
59
|
+
|
|
60
|
+
Where a token/key/credential is read, forwarded, or scrubbed across the shipped runtime.
|
|
61
|
+
|
|
62
|
+
| File | Secret touched | Action | Risk note |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| `scripts/lib/redact.cjs` | PEM, JWT, Anthropic `sk-ant-`, Stripe `sk_live_`, Slack `xox*`, GitHub `ghp_`, AWS `AKIA`, generic `sk-` (8 patterns) | **scrub** | Deep-walks event-stream payloads at serialize time so everything that hits disk / a bus subscriber is `[REDACTED:<type>]`. **D-07 extends** with Gemini `AIza…`, GitHub fine-grained `github_pat_`, and GitHub `ghs_/gho_/ghu_/ghr_` (currently uncovered). |
|
|
65
|
+
| `scripts/lib/peer-cli/acp-client.cjs` (line 102) | full `process.env` | **forward** (default) | `const env = opts.env … : process.env` → GDD's `ANTHROPIC_API_KEY`/`GH_TOKEN`/`GDD_*` leak to a spawned peer when `opts.env` is absent. **Fixed by 33.5-04** (allowlist-forward, default-deny, shared `sanitize-env`). |
|
|
66
|
+
| `scripts/lib/peer-cli/asp-client.cjs` (line 122) | full `process.env` | **forward** (default) | Same default-inherit gap; same 33.5-04 fix. |
|
|
67
|
+
| `scripts/lib/figma-extract/pull.cjs` | `FIGMA_TOKEN` / `FIGMA_PERSONAL_ACCESS_TOKEN` | **read** | Lives only inside caller-provided `headers`; NEVER written to disk, NEVER logged (diagnostics read a short body prefix, never the token — D-10 of the figma sub-plan). |
|
|
68
|
+
| `scripts/lib/issue-reporter/gh-submit.cjs` / `dedup.cjs` | none held by GDD | **delegate** | Authentication is `gh`'s own logged-in credential store; GDD never reads or forwards a GitHub token for these spawns. |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## External-input surfaces
|
|
73
|
+
|
|
74
|
+
Where untrusted data crosses a trust boundary into GDD's runtime.
|
|
75
|
+
|
|
76
|
+
| Surface | Untrusted source | Boundary | Risk note |
|
|
77
|
+
| --- | --- | --- | --- |
|
|
78
|
+
| WebSocket upgrade request | a remote client connecting to the event-stream WS | `scripts/lib/transports/ws.cjs` HTTP `upgrade` handler | Bearer-token gate (≥8 chars) already ships; **33.5-03** adds 127.0.0.1-default bind + timing-safe compare so the default config does not expose `0.0.0.0`. |
|
|
79
|
+
| gdd-state MCP tool inputs | the MCP client / model | `sdk/mcp/gdd-state/tools/*.ts` (11 tools) + `tools/shared.ts` | Each tool has a JSON schema under `sdk/mcp/gdd-state/schemas/`; **33.5-08** tightens them (`additionalProperties:false` + `maxLength`) and adds a payload-size cap (JSON-bomb guard). |
|
|
80
|
+
| `GDD_STATE_PATH` env override | the launching environment | `sdk/mcp/gdd-state/tools/shared.ts:resolveStatePath()` (line 60–61) | `process.env['GDD_STATE_PATH'] ?? .design/STATE.md` with **no path-traversal guard** today — `..`/absolute-outside escape is unchecked. **Closed by 33.5-08** (resolve + assert within project root / `.design/`). |
|
|
81
|
+
| `.design/config.json` | a repo-local config file (potentially attacker-influenced in a malicious clone) | 14 modules incl. `scripts/lib/peer-cli/registry.cjs` (line 154) | Drives `peer_cli.enabled_peers` / (33.5) `peer_cli.env_allowlist`, the WS `event_stream.bind_host`, and the issue-reporter kill-switch. Parsed defensively; opt-in by design (a peer must be explicitly enabled). |
|
|
82
|
+
| peer child `stdout` | a spawned LOCAL peer CLI | `scripts/lib/peer-cli/acp-client.cjs` (JSON-RPC frame parser) | A malicious/buggy peer could flood stdout — **already capped at 16 MiB un-newlined** (DoS guard). JSON-RPC frames are parsed, not `eval`'d. |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Finding
|
|
87
|
+
|
|
88
|
+
The legitimate active-egress set is fully enumerated above and **no raw unexpected egress was
|
|
89
|
+
found** — every outbound call, server bind, or child spawn maps to one of six trusted modules,
|
|
90
|
+
each frozen into `scripts/security/outbound-allowlist.json` with a justification. The allowlist
|
|
91
|
+
uses **directory globs** so a new helper in an already-trusted module (e.g. another
|
|
92
|
+
`figma-extract` file) does not silently trip the gate, and every glob is asserted by
|
|
93
|
+
`test/suite/phase-33-5-audit.test.cjs` to resolve to ≥1 real file — so the 33.5-04 gate cannot be
|
|
94
|
+
defeated by a stale entry that matches nothing.
|
|
95
|
+
|
|
96
|
+
The residual gaps surfaced here are **closed by the remaining Phase 33.5 plans**:
|
|
97
|
+
|
|
98
|
+
- **33.5-03** — WebSocket bind hardening: 127.0.0.1 default bind (no more `0.0.0.0`), opt-in remote
|
|
99
|
+
via `event_stream.bind_host`, and a timing-safe (`crypto.timingSafeEqual`) token compare. Closes
|
|
100
|
+
the WS-upgrade external-input row.
|
|
101
|
+
- **33.5-04** — Outbound-network static CI gate: `scripts/scan-outbound-network.cjs` +
|
|
102
|
+
`npm run scan:outbound`, consuming THIS plan's `outbound-allowlist.json`. Also lands the
|
|
103
|
+
peer-CLI env-allowlist sandbox (shared `sanitize-env`) that closes the `acp-client.cjs` /
|
|
104
|
+
`asp-client.cjs` full-`process.env` forward rows.
|
|
105
|
+
- **33.5-05** (and 33.5-07/08) — secret-scan extension (Gemini + GitHub fine-grained/server
|
|
106
|
+
tokens) with a synthetic-secret fuzz, the gdd-state path-traversal guard + payload cap +
|
|
107
|
+
tightened schemas, `SECURITY.md`, and the regression baseline.
|
|
108
|
+
|
|
109
|
+
This report + the canonical allowlist satisfy **SEC-02** and unblock ROADMAP **SC#5** (the
|
|
110
|
+
outbound-network gate), as amended by **D-05** (corrected paths `reference/` + `test/suite/`) and
|
|
111
|
+
**D-06** (the gate mirrors the injection-scanner: a data file + a scanner that loads it).
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# GDD Runtime Threat Model (STRIDE)
|
|
2
|
+
|
|
3
|
+
> Phase 33.5 · SEC-01 · STRIDE pass over GDD's **own** runtime attack surface.
|
|
4
|
+
> Generated against branch `phase/33-5-runtime-security`, HEAD `5374bed`.
|
|
5
|
+
|
|
6
|
+
## Scope
|
|
7
|
+
|
|
8
|
+
This document models the security posture of **GDD's own runtime** — the
|
|
9
|
+
multi-MCP-server, peer-CLI-spawning, WebSocket-emitting SDK that grew across
|
|
10
|
+
Phases 20–27 without a formalized security model. It does **NOT** model the
|
|
11
|
+
user code that GDD audits; the safety floor for *audited user code* is Phase
|
|
12
|
+
14.5's concern. This is the equivalent threat model for GDD's *own* moving
|
|
13
|
+
parts: the hooks that run on every session start, the two MCP servers that
|
|
14
|
+
read and mutate `STATE.md`, the broker that spawns peer CLIs, the WebSocket
|
|
15
|
+
transport that streams the event bus, and the issue-reporter that reaches the
|
|
16
|
+
network through `gh`.
|
|
17
|
+
|
|
18
|
+
**STRIDE** is the Microsoft threat taxonomy used throughout: **S**poofing
|
|
19
|
+
(pretending to be someone/something you are not), **T**ampering (unauthorized
|
|
20
|
+
modification of data or code), **R**epudiation (denying an action with no
|
|
21
|
+
audit trail), **I**nformation disclosure (leaking data to the wrong party),
|
|
22
|
+
**D**enial of service (exhausting a resource so legitimate use fails), and
|
|
23
|
+
**E**levation of privilege (gaining capabilities you were not granted).
|
|
24
|
+
|
|
25
|
+
Each of the five in-scope components below gets a fixed five-part treatment:
|
|
26
|
+
**Assets** (what an attacker wants), **Entry points** (the untrusted-input
|
|
27
|
+
boundary), **STRIDE threats** (which categories apply), **Current mitigations**
|
|
28
|
+
(citing **real shipped code** — file + line + behavior), and **Residual risks**
|
|
29
|
+
(threats current code does **not** fully cover, each routed to the Phase 33.5
|
|
30
|
+
plan that closes it). Out of scope per CONTEXT: rewriting the issue-reporter
|
|
31
|
+
network model — it is **documented** here as already-mitigated, not
|
|
32
|
+
re-engineered.
|
|
33
|
+
|
|
34
|
+
## Trust boundaries
|
|
35
|
+
|
|
36
|
+
The runtime crosses four trust boundaries. On the untrusted side of each sits
|
|
37
|
+
input that an attacker (or a compromised peer / config author / network host)
|
|
38
|
+
controls; the table names what crosses the line.
|
|
39
|
+
|
|
40
|
+
| Boundary | Untrusted side | What crosses |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| WS event-stream server `←` client | A WebSocket client on the network (LAN/internet if bound wide) | The HTTP `Upgrade` request + `Authorization: Bearer` header |
|
|
43
|
+
| gdd-state MCP `←` environment / config / tool input | Whoever sets `GDD_STATE_PATH` or supplies a tool-call payload, or authors `.design/config.json` | The `GDD_STATE_PATH` env value + the JSON tool-input payloads |
|
|
44
|
+
| Peer-CLI broker `↔` spawned child | A spawned peer CLI (Codex / Gemini / Cursor / Copilot / Qwen) and its stdout stream | The child's stdout JSON frames + the parent env handed to the child |
|
|
45
|
+
| Outbound call sites `↔` external host | The remote HTTP host / GitHub / Figma the call reaches | The outbound request payload + whatever the remote returns |
|
|
46
|
+
|
|
47
|
+
The event payloads that traverse the bus (and therefore the WS transport and
|
|
48
|
+
any persisted JSONL) are scrubbed at serialize time — see Component 4's
|
|
49
|
+
`redact.cjs` mitigation, which is the cross-cutting information-disclosure
|
|
50
|
+
control for the whole bus.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Component 1 — Hooks (SessionStart update-check + budget/context-monitor)
|
|
55
|
+
|
|
56
|
+
The hooks run automatically: `SessionStart` fires the update-check on every
|
|
57
|
+
session, and the budget / context-monitor hook runs on tool-use to enforce
|
|
58
|
+
spend and context ceilings. They execute with the user's full shell privileges
|
|
59
|
+
inside the user's repo, with no sandbox.
|
|
60
|
+
|
|
61
|
+
- **Assets:** The user's shell + filesystem (the hook runs as the user); the
|
|
62
|
+
integrity of the budget/context accounting the monitor maintains; the
|
|
63
|
+
network reachability of the update-check's outbound call.
|
|
64
|
+
- **Entry points:** The update-check's outbound HTTP fetch and whatever it
|
|
65
|
+
parses from the response (a version string / changelog); the hook's read of
|
|
66
|
+
`.design/config.json` (a malicious or malformed config is untrusted input);
|
|
67
|
+
the tool-use payload the budget monitor inspects.
|
|
68
|
+
- **STRIDE threats:**
|
|
69
|
+
- **Spoofing:** A spoofed update endpoint (DNS/MITM) could feed a forged
|
|
70
|
+
"latest version" response to the update-check.
|
|
71
|
+
- **Tampering:** A malformed `.design/config.json` could try to corrupt the
|
|
72
|
+
budget/context accounting or flip the monitor's thresholds.
|
|
73
|
+
- **Repudiation:** Hook actions are largely silent — limited audit trail of
|
|
74
|
+
what a SessionStart hook did or why a budget veto fired.
|
|
75
|
+
- **Information disclosure:** The update-check's User-Agent / outbound
|
|
76
|
+
request reveals that GDD is in use; a verbose hook could echo env into logs.
|
|
77
|
+
- **Denial of service:** A hung or slow update endpoint could stall session
|
|
78
|
+
start if the fetch were unbounded.
|
|
79
|
+
- **Elevation of privilege:** The hook already runs at full user privilege —
|
|
80
|
+
the residual concern is a config-driven path or command injection lifting
|
|
81
|
+
*attacker* input to that privilege level.
|
|
82
|
+
- **Current mitigations:** The update-check is **advisory** — it informs of a
|
|
83
|
+
newer version and never auto-installs or executes downloaded code, so a
|
|
84
|
+
spoofed version string cannot achieve code execution. The budget /
|
|
85
|
+
context-monitor reads config defensively (missing file / malformed JSON /
|
|
86
|
+
missing key are tolerated, mirroring the issue-reporter kill-switch's
|
|
87
|
+
config-tolerance contract in `scripts/lib/issue-reporter/kill-switch.cjs`).
|
|
88
|
+
Hooks emit through the event bus, which is redacted by `redact.cjs` at
|
|
89
|
+
serialize time (see Component 4), so secrets in hook telemetry are scrubbed.
|
|
90
|
+
- **Residual risks:** The update-check's outbound egress is one of the
|
|
91
|
+
cross-cutting call sites that currently has **no machine-readable allowlist
|
|
92
|
+
and no CI gate** asserting it is the only network touch a hook makes →
|
|
93
|
+
audited + allowlisted in **33.5-02** and gated in **33.5-04**. (The shell
|
|
94
|
+
hook `hooks/update-check.sh` is `.sh`, outside the `.js`-family static
|
|
95
|
+
scanner's scope, so it is **documented** in the **33.5-02** audit report
|
|
96
|
+
rather than hard-gated.)
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Component 2 — MCP servers (gdd-state: 11 mutating tools / gdd-mcp: read)
|
|
101
|
+
|
|
102
|
+
Two MCP servers expose GDD state to an MCP client: **gdd-state**
|
|
103
|
+
(`sdk/mcp/gdd-state/`) with **11 mutating tools** — `add_blocker`,
|
|
104
|
+
`add_decision`, `add_must_have`, `checkpoint`, `frontmatter_update`, `get`,
|
|
105
|
+
`probe_connections`, `resolve_blocker`, `set_status`, `transition_stage`,
|
|
106
|
+
`update_progress` — and **gdd-mcp** (`sdk/mcp/gdd-mcp/`) with read tools. The
|
|
107
|
+
mutating server is the higher-value target because it writes `STATE.md`.
|
|
108
|
+
|
|
109
|
+
- **Assets:** The integrity of `STATE.md` (the project's source of truth for
|
|
110
|
+
position, decisions, blockers, stage); the event stream the mutations emit;
|
|
111
|
+
the filesystem region the server is allowed to write.
|
|
112
|
+
- **Entry points:** The `GDD_STATE_PATH` environment variable (which redirects
|
|
113
|
+
*where the server reads/writes*); the JSON tool-input payloads for all 11
|
|
114
|
+
mutating tools; the `.design/STATE.md` file content the server parses.
|
|
115
|
+
- **STRIDE threats:**
|
|
116
|
+
- **Spoofing:** A tool caller could impersonate a legitimate pipeline stage
|
|
117
|
+
and drive `transition_stage` / `set_status` without authorization.
|
|
118
|
+
- **Tampering:** Crafted tool inputs could write hostile content into
|
|
119
|
+
`STATE.md`, or `GDD_STATE_PATH` could redirect writes onto an unintended
|
|
120
|
+
file (path traversal).
|
|
121
|
+
- **Repudiation:** Without a complete mutation audit trail, a hostile or
|
|
122
|
+
buggy mutation is hard to attribute — partly addressed by the event
|
|
123
|
+
emissions below.
|
|
124
|
+
- **Information disclosure:** A `get` against a traversed path could read a
|
|
125
|
+
file outside the intended `.design/` boundary.
|
|
126
|
+
- **Denial of service:** A JSON-bomb (deeply nested object / multi-megabyte
|
|
127
|
+
string field) in a tool payload could exhaust memory/CPU during parse.
|
|
128
|
+
- **Elevation of privilege:** Path traversal via `GDD_STATE_PATH` plus an
|
|
129
|
+
absent boundary check effectively elevates a tool caller's reach to any
|
|
130
|
+
file the process can write.
|
|
131
|
+
- **Current mitigations:** Every mutation emits a `state.mutation` /
|
|
132
|
+
`state.transition` event through `emitStateMutation()` / `emitStateTransition()`
|
|
133
|
+
(`sdk/mcp/gdd-state/tools/shared.ts` lines 91–140), giving a partial audit
|
|
134
|
+
trail (anti-repudiation). Handlers **never throw to the harness** — every
|
|
135
|
+
error funnels through `errorResponse()` → `toToolError()` into a structured
|
|
136
|
+
`{success:false,error}` (shared.ts lines 28–31, 148–151), so a malformed
|
|
137
|
+
input degrades to a clean error instead of a crash. Each of the 11 tools
|
|
138
|
+
already ships a JSON input schema under `sdk/mcp/gdd-state/schemas/`. State
|
|
139
|
+
events are redacted by `redact.cjs` at serialize time (Component 4).
|
|
140
|
+
- **Residual risks:** `resolveStatePath()` (`sdk/mcp/gdd-state/tools/shared.ts`
|
|
141
|
+
lines 60–64) honors `GDD_STATE_PATH` with **no path-traversal guard** — it
|
|
142
|
+
returns the override verbatim, so `..` escape / absolute-outside / symlink
|
|
143
|
+
escape are unchecked. The tool schemas exist but carry **no payload-size cap**
|
|
144
|
+
(no JSON-bomb guard) and are not uniformly tightened
|
|
145
|
+
(`additionalProperties:false` + `maxLength`). Path traversal + JSON-bomb +
|
|
146
|
+
un-tightened schemas are all closed by **33.5-03** (path-traversal guard +
|
|
147
|
+
payload cap + all 11 schemas tightened).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Component 3 — Peer-CLI broker (acp-client + asp-client child spawn)
|
|
152
|
+
|
|
153
|
+
The broker spawns peer CLIs over stdio: `scripts/lib/peer-cli/acp-client.cjs`
|
|
154
|
+
(ACP-protocol peers) and `scripts/lib/peer-cli/asp-client.cjs` (Codex
|
|
155
|
+
app-server protocol). Both fork a local child process and exchange
|
|
156
|
+
line-delimited JSON over its stdio. The child is **untrusted** — it is a
|
|
157
|
+
third-party CLI whose stdout the broker parses.
|
|
158
|
+
|
|
159
|
+
- **Assets:** GDD's process environment — specifically `ANTHROPIC_API_KEY`,
|
|
160
|
+
`GH_TOKEN`, and any `GDD_*` / provider secret in `process.env`; the broker's
|
|
161
|
+
memory/availability; the integrity of the JSON protocol exchange.
|
|
162
|
+
- **Entry points:** The child's **stdout** (untrusted JSON frames the broker
|
|
163
|
+
must parse); the **environment handed to the child** at spawn time; the
|
|
164
|
+
`opts.command` / `opts.args` the broker is asked to launch.
|
|
165
|
+
- **STRIDE threats:**
|
|
166
|
+
- **Spoofing:** A misbehaving peer could emit forged protocol replies /
|
|
167
|
+
request IDs to confuse the correlation map.
|
|
168
|
+
- **Tampering:** A peer could stream malformed frames attempting to corrupt
|
|
169
|
+
the broker's line-buffer / pending-request state.
|
|
170
|
+
- **Repudiation:** Limited record of exactly what env a given child was
|
|
171
|
+
handed at spawn.
|
|
172
|
+
- **Information disclosure:** **The headline risk** — the child inherits
|
|
173
|
+
GDD's full environment, so a hostile or compromised peer reads
|
|
174
|
+
`ANTHROPIC_API_KEY` / `GH_TOKEN` straight out of `process.env`.
|
|
175
|
+
- **Denial of service:** A peer that never emits a newline could force the
|
|
176
|
+
broker to buffer unbounded stdout until memory exhaustion.
|
|
177
|
+
- **Elevation of privilege:** Inherited secrets let a peer act *as GDD*
|
|
178
|
+
against GDD's providers — using GDD's keys for the peer's own ends.
|
|
179
|
+
- **Current mitigations:** `acp-client.cjs` caps an un-terminated stdout line
|
|
180
|
+
at **`MAX_LINE_BYTES = 16 * 1024 * 1024`** (16 MiB; defined line 62, enforced
|
|
181
|
+
lines 166–176 — a peer that emits 16 MiB without a newline gets its active
|
|
182
|
+
prompt rejected as a protocol violation). This is a real **DoS guard** on the
|
|
183
|
+
untrusted stdout channel. The broker uses plain `spawn` with **no shell**
|
|
184
|
+
(acp-client.cjs lines 106–113, `windowsHide: true`), avoiding shell-injection
|
|
185
|
+
on the command path. Per-request correlation via a pending-id map bounds the
|
|
186
|
+
protocol state machine.
|
|
187
|
+
- **Residual risks:** Both clients default the child's environment to the
|
|
188
|
+
**full `process.env`** when `opts.env` is absent — `acp-client.cjs` line 102
|
|
189
|
+
(`const env = opts.env && typeof opts.env === 'object' ? opts.env :
|
|
190
|
+
process.env;`) and `asp-client.cjs` line 122 (when `opts.env` is absent no
|
|
191
|
+
`spawnOptions.env` is set, so the child inherits the parent's `process.env` by
|
|
192
|
+
Node default). This leaks GDD's `ANTHROPIC_API_KEY` / `GH_TOKEN` / `GDD_*` to
|
|
193
|
+
every spawned peer. Closed by **33.5-04** (allowlist-forward, default-deny env
|
|
194
|
+
sandbox via a shared `sanitize-env` helper applied to both clients; secrets
|
|
195
|
+
are never forwarded unless explicitly allowlisted in `.design/config.json`).
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Component 4 — WebSocket event-stream transport (scripts/lib/transports/ws.cjs)
|
|
200
|
+
|
|
201
|
+
`scripts/lib/transports/ws.cjs` exposes the event-stream bus over WebSocket:
|
|
202
|
+
one JSON event per text frame, with optional replay of a tail file to each new
|
|
203
|
+
connection. It is an **optional dependency** (`ws`) — absent installs render an
|
|
204
|
+
install hint instead of starting. When running, it is a network listener.
|
|
205
|
+
|
|
206
|
+
- **Assets:** The **event stream itself** (every `state.mutation` /
|
|
207
|
+
`state.transition` / pipeline event, which can carry payload detail); the
|
|
208
|
+
listening socket; the Bearer token that authorizes a connection.
|
|
209
|
+
- **Entry points:** The HTTP **`Upgrade` request** from any client that can
|
|
210
|
+
reach the bound socket, and specifically its `Authorization: Bearer <token>`
|
|
211
|
+
header; the `tailFrom` replay file path.
|
|
212
|
+
- **STRIDE threats:**
|
|
213
|
+
- **Spoofing:** A client without the token attempting to subscribe to the
|
|
214
|
+
live event stream.
|
|
215
|
+
- **Tampering:** N/A for inbound (the transport is push-only to clients) —
|
|
216
|
+
the concern is read access, not write.
|
|
217
|
+
- **Repudiation:** No per-connection identity beyond the shared token, so
|
|
218
|
+
individual subscribers are not distinguishable in an audit.
|
|
219
|
+
- **Information disclosure:** **The headline risk** — an unauthorized
|
|
220
|
+
subscriber would receive the entire live event stream, including any
|
|
221
|
+
sensitive payload detail, if it could reach the socket and pass auth.
|
|
222
|
+
- **Denial of service:** Many connections / a slow consumer could pressure
|
|
223
|
+
the server (mitigated in part by fire-and-forget, no-queue backpressure).
|
|
224
|
+
- **Elevation of privilege:** A network-reachable listener turns a
|
|
225
|
+
local-only observability feature into a remotely-reachable data source.
|
|
226
|
+
- **Current mitigations:** **Bearer-token auth is enforced on every upgrade**:
|
|
227
|
+
`ws.cjs` lines 110–116 reject any upgrade whose header is missing or where
|
|
228
|
+
the supplied token does not match the expected `Bearer` value, returning an `HTTP/1.1 401 Unauthorized` and a
|
|
229
|
+
socket destroy. The token **must be ≥8 chars** — `startServer` throws a
|
|
230
|
+
`TypeError` if `opts.token.length < 8` (line 74), preventing trivially weak
|
|
231
|
+
tokens. Backpressure is **fire-and-forget with no queue** (lines 91–108):
|
|
232
|
+
events for a non-OPEN socket are dropped, bounding memory under a slow
|
|
233
|
+
consumer. Cross-cutting for the whole bus: **`redact.cjs`** deep-walks every
|
|
234
|
+
event payload at serialize time (`scripts/lib/redact.cjs` — `redact()` lines
|
|
235
|
+
95–116, `redactString()` lines 75–83) and scrubs **8 secret patterns** (pem,
|
|
236
|
+
jwt, anthropic `sk-ant-`, stripe `sk_live_`, slack `xox[baprs]`, github_pat
|
|
237
|
+
`ghp_`, aws `AKIA`, generic `sk-`), so secrets in event payloads are masked
|
|
238
|
+
before they ever reach a WS subscriber or hit disk. This `redact.cjs` scrub is
|
|
239
|
+
the runtime's primary information-disclosure control across **all** components
|
|
240
|
+
that emit events.
|
|
241
|
+
- **Residual risks:**
|
|
242
|
+
- The server binds to **all interfaces (`0.0.0.0`)** by default —
|
|
243
|
+
`httpServer.listen(opts.port, ...)` (line 145) passes **no host argument**,
|
|
244
|
+
so on a multi-homed / LAN host the token-protected stream is reachable
|
|
245
|
+
off-box. The token compare uses `!==` (line 112), which is
|
|
246
|
+
**timing-unsafe**. Both closed by **33.5-03** (default bind `127.0.0.1` +
|
|
247
|
+
opt-in remote via `event_stream.bind_host` / `GDD_WS_BIND_HOST` + a CI gate
|
|
248
|
+
that fails if the default config would bind `0.0.0.0`; upgrade the compare to
|
|
249
|
+
`crypto.timingSafeEqual`).
|
|
250
|
+
- `redact.cjs` is **missing three modern token formats**: Gemini / GCP
|
|
251
|
+
`AIza…`, GitHub fine-grained `github_pat_…`, and GitHub server / oauth /
|
|
252
|
+
user / refresh `gh[sour]_…`. A payload carrying one of these would leak
|
|
253
|
+
through the scrub onto the stream and disk. Closed by **33.5-05** (add the
|
|
254
|
+
three patterns + a synthetic-secret fuzz test asserting zero leak).
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Component 5 — Issue-reporter outbound (gh CLI only)
|
|
259
|
+
|
|
260
|
+
`scripts/lib/issue-reporter/` is the only first-party feature that intentionally
|
|
261
|
+
reaches the network. It assembles a bug report and submits it through the user's
|
|
262
|
+
**`gh` CLI**. **This network model is already mitigated and is DOCUMENTED here,
|
|
263
|
+
not re-engineered** (CONTEXT Out-of-scope: rewriting the issue-reporter network
|
|
264
|
+
model).
|
|
265
|
+
|
|
266
|
+
- **Assets:** The user's GitHub identity (via the local `gh` auth); the content
|
|
267
|
+
of the submitted report (which must not carry the user's secrets or
|
|
268
|
+
unintended PII); the integrity of the destination repo.
|
|
269
|
+
- **Entry points:** The user-invoked report flow (the body / title assembled
|
|
270
|
+
from local state); the `.design/config.json` and the env that gate whether the
|
|
271
|
+
reporter runs at all.
|
|
272
|
+
- **STRIDE threats:**
|
|
273
|
+
- **Spoofing:** A forged destination could try to receive reports — mitigated
|
|
274
|
+
by the frozen destination below.
|
|
275
|
+
- **Tampering:** Attempting to redirect submissions to an attacker repo by
|
|
276
|
+
injecting a destination override.
|
|
277
|
+
- **Repudiation:** Submissions flow through `gh` under the user's identity,
|
|
278
|
+
which is itself the attribution record.
|
|
279
|
+
- **Information disclosure:** **The headline risk** — a report could exfiltrate
|
|
280
|
+
secrets / PII embedded in local state if the payload were not scrubbed.
|
|
281
|
+
- **Denial of service:** Not a meaningful vector — submission is a
|
|
282
|
+
user-initiated, one-shot CLI call.
|
|
283
|
+
- **Elevation of privilege:** Using the user's `gh` credentials beyond the
|
|
284
|
+
single sanctioned submit.
|
|
285
|
+
- **Current mitigations (ALREADY shipped — documented, no change here):**
|
|
286
|
+
- **Outbound is via the `gh` CLI ONLY.** `gh-submit.cjs` wraps
|
|
287
|
+
`gh issue create --repo <DESTINATION_REPO> --title … --body-file …` and is
|
|
288
|
+
explicit that "the user's gh CLI is the sole outbound primitive. No HTTP-S
|
|
289
|
+
URL literals, no global fetch primitive, no plugin-side credentials" (D-05).
|
|
290
|
+
There is no raw HTTP egress in this subtree.
|
|
291
|
+
- **Frozen destination.** `destination.cjs` is an `Object.freeze`-d module —
|
|
292
|
+
the single source of truth for the destination repo, with **no env-var
|
|
293
|
+
lookup, no config override, no flag override**. A static CI gate asserts it
|
|
294
|
+
is the only file under the report-issue tree that contains the destination
|
|
295
|
+
literal, so a redirect attempt fails the build.
|
|
296
|
+
- **Kill-switch (dual-surface).** `kill-switch.cjs` disables the reporter via
|
|
297
|
+
**either** the env var `GDD_DISABLE_ISSUE_REPORTER === '1'` **or** the config
|
|
298
|
+
`.design/config.json` `{ "issue_reporter": false }`; either surface alone is
|
|
299
|
+
sufficient, and config is read tolerantly (missing file / malformed JSON /
|
|
300
|
+
missing key are safe).
|
|
301
|
+
- Payloads pass through privacy-diff / consent-prompt machinery before
|
|
302
|
+
submission, and event telemetry is redacted by `redact.cjs` (Component 4).
|
|
303
|
+
- **Residual risks:** The issue-reporter's **own** network model has no residual
|
|
304
|
+
this phase changes — it is intentionally documented as complete. The only
|
|
305
|
+
cross-cutting residual touching it is the **lack of a machine-readable
|
|
306
|
+
outbound allowlist + CI gate** that proves `gh-submit` is the sole egress in
|
|
307
|
+
this subtree at a tree-wide level: closed by **33.5-02** (the canonical
|
|
308
|
+
outbound-network allowlist data, which lists `scripts/lib/issue-reporter/**`
|
|
309
|
+
as an allowed egress glob) and **33.5-04** (the `scan:outbound` CI gate that
|
|
310
|
+
fails on any active-egress site not under an allowlisted glob).
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Residual-risk → closing-plan map
|
|
315
|
+
|
|
316
|
+
Every residual risk identified above is routed to the Phase 33.5 plan (or
|
|
317
|
+
policy doc) that closes it. No residual is left unmapped. This table is the
|
|
318
|
+
spine the phase closeout (33.5-06) uses to prove completeness.
|
|
319
|
+
|
|
320
|
+
| Residual risk | Component | Closing plan |
|
|
321
|
+
| --- | --- | --- |
|
|
322
|
+
| WS binds `0.0.0.0` by default (`listen` line 145, no host) + timing-unsafe `!==` token compare (line 112) | WebSocket transport | **33.5-03** |
|
|
323
|
+
| `GDD_STATE_PATH` path traversal (no guard, shared.ts 60–64) + no payload-size cap + un-tightened tool schemas | gdd-state MCP | **33.5-03** |
|
|
324
|
+
| Full `process.env` (incl. `ANTHROPIC_API_KEY` / `GH_TOKEN`) leaks to spawned peers (acp 102 / asp 122) | Peer-CLI broker | **33.5-04** |
|
|
325
|
+
| Outbound egress sites have no machine-readable allowlist + no CI gate | cross-cutting (hooks update-check, figma-extract, issue-reporter, e2e) | **33.5-02** (allowlist) + **33.5-04** (scan gate) |
|
|
326
|
+
| Secret-scan misses Gemini `AIza…` / GitHub fine-grained `github_pat_…` / GitHub server `gh[sour]_…` tokens | redact.cjs | **33.5-05** |
|
|
327
|
+
| No published vulnerability-disclosure policy | project | **33.5-06** (SECURITY.md) |
|
|
328
|
+
|
|
329
|
+
### Already-mitigated (documented, NOT re-engineered)
|
|
330
|
+
|
|
331
|
+
| Already-mitigated surface | Component | Evidence |
|
|
332
|
+
| --- | --- | --- |
|
|
333
|
+
| Outbound via `gh` CLI only; frozen destination; dual-surface kill-switch | Issue-reporter | `gh-submit.cjs` (`gh issue create` only), `destination.cjs` (`Object.freeze`, no override), `kill-switch.cjs` (env + `.design/config.json`) |
|
|
334
|
+
| Bearer-token auth on every WS upgrade + ≥8-char token rule | WebSocket transport | `ws.cjs` 110–116 (401 on mismatch) + line 74 (`length < 8` → `TypeError`) |
|
|
335
|
+
| 16 MiB un-newlined-stdout DoS cap on untrusted peer output | Peer-CLI broker | `acp-client.cjs` `MAX_LINE_BYTES` line 62, enforced 166–176 |
|
|
336
|
+
| Deep-walk secret scrub of every event payload at serialize time | cross-cutting (event bus) | `redact.cjs` `redact()` 95–116 / `redactString()` 75–83, 8 patterns |
|
package/reference/registry.json
CHANGED
|
@@ -860,6 +860,20 @@
|
|
|
860
860
|
"type": "meta-rules",
|
|
861
861
|
"phase": 30,
|
|
862
862
|
"description": "Phase 30 triage gate catalogue — locally-fixable failure modes (id/pattern/diagnosis/remedy/severity, with optional propose_report whitelist flag per D-11) consulted by scripts/lib/issue-reporter/triage-matcher.cjs before the report-issue consent prompt (D-07/D-11)."
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
"name": "gdd-threat-model",
|
|
866
|
+
"path": "reference/gdd-threat-model.md",
|
|
867
|
+
"type": "heuristic",
|
|
868
|
+
"phase": 33.5,
|
|
869
|
+
"description": "Phase 33.5 STRIDE threat model of GDD's own runtime attack surface — hooks, the gdd-state + gdd-mcp MCP servers, the peer-CLI broker, the WebSocket event-stream transport, and issue-reporter outbound; maps each residual risk to the 33.5 plan that closes it."
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
"name": "gdd-runtime-audit",
|
|
873
|
+
"path": "reference/gdd-runtime-audit.md",
|
|
874
|
+
"type": "heuristic",
|
|
875
|
+
"phase": 33.5,
|
|
876
|
+
"description": "Phase 33.5 static security audit of GDD's shipped runtime surface (hooks/scripts/sdk/bin) — outbound-network call sites, secret-handling sites, and external-input surfaces; human-readable companion to scripts/security/outbound-allowlist.json (the canonical active-egress allowlist the 33.5-04 scan-outbound-network.cjs gate consumes) and reference/gdd-threat-model.md."
|
|
863
877
|
}
|
|
864
878
|
]
|
|
865
879
|
}
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
const { spawn } = require('child_process');
|
|
52
52
|
const { EventEmitter } = require('events');
|
|
53
|
+
const { sanitizeEnv, readPeerCliAllowlist } = require('./sanitize-env.cjs');
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Hard cap on the size of a single un-terminated line read from the
|
|
@@ -99,7 +100,14 @@ function createAcpClient(opts) {
|
|
|
99
100
|
const command = opts.command;
|
|
100
101
|
const args = Array.isArray(opts.args) ? opts.args : [];
|
|
101
102
|
const cwd = typeof opts.cwd === 'string' ? opts.cwd : process.cwd();
|
|
102
|
-
|
|
103
|
+
// Plan 33.5-04 (D-03): when the caller does not supply an explicit env, the
|
|
104
|
+
// child inherits a SANITIZED env (OS-essential baseline + the configured
|
|
105
|
+
// peer_cli.env_allowlist) instead of the raw full process.env — so GDD's
|
|
106
|
+
// ANTHROPIC_API_KEY/GH_TOKEN/GDD_* never leak to spawned peers. An explicit
|
|
107
|
+
// opts.env still wins (callers/tests can pass a full env).
|
|
108
|
+
const env = opts.env && typeof opts.env === 'object'
|
|
109
|
+
? opts.env
|
|
110
|
+
: sanitizeEnv(process.env, { allowlist: readPeerCliAllowlist() });
|
|
103
111
|
|
|
104
112
|
const events = new EventEmitter();
|
|
105
113
|
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
'use strict';
|
|
81
81
|
|
|
82
82
|
const { spawn } = require('node:child_process');
|
|
83
|
+
const { sanitizeEnv, readPeerCliAllowlist } = require('./sanitize-env.cjs');
|
|
83
84
|
|
|
84
85
|
/** Per-line cap before we treat the stream as malformed. */
|
|
85
86
|
const MAX_LINE_BYTES = 16 * 1024 * 1024;
|
|
@@ -119,7 +120,15 @@ function createAspClient(opts) {
|
|
|
119
120
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
121
|
};
|
|
121
122
|
if (typeof opts.cwd === 'string' && opts.cwd.length > 0) spawnOptions.cwd = opts.cwd;
|
|
122
|
-
if (opts.env && typeof opts.env === 'object')
|
|
123
|
+
if (opts.env && typeof opts.env === 'object') {
|
|
124
|
+
spawnOptions.env = opts.env;
|
|
125
|
+
} else {
|
|
126
|
+
// Plan 33.5-04 (D-03): without an explicit env, the child ALWAYS gets a
|
|
127
|
+
// defined, SANITIZED env (OS-essential baseline + peer_cli.env_allowlist)
|
|
128
|
+
// rather than Node defaulting to the raw process.env — closing the
|
|
129
|
+
// ANTHROPIC_API_KEY/GH_TOKEN/GDD_* leak. Explicit opts.env still wins.
|
|
130
|
+
spawnOptions.env = sanitizeEnv(process.env, { allowlist: readPeerCliAllowlist() });
|
|
131
|
+
}
|
|
123
132
|
|
|
124
133
|
// Test-injection seam: callers (or unit tests) can supply a pre-built
|
|
125
134
|
// ChildProcess so we don't actually fork a binary in tests. The mock
|