Skip to content

feat: 170-commit hardening sprint — security, bug fixes, refactoring, and feature ports#46

Closed
briansumma wants to merge 308 commits into
cytostack:mainfrom
chesa:develop
Closed

feat: 170-commit hardening sprint — security, bug fixes, refactoring, and feature ports#46
briansumma wants to merge 308 commits into
cytostack:mainfrom
chesa:develop

Conversation

@briansumma

Copy link
Copy Markdown

Summary

This PR merges 170 commits from chesa/openwolf:develop into cytostack/openwolf:main, representing a comprehensive hardening and feature sprint built on top of the v1.0.x baseline.


What's Changed

Security Hardening

  • P0: WebSocket authenticationwolf-daemon.ts verifyClient now parses and validates an Authorization header; wolf-client.ts sends the token on connect; auth token stripped from URLs before writing to dashboard.log
  • HIGH-001: Prompt injection via cerebrum.md — resolved overwrite vulnerability; added explicit task-based control for instruction file writes
  • Defense-in-depth — hardened daemon, fs, and path modules; comprehensive security audit report included in docs

Bug Fixes

  • F-01 (fs-safe): suppress spurious ENOENT on safeCopyFile temp-file cleanup
  • F-02 (stop hook): guard main() against undefined wolfDir/sessionDir
  • cron-engine: skip zero-action sessions in consolidateMemory()
  • seedStatus now called when STATUS.md is first created during upgrade
  • config.json seeding uses safeCopyFile instead of raw fs.copyFileSync
  • Differentiate ENOENT from real errors in readMarkdown and checkCerebrumFreshness
  • Error detail added to killPid and daemonLogs catch blocks

Refactoring (Phase 02–03)

  • Hook modules split into leaf modules (wolf-files, wolf-describe); shared.ts replaced with a thin barrel facade
  • extractSmart refactored to delegate to language-family modules
  • All tests consolidated under tests/ directory
  • Worktree helper contract documented in hooks.md

Features

Testing

  • Regression guards for F-01 and F-02
  • security.test.ts rewritten to import and exercise production modules (not mocks)
  • Vitest API migration for security tests
  • Worktree ID hash stability pin

Documentation

  • 40+ documentation verification and correction passes across all .wolf/ and planning files
  • Codebase map added (stack, architecture, structure, integrations)
  • Phase plans, research docs, and session summaries for phases 01–04
  • Security audit report with threat model for dashboard WebSocket auth

Test Plan

  • pnpm install && pnpm build — clean build across CLI, hooks, and dashboard
  • tsc --noEmit and tsc --noEmit -p tsconfig.hooks.json — no type errors
  • Run test suite — all tests pass, including F-01/F-02 regression guards and security tests
  • node dist/bin/openwolf.js --help — CLI smoke test
  • Start daemon, connect dashboard — verify WebSocket auth (Authorization header required)
  • Verify cerebrum.md write protection is enforced

🤖 Generated with Claude Code

briansumma and others added 30 commits May 14, 2026 15:17
* security: bind dashboard to loopback, reject cross-origin WS upgrades

The dashboard server previously called app.listen(port) with no host
argument, binding to 0.0.0.0. Combined with the fact that none of the
HTTP or WebSocket endpoints require authentication, this meant the
dashboard was reachable from the LAN and cross-origin from any webpage
the user visited in a browser.

Exposed surface included:
- GET /api/files — contents of cerebrum.md, memory.md, buglog.json,
  token-ledger.json, and suggestions.json.
- POST /api/cron/run/:taskId and the WebSocket "trigger_task" handler —
  both execute cron tasks, including ai_task actions that shell out to
  claude -p in the project root.

This change:

1. Binds the HTTP/WebSocket server to 127.0.0.1 by default. The bind
   address is read from openwolf.dashboard.bind in .wolf/config.json
   (new optional field), defaulting to "127.0.0.1" when the field is
   absent so existing installs become loopback-only on restart.

