Skip to content

release: v0.1.6 — security tactical (4 CWE fixes from PRD-002)#20

Merged
explosivebit merged 19 commits into
mainfrom
release/v0.1.6
May 5, 2026
Merged

release: v0.1.6 — security tactical (4 CWE fixes from PRD-002)#20
explosivebit merged 19 commits into
mainfrom
release/v0.1.6

Conversation

@explosivebit
Copy link
Copy Markdown
Contributor

Patch release. Ships PRD-002 S1 batch to npm: CWE-78 FORGEPLAN_BIN regex, CWE-59 update symlink-guard, CWE-770 spawn concurrency cap, CWE-1357 build --ignore-scripts. Plus the docs rewrite that was meant for 0.1.5 but landed on develop after that tag. Refs PRD-002 EVID-009.

explosivebit and others added 19 commits May 4, 2026 21:29
Brings v0.1.5 release commits (version bump 0.1.4→0.1.5 in package.json
+ template/package.json) back into develop per Git Flow §2.5 step 4.
Without this back-merge develop forever lags main on version, and the
next release/v0.1.6 cut would create merge conflicts.
…EVID-007/008)

Two ID collisions sat on disk after merging two parallel sessions: EVID-004 had both my claude-md-baseline file and the smoke-update file, EVID-005 had the rule-12-adr-002-protocol file and my safety-hardening file. forgeplan reindex silently took whichever the projection iterator hit first, so the index showed 8 artifacts while the markdown tree had 11 (plus ADR-002, RFC-002, RFC-003, EVID-006 that were also missing because the prior reindex never picked them up).

Rename strategy: my EVID-004 and EVID-005 are already linked into PRD-001 with R_eff = 1.00 — touch them and links break. Renumber the user-side dupes to the next free IDs: smoke-update → EVID-007, rule-12-adr-002-protocol → EVID-008. Frontmatter id: + body heading updated together so the file is internally consistent. State sidecar EVID-005.yaml renamed to EVID-008.yaml.

After this commit and a forgeplan reindex run: 14 artifacts in the index, every markdown file on disk has exactly one row in forgeplan list, four typed links restored (EVID-006 informs RFC-003, EVID-007 informs RFC-002, EVID-008 informs ADR-002, plus existing EVID-001..005 links). Health: blind_spots and orphans both empty.

Refs: PRD-001
Today's audit ran in Mode B (sub-agents fallback) because TeamCreate / TeamDelete / SendMessage tools were not exposed to the harness. The agent-team-orchestration skill explicitly prefers Mode A (TeamCreate) when available — shared context, addressable agents, team-lead oversight. The env flag opts this repo into the experimental Agent Teams runtime.

Effect on tarball: zero. .claude/ is repo-local config, not in package.json#files.

Refs: PRD-001
Two ID collisions on disk after merging parallel sessions. forgeplan
reindex was silently shadowing 6 of 14 markdown files. Renumbered the
user-side dupes to next free IDs preserving links. After this 14
artifacts in index, all links restored, health blind_spots and orphans
empty. Refs PRD-001.
Opt this repo into Mode A (TeamCreate/TeamDelete/SendMessage) for future
multi-expert audits. Today's audit ran in Mode B fallback because the
flag was off. Zero tarball impact — .claude/ is not in
package.json#files.
Add .forgeplan runtime directories to .gitignore:
- .fastembed_cache/ (vector search cache)
- logs/ (activity logs)
- trash/ (soft-deleted artifacts)
- state/ (phase tracking)
- claims/ (work coordination)
- journal/ (decision timeline)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add .forgeplan runtime directories to .gitignore:
- .fastembed_cache/ (vector search cache)
- logs/ (activity logs)
- trash/ (soft-deleted artifacts)
- state/ (phase tracking)
- claims/ (work coordination)
- journal/ (decision timeline)
…CWE-770)

Security audit on v0.1.5 surfaced two issues at the SvelteKit-to-forgeplan-CLI boundary in template/src/shared/server/forgeplan.ts:

1) FORGEPLAN_BIN reads from env and is passed to spawn with shell:true on Windows. With shell:true the executable path interpolates into cmd.exe, so any whitespace or shell metacharacter in FORGEPLAN_BIN becomes a shell token. CWE-78 hostile-env command injection. Validate at module load against a regex anchored ^[A-Za-z0-9_./:\\-]+$. On rejection: console.error the offending value, fall back to literal forgeplan, refuse every spawn with a 502-shaped envelope.

2) runForgeplan had no concurrency cap. A loopback DoS or HOST=0.0.0.0 LAN-bound instance can pile up forgeplan subprocess spawns until the event loop stalls. CWE-770 resource exhaustion. In-process semaphore caps simultaneous spawns at 4; further calls queue via acquireSpawnSlot/releaseSpawnSlot, with the slot released in a finally block on the inner Promise so timeout, error, close all return capacity.

Tests: svelte-check 0 errors / 0 warnings. Smoke (npm run smoke) PASS on macOS in 11.46s; T-1 hardening visible in compiled dist/server/chunks/server-*.js (regex literal, console.error message, refuse-with-502 path).

Refs: PRD-002 (FR-001, FR-004, FR-007)
…(CWE-59)

