From 8fd57291a700f7927be59c98770227ffb4a599ab Mon Sep 17 00:00:00 2001 From: Dan Stolts Date: Sun, 26 Apr 2026 03:20:47 -0400 Subject: [PATCH] fix(docs+security): weekly scan W17 -- stale counts + dashboard security ## What - CLAUDE.md: correct hook count (4 -> 10 scripts / 9 events) and command count (16 -> 17, add verify) - docs/commands-reference.md: add missing /help and /verify entries; update lead line from "15 commands" to "17 commands" - templates/commands/verify.md: sync expected counts (commands 15->17, hook scripts 5->10, hook events 4->9) - templates/dashboard/server.js: fix 4 security issues from sys-security static review (2026-04-11) ## Why Weekly W17 scan surfaced #37 (stale CLAUDE.md counts) and #43 (missing /help + /verify in commands-reference.md) still open from prior weeks. Security issues #32-#35 in dashboard/server.js have been open 15 days without fix -- P1 path traversal ships in a published template. ## Security fixes (closes #32, #33, #34, #35) - #32 P1: path traversal in getSessionContent -- added path.resolve + boundary check before readFileSync - #33 P2: CORS wildcard + 0.0.0.0 bind -- CORS now reflects localhost origins only; server.listen binds 127.0.0.1 - #34 P3: unauthenticated POST /api/settings -- added schema validation (known keys only, type checks) - #35 P4: child_process.exec shell string -- replaced exec with spawn(args[]) to eliminate shell injection surface ## Risk Dashboard server.js changes are localhost-only dev tool. CORS and bind changes are behavior changes: any caller that was relying on external access will now be rejected -- intended, as this is a local tool. Schema validation on /api/settings is additive (rejects unknown keys). ## Test plan - [x] Manual: node templates/dashboard/server.js -- verify server starts on localhost:9847 - [x] Manual: curl http://127.0.0.1:9847/api/sessions/..%2Fetc%2Fpasswd -- expect 404 (path traversal blocked) - [x] Manual: verify /help and /verify appear in /help output and commands-reference rendering --- CLAUDE.md | 6 ++-- docs/commands-reference.md | 32 +++++++++++++++++++- templates/commands/verify.md | 14 ++++----- templates/dashboard/server.js | 55 ++++++++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 19 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f78488c..d5d56ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ JIT memory management framework for Claude Code -- persistent context across ses ## Key Paths | Path | Purpose | |------|---------| -| templates/commands/ | Slash command templates (22 files: 16 commands + 5 shortcuts + test-tools) | +| templates/commands/ | Slash command templates (22 files: 17 commands + 5 shortcuts) | | templates/hooks/ | Hook script templates (4 hooks) | | templates/engrams/ | Engram templates + examples | | templates/rules/ | Path-scoped rule templates | @@ -24,9 +24,9 @@ JIT memory management framework for Claude Code -- persistent context across ses | install.sh / install.ps1 | Installation scripts (workspace/project/user modes) | ## Key Components -- 16 commands: help, session, sessions, divergent, learn, health, gitstatus, diff, enterprise, audit, bundle, onboard, orchestrate, conversation-log, test-tools, schedule +- 17 commands: help, session, sessions, divergent, learn, health, gitstatus, diff, enterprise, audit, bundle, onboard, orchestrate, conversation-log, test-tools, schedule, verify - 5 shortcuts: save, load, pulse, status, dashboard (delegate to session/sessions based on preference) -- 4 hooks: PreCompact save, SessionStart recovery, Branch protection, SessionEnd auto-save +- 10 hook scripts / 9 hook events: PreCompact save, SessionStart (write-id, post-clear, recovery, scheduled-agents), PreToolUse (branch-protection, pre-agent-register), PostToolUse (heartbeat, post-agent-complete), SessionEnd auto-save - Engram system: per-project deep context files (50-150 lines each) - Bundle system: domain knowledge files loaded on-demand via routing weights - Context manifest: bundle index and routing weight definitions diff --git a/docs/commands-reference.md b/docs/commands-reference.md index a938af1..cbd4cec 100644 --- a/docs/commands-reference.md +++ b/docs/commands-reference.md @@ -1,6 +1,6 @@ # Commands Reference -JitNeuro ships 15 commands and 5 shortcuts organized into 7 categories. All commands are read-only unless explicitly noted. +JitNeuro ships 17 commands and 5 shortcuts organized into 7 categories. All commands are read-only unless explicitly noted. --- @@ -283,6 +283,36 @@ Examples: --- +## Setup and Verification + +### /help +Display JitNeuro quick reference. Zero token cost -- reads and displays a static help file verbatim. + +- **Arguments:** None +- **Source:** Reads `.claude/help.md` from the install root; falls back to the jitneuro templates `help.md` + +Example: +``` +/help +``` +Displays the quick-reference card for all commands, shortcuts, and common workflows. Offers to open the file in the editor. + +--- + +### /verify +Post-install health check. Reads the install root and validates all 9 framework components. Read-only -- does not modify any files. + +- **Arguments:** None +- **Checks:** install version, commands directory, hook scripts, hooks config, hook paths, hook event names, bundles, engrams, context manifest + +Example: +``` +/verify +``` +Returns a GREEN/YELLOW/RED table for each component with recommended remediation steps for any failures. + +--- + ## Shortcuts These delegate to `/session` or `/sessions` based on the `shortcut_scope` preference in `.claude/session-state/.preferences`. Default scope: `session` (current). diff --git a/templates/commands/verify.md b/templates/commands/verify.md index 7f99256..b30f463 100644 --- a/templates/commands/verify.md +++ b/templates/commands/verify.md @@ -17,9 +17,9 @@ When invoked as `/verify`: | # | Component | Check | GREEN | YELLOW | RED | |---|-----------|-------|-------|--------|-----| | 1 | Install version | Read version from .claude/jitneuro.json | Version found | Pre-versioned install (no jitneuro.json) | Not installed | - | 2 | Commands | Scan .claude/commands/*.md | All 15 commands present | Some commands missing | commands/ dir missing | - | 3 | Hook scripts | Check .claude/hooks/*.sh exist | All 5 scripts present | Some missing | hooks/ dir missing | - | 4 | Hooks config | Read settings.local.json, verify hooks section | All 4 hook events configured | Some events missing | No hooks config | + | 2 | Commands | Scan .claude/commands/*.md | All 17 commands present | Some commands missing | commands/ dir missing | + | 3 | Hook scripts | Check .claude/hooks/*.sh exist | All 10 scripts present | Some missing | hooks/ dir missing | + | 4 | Hooks config | Read settings.local.json, verify hooks section | All 9 hook events configured | Some events missing | No hooks config | | 5 | Hook paths | Each hook command path in settings.local.json points to existing file | All paths valid | -- | Script file not found | | 6 | Hook events | Event names match Claude Code events (PreCompact, SessionStart, PreToolUse, SessionEnd) | All valid | Unknown event name | -- | | 7 | Bundles | Check .claude/bundles/ has files | Has bundles | Only example.md | Empty | @@ -42,9 +42,9 @@ When invoked as `/verify`: Install: .claude/ at [path relative to workspace] [1] Install version GREEN v0.1.2 - [2] Commands GREEN 15/15 installed -[3] Hook scripts GREEN 5/5 present -[4] Hooks config GREEN 4 event types configured (SessionStart has 2 hooks) + [2] Commands GREEN 17/17 installed +[3] Hook scripts GREEN 10/10 present +[4] Hooks config GREEN 9 hook events configured (SessionStart has 4 hooks) [5] Hook paths GREEN All paths valid [6] Hook events GREEN All event names valid [7] Bundles GREEN 3 bundles @@ -73,5 +73,5 @@ When invoked as `/verify`: ## Important - Use relative paths in output (not full system paths with username) - This command is READ-ONLY -- never modify files -- If settings.local.json has extra hooks beyond JitNeuro's 5 scripts (4 event types), that's fine -- only check for JitNeuro's hooks +- If settings.local.json has extra hooks beyond JitNeuro's 10 scripts (9 hook events), that's fine -- only check for JitNeuro's hooks - Do not fail on extras, only on missing components diff --git a/templates/dashboard/server.js b/templates/dashboard/server.js index 6d61572..a9dcd81 100644 --- a/templates/dashboard/server.js +++ b/templates/dashboard/server.js @@ -8,7 +8,7 @@ const http = require('http'); const fs = require('fs'); const path = require('path'); const os = require('os'); -const { exec } = require('child_process'); +const { spawn } = require('child_process'); // -- Configuration -- @@ -306,7 +306,12 @@ function getSessions() { } function getSessionContent(name) { - var filePath = path.join(SESSIONS_DIR, name + '.md'); + // Security: reject names with path separators or traversal sequences + if (!name || /[/\\]/.test(name) || name.indexOf('..') !== -1) return null; + var filePath = path.resolve(SESSIONS_DIR, name + '.md'); + // Security: ensure resolved path stays within SESSIONS_DIR + var safeDir = path.resolve(SESSIONS_DIR) + path.sep; + if (filePath.indexOf(safeDir) !== 0) return null; try { return fs.readFileSync(filePath, 'utf8'); } catch (e) { return null; } } @@ -341,16 +346,30 @@ function readBody(req, cb) { // -- Browser auto-open -- function openBrowser(url) { - var cmd = process.platform === 'win32' ? 'cmd /c start "" "' + url + '"' - : process.platform === 'darwin' ? 'open "' + url + '"' - : 'xdg-open "' + url + '"'; - exec(cmd, function() {}); + // Security: use spawn with arg array to avoid shell injection -- URL is controlled + // but defensive practice is to never pass untrusted values through a shell string + var args; + if (process.platform === 'win32') { + args = ['cmd', ['/c', 'start', '', url], { shell: false }]; + } else if (process.platform === 'darwin') { + args = ['open', [url], { shell: false }]; + } else { + args = ['xdg-open', [url], { shell: false }]; + } + try { spawn(args[0], args[1], Object.assign({ detached: true, stdio: 'ignore' }, args[2])).unref(); } + catch (e) {} } // -- HTTP server -- +var LOCALHOST_ORIGINS = ['http://localhost:' + PORT, 'http://127.0.0.1:' + PORT]; + var server = http.createServer(function(req, res) { - res.setHeader('Access-Control-Allow-Origin', '*'); + // Security: restrict CORS to localhost origins only -- dashboard is a local tool + var origin = req.headers.origin || ''; + if (LOCALHOST_ORIGINS.indexOf(origin) !== -1) { + res.setHeader('Access-Control-Allow-Origin', origin); + } res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); @@ -382,6 +401,25 @@ var server = http.createServer(function(req, res) { readBody(req, function(body) { try { var data = JSON.parse(body); + // Security: validate schema -- only allow known top-level keys with expected types + if (typeof data !== 'object' || data === null || Array.isArray(data)) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Settings must be a JSON object'); + return; + } + var allowed = ['dashboard']; + for (var k in data) { + if (allowed.indexOf(k) === -1) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Unknown settings key: ' + k); + return; + } + } + if (data.dashboard !== undefined && (typeof data.dashboard !== 'object' || Array.isArray(data.dashboard))) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('settings.dashboard must be an object'); + return; + } if (saveSettings(data)) { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(JSON.stringify(getSettings())); @@ -545,7 +583,8 @@ var server = http.createServer(function(req, res) { res.end('Not found'); }); -server.listen(PORT, function() { +// Security: bind to loopback only -- dashboard is a local developer tool +server.listen(PORT, '127.0.0.1', function() { var url = 'http://localhost:' + PORT; console.log(''); console.log(' JitNeuro Agent Dashboard');