2. Adds a verifyClient check on the WebSocket upgrade that allows
   same-origin connections (dashboard loaded from
   http://127.0.0.1:<port> or http://localhost:<port>) and non-browser
   clients (no Origin header), while rejecting any other Origin.

3. Logs a warning when the dashboard is bound to a non-loopback
   address, to make the security implication explicit for anyone who
   sets bind: "0.0.0.0" on purpose.

4. Documents the new default and the daemon.dashboard.bind opt-in in
   the README.

Users who were intentionally exposing the dashboard to their network
will need to set "bind": "0.0.0.0" under openwolf.dashboard in their
.wolf/config.json after upgrading.

* fix(daemon): correct WebSocket origin check for non-loopback bind

Three issues addressed in isAllowedOrigin / WebSocket CSRF guard:

1. (Bug) When bind="0.0.0.0", browsers send Origin: http://<actual-ip>:<port>,
   never http://0.0.0.0:<port>. The old code added the literal bind address to
   the allowed set, so remote WebSocket connections always failed the check even
   for users who explicitly opted into network access. Fix: use the Host request
   header to dynamically match whatever IP the client connected on.

2. (Warning) Absent Origin header was unconditionally allowed, letting any
   remote machine bypass the CSRF check with a non-browser client when
   bind="0.0.0.0". Fix: restrict no-Origin connections to loopback remote
   addresses only.

3. (Warning) The loopback identity check was duplicated between isAllowedOrigin
   and the startup warning logger. Fix: extract isLoopback() helper and use it
   in both places.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: autofix PR #2 per review comments

---------

Co-authored-by: Saad Khan <skhan8@mail.einstein.yu.edu>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: extend scanner language support and sync CODE_EXTS

Adds Flutter-critical languages (Kotlin, Swift, Objective-C) plus
common gaps (C++ variants, C#, Ruby, PHP, Lua, Vue, Svelte, HTML,
Protobuf, GraphQL, Terraform, shell variants) to both extension sets.

Also brings src/tracker/token-estimator.ts CODE_EXTS back in sync with
src/scanner/anatomy-scanner.ts CODE_EXTENSIONS — the two sets had
drifted apart since only CODE_EXTENSIONS gets the .dart addition from
#10. Adds a one-line "Keep in sync with ..." comment above each so
future additions hit both places.

These sets control the chars-per-token ratio (3.5 for code vs 3.75
fallback) used by estimateTokens; the net effect is ~7% more accurate
token accounting in anatomy.md and detectContentType() consumers for
projects written in these languages.

* refactor: extract shared CODE_EXTENSIONS to utils/extensions.ts; fix HTML ratio

Addresses two PR review findings:

WR-01 — Eliminate duplicate parallel Sets
  Both `anatomy-scanner.ts` and `token-estimator.ts` maintained byte-for-byte
  identical `CODE_EXTENSIONS`/`CODE_EXTS` sets linked only by a "Keep in sync"
  comment — an unenforceable convention that would silently produce divergent
  token-ratio behaviour the moment a future contributor updated one file and
  missed the other.  Extract to `src/utils/extensions.ts` as a single source
  of truth and import from both consumers.

WR-02 — Remove .html/.htm from CODE_EXTENSIONS
  HTML is markup with prose content and attribute text; classifying it as
  `code` applies a 3.5 chars/token ratio that consistently under-counts
  tokens, risking budget overruns.  Dropping .html/.htm from CODE_EXTENSIONS
  lets them fall through to the default `mixed` ratio (3.75), which is more
  accurate.  A comment in extensions.ts records the rationale.

* chore: autofix PR #3 per review comments

---------

Co-authored-by: Saad Khan <skhan8@mail.einstein.yu.edu>
* fix(readJSON): deep-merge defaults to survive partial configs

readJSON's fallback was whole-file-only: it returned the fallback
object only when the file read/parse threw. If .wolf/config.json
exists and parses but is missing a nested section (e.g. an older
config written before dashboard/daemon/cron were added), every
accessor like `config.openwolf.dashboard.port` throws
`TypeError: Cannot read properties of undefined`.

Make readJSON deep-merge the parsed value over the fallback:
loaded values always win, but missing nested keys fall through to
the caller-supplied defaults. Arrays and scalars are replaced
wholesale — only plain objects are merged. File read/parse
failures still return the fallback as-is (unchanged behavior).

Net effect: every existing readJSON call site becomes tolerant of
older/partial configs without any call-site changes. Fixes
`openwolf dashboard` and `openwolf daemon *` crashes when a user's
.wolf/config.json predates a section the current release reads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: autofix PR #6 per review comments

---------

Co-authored-by: Michael Minor <mikeminor.creativetechnologist@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* init/update: tag hook entries with _managedBy: "openwolf"

Adds `_managedBy: "openwolf"` to every hook object in `HOOK_SETTINGS`
so Claude Code's settings round-tripper recognizes them as third-party
managed entries and preserves them through `/effort`, `/config`, and
similar rewrites. Without the tag, entries get silently dropped: a
working OpenWolf install can be de-wired by typing `/effort medium`
once, since Claude Code's merge logic only preserves entries it
recognizes as owned (claude-hooks uses the same field, for example).

Also tightens `replaceOpenWolfHooks` to recognize the new tag in
addition to the legacy `.wolf/hooks/` substring — defensive against
future path schema changes, and keeps the dedupe correct for installs
upgrading from a pre-tag version.

The two changes are minimal and backward-compatible: untagged entries
from older installs still match the substring fallback, so upgrades
clean up cleanly. New installs get tagged from the start.

Fixes cytostack#31.

* fix(hook-settings): address PR #8 review comments

- isOpenWolfHook: check _managedBy as primary signal, path substring as backward-compat fallback for pre-tag installs
- Add comment documenting that _managedBy is empirically observed passthrough, not a guaranteed Claude Code field (Warning #2)
- Add comment in replaceOpenWolfHooks documenting co-location assumption: one inner hook per outer entry unsupported (Warning #1)
- Reformat HOOK_SETTINGS to multi-line expanded style, respects 80-char line length rule (Info #4)
- Code duplication between init.ts and update.ts already resolved by prior extraction to hook-settings.ts (Info #3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: ManniX-ITA <20623405+mann1x@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: strip \r before parsing anatomy.md lines

On Windows with core.autocrlf=true, files checked out from git have
CRLF line endings. The parseAnatomy regex uses $ anchors that fail
to match when \r precedes \n, causing all entries to silently drop.

This results in anatomy.md being rewritten with empty sections on
every post-write hook invocation, losing all tracked file entries.

Fix: strip trailing \r from each line before regex matching in all
three copies of parseAnatomy (hooks/shared, scanner, dashboard).

* chore: autofix PR #4 per review comments

---------

Co-authored-by: Ferhat Şener <ruless@gmail.com>
* fix: use safe config access with defaults in CLI and daemon

readJSON's fallback only applies when the file fails to read/parse.
If .wolf/config.json exists but is missing a nested section (e.g. an
older config written before dashboard/daemon/cron keys were added),
every accessor like `config.openwolf.dashboard.port` throws
`TypeError: Cannot read properties of undefined`.

Replace the 8 unsafe accessors with optional chaining + nullish
coalescing to sensible defaults, matching the pattern already used
in src/cli/designqc-cmd.ts:33.

Fixes `openwolf dashboard` and `openwolf daemon *` crashes when
config.json predates a section the current release reads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-review): CR-01/WR-02 full exclude_patterns fallback with shared constants

Add DEFAULT_EXCLUDE_PATTERNS (19 patterns, matching config.json template) and
DEFAULT_MAX_FILES constants to anatomy-scanner.ts. Replace the truncated 5-pattern
inline list in both the readJSON fallback and the ?? expression. Make WolfConfig
fields optional to match runtime reality.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(pr-review): WR-01/WR-03 make WolfConfig interfaces match optional-access reality

Mark all nested WolfConfig fields as optional (?:) across wolf-daemon.ts,
dashboard.ts, and cron-cmd.ts. TypeScript will now warn if any future code
accesses these fields without ?. or null-coalescing, preventing recurrence of
the crash this PR originally fixed.

Also add clarifying comment to the cron.enabled ?? true guard in wolf-daemon.ts
explaining why absent key defaults to enabled (matches template default).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: autofix PR #5 per review comments

* fix(daemon): add bind to WolfConfig type and use safe access

Add `bind?: string` to the dashboard interface in WolfConfig so the
property is recognized by TypeScript, and use optional chaining when
reading it to satisfy strict null checks introduced after merging develop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Michael Minor <mikeminor.creativetechnologist@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* security: patch command injection, path traversal, and unauthorized access

- Fix critical command injection in CLI by using execFileSync
- Bind dashboard to localhost and add token-based authentication
- Prevent path traversal in CronEngine via path resolution validation
- Mitigate DoS in file watcher with 1MB broadcast limit
- Add security test harness using node:test

* fix: WR-03 remove unused spawnSync import from daemon-cmd.ts

* fix: BL-01/WR-01/WR-02 fix auth middleware order and token file security

- Move express.static before auth middleware so dashboard assets load
  without a token (static files contain no sensitive data)
- Restrict auth middleware to /api/ routes only (BL-01)
- Add mkdirSync before writeFileSync to prevent ENOENT on cold init (WR-01)
- Write daemon-token.tmp with mode 0o600 to prevent world-readable token (WR-02)

* fix: WR-05 normalize paths to lowercase before traversal check

startsWith comparison was case-sensitive and bypassable on macOS
(APFS/HFS+ case-insensitive) and Windows filesystems.

* fix: WR-06 document token URL exposure with TODO for sessionStorage migration

* fix: WR-07/WR-08 remove AI-generated artifacts and gitignore them

directives.md was auto-generated by Gemini CLI on a different developer's
machine (/home/rwolf/) and leaked their home path, OS, and workspace.
summary.md was a Gemini CLI session summary. Neither belongs in source
control. Both added to .gitignore to prevent recurrence.

---------

Co-authored-by: river wolf <riverwolf67@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Use safeCopyFile shim to avoid copy_file_range EPERM on WSL2 + EFS

fs.copyFileSync (and the underlying libuv uv_fs_copyfile) uses Linux's
copy_file_range syscall as a fast path. That syscall fails with EPERM
when the destination is on a Windows volume mounted via WSL2 9P AND
the destination directory has the EFS Encrypted attribute. This makes
`openwolf init` and `openwolf update` unusable on any Windows-EFS path
opened from WSL.

Plain read+write avoids copy_file_range and works in all cases. Add
safeCopyFile to utils/fs-safe.ts (matching the existing safe-write
pattern) and replace all 12 fs.copyFileSync call sites in cli/init.ts
and cli/update.ts.

Reproduction:
  1. On Windows, mark a directory EFS-encrypted (cipher /e <dir>)
  2. Open WSL2, cd into the directory via /mnt/<drive>
  3. openwolf init
  -> EPERM at fs.copyFileSync of OPENWOLF.md

After this change: init and update succeed; new files inherit the
parent's Encrypted attribute correctly via standard NTFS inheritance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: stage pnpm-lock.yaml from merge with main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: make safeCopyFile atomic and document contract differences (CR-01, WR-01, WR-02)

- Wrap write in temp+rename pattern (matches writeJSON/writeText) so an interrupted
  copy leaves no partially-written hook script at the destination (CR-01)
- On write/rename failure, unlink the tmp file before re-throwing so no orphan is
  left behind
- Add explanatory comment to the empty chmod catch block (WR-01)
- Update docstring to note the two semantic differences from fs.copyFileSync:
  read+write bypass and silent dest-dir creation (WR-02)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Tony Cirigliano <tony@cirigliano.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(update): preserve config.json across `openwolf update` (fixes cytostack#37)

`update.ts` was unconditionally copying `src/templates/config.json`
over every registered project's `.wolf/config.json`. That file is
not template content — it carries each project's daemon port,
dashboard port, scan intervals, and exclude patterns. Overwriting it
across all projects in one shot resets every registered project to
the same defaults (`daemon=18790`, `dashboard=18791`), so only the
first daemon to start binds and the rest crash-loop on `EADDRINUSE`.

Move `config.json` from `ALWAYS_OVERWRITE` to `USER_DATA_FILES`. It
is still included in `BACKUP_FILES` via the spread, so `openwolf
restore` can still recover it.

Reproduced on Linux (PVE 7) and Windows 11. After local apply, ten
projects on one host kept their unique ports through a simulated
update; pre-patch, all ten fell to defaults inside one second.

No behavior change for `OPENWOLF.md` or `reframe-frameworks.md` —
those remain protocol-doc overwrites.

* fix: address PR #12 review comments in update.ts

- Fix stale comment on line 177: remove config.json reference since it
  is no longer in ALWAYS_OVERWRITE (now lists OPENWOLF.md and
  reframe-frameworks.md accurately)
- Add "create if missing" seed block for config.json after the template
  copy loop so older projects that predate the config.json template
  addition receive the file on their next `openwolf update`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: ManniX-ITA <20623405+mann1x@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add STATUS.md as session handoff document

Adds a `.wolf/STATUS.md` file that acts as the single source of truth for
resuming work across sessions. Reduces session-resume cost from ~6 file
reads (memory.md + cerebrum.md + plan files + code) to a single read.

Changes:
- New template `src/templates/STATUS.md` with sections for ✅ Concluído,
  🚀 Próxima fase, 📁 Arquitetura ativa, ⚠️ Pendências, 🔧 Comandos.
- `src/templates/OPENWOLF.md`: new "STATUS.md — Single Source of Truth"
  section at top + bumped step in Session End to update STATUS.md first.
- `src/templates/claude-rules-openwolf.md`: rules to read STATUS.md first
  and to update it when a quest finishes or before /clear.
- `src/cli/init.ts`: register STATUS.md in CREATE_IF_MISSING and add an
  embedded fallback in generateTemplate.
- `src/hooks/stop.ts`: new `checkStatusFreshness()` — emits a stderr
  reminder when 3+ code writes happen but STATUS.md mtime predates the
  session start (or when STATUS.md is missing entirely).

Result: at the end of a quest the AI moves done items to ✅ and writes
the next quest in 🚀, so /clear is cheap and resumes in 1 read.

* fix: translate Portuguese STATUS.md headers, add seedStatus(), fix stop-hook path/catch bugs

- CR-01: add seedStatus() to init.ts — substitutes {{PROJECT_NAME}} and {{DATE}}
  placeholders immediately after STATUS.md is written on fresh init
- CR-02: translate all Portuguese section headers in STATUS.md to English
  (Concluído→Done, Próxima fase→Next Phase, Arquitetura ativa→Active Architecture,
  Pendências externas→External Dependencies, etc.) and update matching
  references in OPENWOLF.md instructions
- WR-01: fix path-separator assumption in checkStatusFreshness — add
  path.sep-based check alongside the Unix slash so .wolf/ writes are
  correctly excluded on Windows
- WR-03: narrow bare catch{} in checkStatusFreshness to ENOENT only;
  permission errors and other fs failures now pass through silently
  instead of emitting a misleading "STATUS.md missing" nudge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: techopcgamer <techopcgamer@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds .planning/config.json with model_profile set to 'inherit'.
This configuration file tracks project-level settings for GSD workflows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fixed silent failures in fs-safe.ts with proper error logging
- Added error logging to all empty catch blocks in wolf-daemon.ts
- Enhanced error messages in dashboard.ts browser opening
- Added 8 new integration tests for security-critical code paths
- All 54 tests passing, build successful

Addresses critical issues identified in PR #15 review:
- Silent failures in error handling (Rating: 9)
- Missing integration tests for security (Rating: 9)
- Inadequate error logging in daemon (Rating: 8)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The security test file was using Node's built-in node:test module
instead of vitest's API, causing test failures. This commit converts
all test assertions to use vitest's API:
- test() → it()
- assert.*() → expect().*()
- before() → beforeAll()
- after() → afterAll()

All 62 tests now pass successfully.

Fixes: test failures in PR #15
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Critical fixes:
- wolf-daemon.ts: pass bind address to app.listen (was always binding 0.0.0.0)
- wolf-daemon.ts: add server.on('error') handler for clean EADDRINUSE messages
- wolf-daemon.ts: wrap startup token write in try/catch with process.exit(1)
- dashboard.ts + main.tsx + useWolfData.ts: strip ?token= from URL via
  history.replaceState on first load; store in sessionStorage; send via
  X-Api-Token header on all subsequent API calls
- init.ts: move config.json from ALWAYS_OVERWRITE to CREATE_IF_MISSING so
  user port assignments are preserved on upgrade
- fs-safe.ts: differentiate ENOENT (silent) from parse/permission errors
  (logged to stderr) in readJSON
- cron-engine.ts: log retry failures instead of swallowing with catch(() => {})

Important fixes:
- cron-engine.ts: log non-ENOENT errors reading AI task context files
- file-watcher.ts: log broadcast failures at debug level (was empty catch)
- daemon-cmd.ts: show actual error message in daemonStart catch
- daemon-cmd.ts: log non-ENOENT errors from findPidOnPort
- daemon-cmd.ts: distinguish PM2 "not found" from permission errors in
  daemonStop and daemonRestart catch blocks
- dashboard.ts: add error and exit event handlers on forked daemon child

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C6: readText now differentiates ENOENT (expected - file not found) from
permission/I-O errors (unexpected - logs to stderr), matching readJSON.

C5: safeCopyFile's two bare catch{} blocks now log unexpected errors:
- temp-file cleanup failure logs the leaked .tmp path
- chmod failure logs non-EPERM/ENOTSUP codes (Windows/WSL2 expected)
…ding

fs.copyFileSync uses copy_file_range on Linux which fails with EPERM on
EFS/WSL2 mounts. safeCopyFile was added to fix exactly this scenario and
was already used everywhere else in update.ts — this one call was missed.
When upgrading from a version that predated STATUS.md, the file is newly
written into CREATE_IF_MISSING but the seedStatus() call was inside
if (!isUpgrade) — leaving raw {{PROJECT_NAME}}/{{DATE}} placeholders in the
file. Track newly-created files and seed STATUS.md whenever it is first
written, regardless of upgrade vs fresh-init path.
url contains ?token=<hex> for the initial dashboard page load. Passing it
directly to logger.error wrote the live session token to dashboard.log,
where anyone with log read access could use it to bypass auth. Strip the
query string before logging; the user-facing console.log(URL) is
intentional (they need it to open the browser manually).
C1: verifyClient now validates ?token= from the WS upgrade URL in addition
to origin, using safeCompareToken. Unauthenticated WS connections are
rejected before the socket is established.

I2: request_full_state replies only to the requesting sender (sender.send)
instead of broadcasting all .wolf/ file contents to every connected client.

I3: shutdown() now unlinks daemon-token.tmp. server.on('error') also unlinks
it so a bind failure (EADDRINUSE) doesn't leave a stale token file.

I4: API auth middleware accepts x-api-token header only. ?token= query param
is removed — it exposed the token in browser history and Referer headers.

I6: Token comparison uses crypto.timingSafeEqual via safeCompareToken helper
instead of !== to prevent timing side-channel attacks.

S4: WebSocketServer maxPayload set to 4 MB to cap memory use from large
JSON messages.
wolf-client.ts: constructor accepts optional token parameter and appends
?token=<value> to the WS upgrade URL (required by C1 fix in wolf-daemon.ts).

useWolfData.ts: reads wolf_token from sessionStorage and passes it to
WolfClient so the WebSocket handshake is authenticated.
I7: killPid bare catch{} now logs EPERM separately (needs elevated privileges)
vs other errors, rather than silently returning false with no diagnostics.

S3: daemonLogs catch now includes the error message from pm2 instead of
discarding it — matches the error-handling upgrade applied elsewhere in this PR.
…checkCerebrumFreshness

I8: shared.ts readMarkdown now logs non-ENOENT errors to stderr instead of
silently returning empty string. Identical fix to readText in fs-safe.ts (C6).

I9: stop.ts checkCerebrumFreshness now distinguishes ENOENT (cerebrum.md not
yet created — expected) from EACCES/I/O errors (unexpected). Mirrors the
pattern checkStatusFreshness already uses in the same file.
…rtifacts

These files are generated by the GSD code-review workflow and should not be
committed to the repository. Also add a *-REVIEW.md and *-REVIEW-FIX.md
pattern to catch phase-prefixed variants from planning workflows.
…n modules

C7: All four test suites now import production code (readJSON, readText,
safeCopyFile, execFileSync) rather than testing inline lambdas. Removing the
production guard now causes the corresponding test to fail.

I10: Added new test suites covering:
- readJSON: ENOENT silent, non-ENOENT logged, malformed JSON logged
- readText: ENOENT silent, reads existing files correctly
- safeCopyFile: correct copy, dest-dir creation, no stale .tmp on success
  or failure (atomicity/cleanup)
- Auth token comparison: timingSafeEqual logic from wolf-daemon.ts (correct
  token accepted, wrong token rejected, short/empty tokens don't throw)
- Path traversal: ../ and absolute-path escapes are blocked, safe paths pass
- File watcher DoS cap: real filesystem stat mirrors the 1 MB guard
Port: develop branch — 10 ported PRs
- Add PROJECT.md from prd.md (11 items, P0/P1/P2 priorities)
- Add config.json (yolo mode, coarse granularity, parallel execution)
- STACK.md: WebSocket auth (cookie-based), module splitting, Vitest, pnpm clean patterns
- FEATURES.md: 11 items characterized as table stakes/differentiators
- ARCHITECTURE.md: 4-phase build order, cookie-based WS auth design
- PITFALLS.md: 11 pitfalls with prevention strategies and phase mapping
- SUMMARY.md: Executive synthesis with phase recommendations

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 17 requirements across 6 categories (Session, Auth, Hook, Scanner, Test, Clean)
- All 11 PRD items traced to requirements with REQ-IDs
- Full traceability matrix with phase mapping
- Out of scope exclusions documented

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
briansumma and others added 28 commits June 7, 2026 11:45
Remove vestigial HOOK_FILES constant from hook-settings.ts and
rewrite tests to validate dynamic discovery via getHookFileNames().
- Removed vestigial HOOK_FILES constant from hook-settings.ts.
- Updated init.test.ts to test getHookFileNames() dynamic discovery.
- Restored .wolf/hooks/ fallback check in isOpenWolfHook for compatibility.
- Updated phase documentation and config.
Remove phase review files (.planning/phases/*/REVIEW*.md) that were part of the v1.0 milestone completion and have been superseded by updated STATE.md and retrospective documentation.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@briansumma briansumma marked this pull request as draft June 7, 2026 23:55
@briansumma briansumma closed this Jun 7, 2026
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.

1 participant