bin/forgeplan-web.mjs#update calls rmSync(target, recursive, force) where target = join(cwd, .forgeplan-web). If .forgeplan-web is a symlink (legitimate dev workflow or hostile), rmSync would happily remove the resolved tree. CWE-59 link following.

Two-layer guard before rmSync: (1) lstatSync target; if isSymbolicLink, fail with refusing-to-follow message and the offending path; (2) defense-in-depth equality assert resolve(target) === resolve(join(cwd, .forgeplan-web)) — tautological today, but a tripwire if a future refactor reassigns target from env or config. Both rejections console.error the failing path so operators see the cause. fail() exits non-zero.

init's cpSync stays untouched: it's add-only, not destructive, and the host-isolation rule 20 already bounds its blast radius.

Tests: node --check bin syntax PASS. Smoke (npm run smoke) PASS — init run 1 created scaffold, run 2 with --force passed through the new guards as a non-symlinked legitimate target. Negative path (symlinked target) verified by code review only; runtime test would require manipulating /tmp scratch outside the smoke harness.

Refs: PRD-002 (FR-002, FR-003, FR-007)
scripts/build.mjs#installRuntimeDeps runs npm install --omit=dev --omit=peer inside template/build/ to populate the published dist/node_modules/. Without --ignore-scripts, every transitive runtime dep gets a chance to execute postinstall lifecycle scripts during package build. CWE-1357 supply-chain via lifecycle hooks.

Adding --ignore-scripts is the npm-blessed mitigation: blocks postinstall, preinstall, and install scripts. Today's 5 runtime deps (@sveltejs/kit, d3-force, d3-selection, d3-zoom, svelte) are pure-JS and don't need any postinstall, so this is loss-free. If a future runtime dep relies on postinstall (e.g. native binding download), the documented fallback is to whitelist that dep — but smoke matrix on 3 OS will catch the breakage at PR review, not in production.

Tests: smoke (npm run smoke) PASS on macOS — runtime install line in stdout ends with --ignore-scripts and added 41 packages without invoking any lifecycle hook.

Refs: PRD-002 (FR-005)
Tactical hardening pass per PRD-002 (S1 batch). One bullet per CWE — FORGEPLAN_BIN regex (CWE-78), update symlink-guard (CWE-59), spawn concurrency cap (CWE-770), build --ignore-scripts (CWE-1357). Existing [Unreleased] preamble (Pending for the next release) untouched; older release sections shifted by 9 lines.

Refs: PRD-002 (FR-006)
Lockfile name field still said 0.1.0; running npm install during smoke updated it to match template/package.json#version (0.1.5). Captured here so develop carries the synced lock.

Refs: PRD-002 (release follow-up)
Adds the PRD-002 markdown driving this branch. Standard depth (PRD only — RFC skipped per architecture-obvious clause), 7 FRs, 4 NFRs, 4 risks. Linked artifacts: PRD-001 parent methodology, EVID-005 prior safety hardening, RFC-S1 / EVID-S1 planned.

Refs: PRD-002
…k-guard, spawn cap, ignore-scripts) (#18)

Closes 4 audit findings from today's multi-expert code audit on
@forgeplan/web@0.1.5. None are CRITICAL — package was publishable as-is
— but each opens a narrow window. CWE-78 hostile-env command injection
on Windows shell:true. CWE-59 update rmSync follows symlink. CWE-770
spawn DoS via /api/log without concurrency cap. CWE-1357 transitive
postinstall during package build. Refs PRD-002. 6 commits, each
git-revert-able. Smoke local PASS (3-OS CI matrix will gate merge). Test
plan in PRD-002 Goals SC-1..SC-6.
EVID-009 verifies PR #18 (security tactical S1) against PRD-002 across three layers: source code grep on develop branch HEAD, compiled dist/server/chunks/ post-build review, and PR #18 CI matrix 3/3 OS green. R_eff for PRD-002 lands at 1.00 with CL3 / F-G-R grade A (0.80).

Activates EVID-009 and PRD-002 (draft to active). Workspace health: 5 ACTIVATED PRD/RFC/ADR, 2 EVIDENCED. Project looks healthy.

Refs: PRD-002, EVID-009
Forge-cycle Step 7-8 for PR #18 (PRD-002 S1 security tactical). EVID-009
verifies all 6 SC + 4 NFR across source/compiled/CI layers. R_eff
PRD-002 = 1.00 CL3 grade A. Activates both EVID-009 and PRD-002. Refs
PRD-002 EVID-009.
Patch release shipping the security tactical S1 batch (PRD-002): four CWE fixes that landed on develop in PR #18 but never reached the npm tarball at v0.1.5. Reorganises CHANGELOG so 0.1.6 has a Security section (the one this release is about), 0.1.5 has Added / Fixed (docs marketing rewrite + en/ru + LICENSE/CHANGELOG/hero — already shipped to npm at v0.1.5). [Unreleased] is empty.

Refs: PRD-002, EVID-009
@explosivebit explosivebit merged commit 40889d1 into main May 5, 2026
3 checks passed
@explosivebit explosivebit deleted the release/v0.1.6 branch May 5, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants