Skip to content

dev → main#87

Draft
github-actions[bot] wants to merge 32 commits intomainfrom
dev
Draft

dev → main#87
github-actions[bot] wants to merge 32 commits intomainfrom
dev

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

Changes on dev

Tests: ✗ failing


PR opened automatically by node9 CI agent

github-actions bot and others added 28 commits April 9, 2026 18:38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Automatically hashes MCP server tool definitions (name, description,
inputSchema) on first connection and blocks if they change on subsequent
connections. This defends against "rug pull" attacks where a trusted MCP
server silently modifies tool descriptions to inject malicious instructions.

- Replace child.stdout.pipe() with readline interceptor in MCP gateway
  to inspect tools/list responses before forwarding to the agent
- SHA-256 hash of canonicalized tool definitions, sorted by name
- Pin storage at ~/.node9/mcp-pins.json (atomic writes, mode 0o600)
- On mismatch: return JSON-RPC -32000 error with clear remediation steps
- CLI: node9 mcp pin list/update/reset for pin management
- 20 unit tests (hashing, storage, pin lifecycle)
- 5 integration tests (first pin, match, rug pull block, re-pin, transparency)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… review-and-approve update

Addresses adversarial review findings:

1. Pin file reads fail closed: corrupt/unreadable pin files now throw
   instead of silently returning empty (which re-trusted the upstream).
   Only ENOENT is treated as "no pin exists."

2. Session quarantine: tools/call is blocked until a tools/list pin check
   passes. Mismatch or corrupt pin state permanently quarantines the
   session — no tool calls forwarded until the operator resolves it.

3. Pin update is now a review flow: `mcp pin update` spawns the upstream,
   fetches current tools, diffs old vs new definitions, and requires
   explicit operator confirmation before re-pinning.

4. README updated with MCP tool pinning section explaining the rug pull
   defense and CLI commands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert mcp pin update to simple delete-and-repin. The review-and-approve
flow (upstream fetch, diff display, confirmation prompt) adds ~170 lines
and is a UX enhancement — not a security fix. Moving to a follow-up PR
to keep this one focused on the two security hardening changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- pin list: uses readMcpPinsSafe() to show friendly error on corrupt file
- pin update: catches corrupt file with recovery instructions
- pin reset: works on corrupt files (clears without reading first)
- README: fix stale comment about pin update reviewing diffs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
node9_rule_add only accepts block/review verdicts — allow is explicitly
rejected at both schema and runtime levels to prevent AI from bypassing
node9 security policies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chained commands like `git add . && git commit && git push` were
bypassing git push/destructive/force-push rules because ^ only matched
when git was at the start of the command. Replaced with \b word boundary.

Same fix applied to review-sudo.

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

1. mcp-pin.unit.test.ts Windows home dir mock:
   Set USERPROFILE alongside HOME — os.homedir() on Windows reads
   USERPROFILE, not HOME, so the temp dir mock was ignored and all
   pin file operations read from the real home directory.
   Skip 0o600 permission test on Windows (Unix file modes not supported).

2. mcp-gateway/index.ts ERR_USE_AFTER_CLOSE crash:
   When drainPendingToolCalls() replays queued tool calls after stdin
   has already closed, agentIn is in a closed state. Calling pause()
   on a closed readline interface throws ERR_USE_AFTER_CLOSE.
   Guard with !deferredStdinEnd — if stdin already closed, the stream
   is done and there is nothing to pause.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: andreykh89 <andreykh89@users.noreply.github.com>
- Add optional description field to SmartRule interface
- Pass ruleDescription through policy → orchestrator → check.ts
- Show description in /dev/tty review/block card for human-readable context
- Add descriptions to all DEFAULT_CONFIG built-in rules and ADVISORY_SMART_RULES
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LICENSE file and package.json already declared Apache-2.0; the README
badge was incorrect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- README: adds Flight Recorder & HUD section showing the 3-line statusline
  (security state, context/rate limits, environment counts)
- config-schema: adds `description` field to SmartRuleSchema alongside `reason`
- policy: falls back to `reason` when `description` is absent so friendly
  messages always render in the approval popup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
node9 init is the correct command for initial setup; node9 setup is
the targeted per-agent alias (setup claude, setup hud, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects when an AI agent (Claude, Codex, Cursor, Gemini CLI, etc.) gets
stuck calling the same tool with identical arguments repeatedly. When
the same tool+argsHash appears at or above threshold within the time
window, the call is hard-blocked with a clear message telling the agent
to try a different approach.

Implementation:
- File-based sliding window at ~/.node9/loop-state.json
- Atomic write via tmp+rename (pattern from auth/state.ts)
- Default threshold: 3 calls within 120s window
- Hard cap at 500 entries + time-window filter on every write
- Fails open if fs unavailable (mocked tests, permissions, etc.)
- Config under policy.loopDetection.{enabled,threshold,windowSeconds}

Default on, zero-config. Every non-ignored tool call flows through this
check before trust sessions and policy fast paths, so loops are caught
even for tools with persistent allow decisions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Default threshold raised from 3 to 5 — 3 identical calls in 2 minutes
  is normal during iterative debugging (e.g. running npm test repeatedly);
  5 is a clearer signal the agent is actually stuck
- Block reason reworded from "Loop detected — try a different approach"
  to a reflective question ("It looks like you've called X 5 times...
  Are you stuck? Step back and reconsider...") — invites Claude to reason
  about what it's trying to accomplish rather than blindly pivoting
- Integration tests updated to match new threshold and message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

🔍 node9 Code Review

Review of AI Changes

The AI applied no changes (empty diff).

There is nothing to review — the AI produced an empty fix, meaning it made no modifications to the codebase.

The test result is also blank (no output after the fix), which is uninformative since the original test command was rejected for containing unsafe characters (&&), not because of a code issue.

What should actually be reviewed (the original diff):

Since the AI did nothing, the original code changes themselves remain unreviewed by the AI. Notable concerns in the original diff worth flagging:

  1. Race condition in recordAndCheck: The loop detector's state is implicitly shared/global. If the underlying implementation uses a module-level store without locking, concurrent auth requests could produce inconsistent counts — though this is likely acceptable in a single-process Node.js context.

  2. windowSeconds * 1000 unit conversion: Assumes windowSeconds is in seconds and recordAndCheck expects milliseconds — this needs to match the loop-detector.ts implementation (not shown). A mismatch would silently break detection.

  3. Optional chaining gap: config.policy.loopDetection is optional in the schema, but accessed without a null check (const ld = config.policy.loopDetection; if (ld.enabled)). If loopDetection is undefined, this throws at runtime.

Conclusion: The AI failed to produce any fix. The original code has a concrete null-dereference bug on config.policy.loopDetection that should have been addressed.


Automated review by node9

@github-actions
Copy link
Copy Markdown
Contributor Author

🔒 node9 Security Review

Security Findings

  • [MEDIUM] loop-detector.ts (inferred) / recordAndCheck — The state file path is constructed from process.env.HOME (or USERPROFILE on Windows) without any validation or normalization: path.join(tmpHome, '.node9', 'loop-state.json'). If an attacker can control the HOME environment variable (e.g., in a misconfigured setuid/setgid scenario, container escape, or when the process inherits a manipulated environment), this becomes a path traversal sink. A value like HOME=/../../../../tmp/evil would cause state files to be written outside the intended directory. The test harness itself demonstrates this: it freely sets process.env.HOME to control the write location, confirming there is no validation of the resolved path.

  • [MEDIUM] loop-detector.ts (inferred) / recordAndCheck — The state file is read and parsed as JSON from ~/.node9/loop-state.json. The test "recovers from corrupt state file" confirms the file is parsed with JSON.parse and the result is used as application state. If the state file can be written by another local user or process (e.g., world-writable /tmp when HOME is a tmpdir, or a symlink attack on ~/.node9/), a malicious JSON payload could manipulate loop-detection logic — e.g., injecting crafted entries that prevent loop detection from triggering, allowing an infinite tool-call loop to proceed unchecked. This is a logic-integrity issue via an untrusted deserialization path.

  • [LOW] src/__tests__/loop-detector.spec.ts / beforeEach — Temporary directories are created under os.tmpdir() with a predictable prefix (loop-test-). While mkdtempSync adds random characters, the prefix is guessable. In a shared /tmp environment a local attacker could pre-create a symlink at the expected location. More concretely: the test sets process.env.HOME to this tmpdir, and any code under test that uses HOME to construct file paths will write there. If a test fails mid-run without afterEach cleanup, residual state files containing tool arguments (potentially sensitive command strings) persist in /tmp.

  • [LOW] computeArgsHash (inferred) — The hash is only 16 hex characters (64-bit output). For a loop-detection mechanism, collision resistance matters: two distinct tool calls that happen to collide on their hash would be incorrectly identified as the same repeated call (false positive loop detection) or, worse, a deliberately crafted command could collide with a benign one to suppress detection. The test "produces different hashes for different args" only checks two specific values — it does not validate the hash function's collision resistance. If the underlying hash is a truncated weak function (e.g., truncated MD5/FNV), birthday attacks at scale become feasible.


Automated security review by node9

Adds `node9 report` command that reads ~/.node9/audit.log and renders
a terminal summary for a chosen time period (today/7d/30d/month):

- Overview: allowed / blocked / DLP hits / block rate
- Top Tools: bar chart of most-used tools with blocked count
- Top Blocks: which rules fired most (smart-rule-block, dlp, timeout...)
- Agent breakdown: which AI agents generated the activity
- Daily Activity: per-day call volume with blocked count

No new dependencies — uses chalk (already in tree).
Skips PostToolUse entries (source: post-hook) to avoid double-counting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

🔍 node9 Code Review

Review of AI Changes

The AI applied no changes (empty diff).

There is nothing to review — the AI produced an empty fix, meaning it made no modifications to the codebase. The test results are also blank (tests were not run due to the unsafe command rejection before, and no output is shown after).

Assessment of the original diff being reviewed:

Since the AI made no changes, the original code stands as-is. A few concerns worth noting in the original:

  1. windowSeconds * 1000 unit conversion: The config stores windowSeconds but recordAndCheck receives milliseconds. This is correct only if recordAndCheck expects ms — needs verification against loop-detector.ts (omitted from diff).

  2. Audit skipped for manual mode: appendLocalAudit is only called when !isManual, but the loop-detection block itself runs regardless. This asymmetry seems intentional but should be confirmed.

  3. No changes to fix any identified problem: The AI was presumably asked to fix something but returned an empty diff. If there was a security issue or logic error in the original, it remains unaddressed.

Verdict: The AI failed to produce any fix. Either no fix was needed (in which case the AI should have stated that explicitly), or it silently failed to address the assigned problem.


Automated review by node9

@github-actions
Copy link
Copy Markdown
Contributor Author

🔒 node9 Security Review

Looking at the visible diff, which is test code for a loop detector that reads/writes state files based on HOME/USERPROFILE environment variables:


  • [MEDIUM] loop-detector.ts (inferred): recordAndCheck / state file path construction — The state file path is constructed from process.env.HOME (or USERPROFILE), which is an environment variable that can be controlled by an attacker with local process access. If HOME is set to a path like /etc or contains path traversal components (e.g., /tmp/../../etc), the path.join(HOME, '.node9', 'loop-state.json') construction could write state files to unintended filesystem locations. The test itself demonstrates this pattern explicitly by overriding HOME to a temp directory. While path.join normalizes .. segments, a crafted HOME value pointing to a sensitive directory (e.g., HOME=/etc/cron.d) could cause the state file to be written there if the process has permissions. The state file content is JSON derived from tool names and argument hashes — an attacker controlling HOME and able to pre-stage a malicious loop-state.json could manipulate loop detection logic (suppress or force loop flags), potentially bypassing safety guards.

  • [LOW] loop-detector.ts (inferred): recordAndCheck — The toolName parameter is incorporated into the state file entries (used as part of the key). If toolName is sourced from external/user input (e.g., parsed from a config file or API response — suggested by the omitted src/config/index.ts and src/config-schema.ts files), a malicious tool name containing characters meaningful to JSON or of excessive length could cause state file bloat or, in edge cases, interfere with JSON parsing on subsequent reads. The recovery-from-corrupt-state tests suggest parsing failures are handled gracefully, limiting impact.

  • [LOW] loop-detector.ts (inferred): resetLoopStateresetLoopState() deletes the state file at the HOME-derived path with no authentication or authorization check. If HOME is attacker-controlled and points to a sensitive file path (within the application's write permissions), this becomes an arbitrary file deletion primitive. Severity is low since it requires control over the process environment.


Root cause pattern across all findings: The loop-state file path is entirely derived from process.env.HOME/USERPROFILE with no validation that the resulting path stays within expected boundaries (e.g., no check that it resolves to the actual user home directory, no realpath canonicalization, no allowlist of acceptable base paths). The omitted src/loop-detector.ts source would confirm exact sink usage, but the test structure makes the pattern unambiguous.


Automated security review by node9

Reads ~/.claude/projects/**/*.jsonl at report time to sum real token
usage and display cost in the overview line. Zero impact on the hook
path — purely offline, runs only when `node9 report` is invoked.

Cost is shown as a magenta 💰 label alongside allowed/blocked/DLP stats.
Falls back to – when no JSONL data is found (non-Claude-Code agents).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

🔍 node9 Code Review

Review

The AI made no changes (empty diff).

There is nothing to review — the AI produced an empty fix, meaning it either declined to act or failed to generate output. The original diff remains as-is, unmodified.

If the intent was to validate the original diff, here are the relevant issues in the original code worth flagging:

Security / Logic concerns in the original diff:

  1. recordAndCheck is a global/shared state mechanism — if the loop detector stores state in-process memory, it will be shared across all concurrent authorization requests. This could cause false positives where different agents/sessions calling the same tool legitimately get blocked due to cross-session contamination.

  2. Loop detection bypassed when isManual is true for audit, but not for the block — the audit is skipped for manual calls (if (!isManual) appendLocalAudit(...)), but the block still applies. This is an asymmetry that may cause confusion: manual calls get blocked silently with no audit trail.

  3. ld.windowSeconds * 1000 without null checksloopDetection schema fields are all .optional(), and the config defaults (in the omitted src/config/index.ts) are not visible here. If defaults aren't set properly, accessing ld.threshold or ld.windowSeconds on an object where those are undefined will produce NaN, potentially breaking recordAndCheck in unpredictable ways.

  4. No test results available — tests were blocked before and after, so correctness cannot be confirmed empirically.

Verdict: The AI fix is a no-op. No changes were made, no issues were addressed.


Automated review by node9

@github-actions
Copy link
Copy Markdown
Contributor Author

🔒 node9 Security Review

Security Findings

  • [MEDIUM] src/__tests__/loop-detector.spec.ts:beforeEach — The test creates a temp directory using fs.mkdtempSync(path.join(os.tmpdir(), 'loop-test-')) and then directly constructs the state file path as path.join(tmpHome, '.node9', 'loop-state.json'). While this is test code, the pattern mirrors the production loop-detector.ts (omitted), where HOME/USERPROFILE environment variables are used to build a filesystem path without validation. If an attacker can control HOME or USERPROFILE (e.g., via a compromised environment in a CI/CD context or a parent process), the state file path in production could be redirected to an arbitrary location — enabling a path traversal write to any writable path on the filesystem (e.g., HOME=../../../../tmp/evil). The tests confirm the production code reads HOME/USERPROFILE directly and writes to $HOME/.node9/loop-state.json. No canonicalization or containment check is visible.

  • [MEDIUM] src/loop-detector.ts (omitted, but confirmed by tests) — The recordAndCheck function writes attacker-influenced data to a JSON state file derived from user-controlled environment variables. The toolName and args parameters are serialized and stored. If toolName or args values originate from external/untrusted input (e.g., tool calls from an LLM or API), a malicious payload could be written to the state file. While the file is later read back and parsed with JSON.parse, the tests confirm corrupt/non-array content is tolerated — but a crafted large payload could cause a disk-fill or denial-of-service condition via unbounded state file growth (no maximum entry count or file size limit is enforced, only a time window).

  • [LOW] src/__tests__/loop-detector.spec.ts:beforeEachprocess.env.HOME and process.env.USERPROFILE are mutated globally in the test process without isolation (no vi.stubEnv or similar). If tests run in parallel, a race condition could cause one test's tmpHome to be used by another test's afterEach cleanup, resulting in deletion of a temp directory still in use. This is a test isolation issue, but it could mask real security behaviors (e.g., the "expired entries" timing test is already acknowledged as racy).

  • [LOW] src/__tests__/loop-detector.spec.ts:afterEachfs.rmSync(tmpHome, { recursive: true, force: true }) is called on a path derived from fs.mkdtempSync. If tmpHome were somehow manipulated (symlink injection in os.tmpdir()), this could recursively delete unintended directories. Mitigated in practice by mkdtempSync guarantees, but worth noting if os.tmpdir() itself is attacker-influenced.

Primary concern: The production loop-detector.ts (omitted from diff) uses HOME/USERPROFILE to construct write paths — validate and canonicalize these values against an expected prefix before use.


Automated security review by node9

- Right-align tool and block counts using fixed-width padStart so numbers
  line up regardless of magnitude (e.g. 1,595 vs 35)
- Remove the ✗ blocked-per-tool note that was causing the left column to
  overflow and push the right column out of position
- Fix header spacing using visible string length instead of a magic
  padEnd(COL+10) that compensated for ANSI escape bytes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

🔍 node9 Code Review

Review of AI Changes

The AI applied no changes (empty diff).

There is nothing to review — the AI produced an empty fix in response to the original diff. This means:

  • No security issues were introduced (nothing was changed)
  • No logic errors were introduced (nothing was changed)
  • No correctness problems were introduced (nothing was changed)

However, the original diff itself warrants noting a few concerns since no fixes were applied to address them:

  1. Loop detector state is process-global: recordAndCheck from loop-detector.ts (omitted file) likely maintains global in-memory state. This means loop detection counts are shared across all concurrent authorization requests, potentially causing false positives under concurrent load.

  2. isManual audit gap: The audit call appendLocalAudit(...) is skipped when isManual is true, meaning manual loop-detected blocks go unlogged — this is an auditing blind spot.

  3. Config defaults not visible: The omitted src/config/index.ts means we can't verify that ld.threshold and ld.windowSeconds have safe defaults before use, and ld.windowSeconds * 1000 could produce NaN if undefined slips through.

The AI failed to produce any fix, leaving any issues in the original diff unaddressed.


Automated review by node9

@github-actions
Copy link
Copy Markdown
Contributor Author

🔒 node9 Security Review

Security Findings

  • [MEDIUM] loop-detector.ts (inferred) — The state file path is constructed from process.env.HOME (or process.env.USERPROFILE on Windows) without validation. Both environment variables are user-controlled. If an attacker can set HOME to a path like /etc or a path containing traversal sequences, the application will read/write loop-state.json to an attacker-chosen location. The test harness itself demonstrates this by freely reassigning process.env.HOME to control file placement. The actual loop-detector.ts source is omitted, so the full impact cannot be confirmed, but if path.join(HOME, '.node9', 'loop-state.json') is used without resolving or validating the resulting path against an expected prefix, this is a path traversal / arbitrary file write risk.

  • [MEDIUM] loop-detector.ts (inferred, JSON deserialization) — The state file contents are deserialized via JSON.parse (evidenced by the "recovers from corrupt state file" and "recovers from non-array state file" tests). The file at ~/.node9/loop-state.json is user-writable. An attacker who can write to the home directory can poison the state file with crafted JSON to manipulate loop detection logic — e.g., injecting entries with arbitrary tool names, timestamps, or counts to either suppress loop detection (allowing infinite loops) or trigger false positives (causing denial of service by blocking legitimate tool calls). The recovery path only handles invalid JSON and non-array types but may not validate individual entry fields (timestamps, tool names, counts), potentially allowing prototype pollution or type confusion if parsed entries are used unsafely.

  • [LOW] src/__tests__/loop-detector.spec.ts:beforeEach — Temporary directories created with fs.mkdtempSync under os.tmpdir() are used to override HOME. In shared/multi-user CI environments, if os.tmpdir() is world-writable and a race condition exists between mkdtempSync and subsequent mkdirSync, an attacker could symlink-race the .node9 subdirectory. This is low severity in test-only code but reflects a pattern that, if replicated in production initialization code, would be exploitable.

  • [LOW] src/cli/commands/report.ts, src/config/index.ts (omitted) — Multiple files involving CLI args and config loading are omitted from the diff. Given that HOME/USERPROFILE and config schema are involved, any config file path constructed from these env vars without canonicalization (fs.realpathSync + prefix check) shares the same path traversal concern noted above.

Summary of highest priority: Validate and canonicalize the resolved state file path against an expected base directory before any read/write operation in loop-detector.ts. Use fs.realpathSync after construction and assert the result starts with the expected prefix.


Automated security review by node9

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

🔍 node9 Code Review

Review

The AI diff is empty — no changes were actually applied. The "tests after" output appears to be a fragment of type definitions (likely from src/config-schema.ts), not test results, so there's nothing meaningful to evaluate about test pass/fail status.

Issues with the original diff (for context)

The original code being reviewed has one notable logic concern worth flagging:

Loop detection fires before trust session check: The loop detector is inserted before getActiveTrustSession(toolName), meaning a trusted tool in an active trust session can still be blocked by loop detection. This may or may not be intentional — if a user explicitly granted trust for repeated calls, loop detection overriding that trust is surprising behavior.

No AI fix to review: Since the applied diff is empty, there are no security issues, logic errors, or correctness problems introduced by the AI — because it introduced nothing.

The test output fragment suggests the build/type-check may have succeeded (the schema types are present), but without actual test results, correctness cannot be confirmed.


Automated review by node9

@github-actions
Copy link
Copy Markdown
Contributor Author

🔒 node9 Security Review

Security Findings

  • [MEDIUM] src/auth/orchestrator.ts:_authorizeHeadlessCore — Loop detection bypass via argument manipulation. The recordAndCheck function compares tool calls with "identical arguments," but the implementation is not visible. If argument comparison uses shallow equality, string serialization, or JSON.stringify on args, an attacker-controlled AI agent can trivially bypass loop detection by making minor variations to arguments (e.g., adding whitespace, reordering keys, or appending benign fields) while achieving the same malicious effect. The loop detection is a security control, and its bypass resistance depends entirely on the hidden loop-detector.ts implementation.

  • [MEDIUM] src/cli/commands/report.ts (omitted) — The --period flag accepts values like today, 7d, 30d, month. If the period value is used to construct file paths (e.g., log directory names) or shell commands without validation, it could enable path traversal or command injection. The implementation is omitted, so this cannot be confirmed, but the pattern of user-controlled period strings fed into file system operations (reading from ~/.claude/projects/) warrants flagging.

  • [LOW] src/config-schema.ts:ConfigFileSchema — The loopDetection config block uses .optional() on all subfields (enabled, threshold, windowSeconds). In orchestrator.ts, ld.windowSeconds * 1000 and ld.threshold are passed directly to recordAndCheck without null/undefined guards visible in the diff. If windowSeconds or threshold are undefined (all fields optional), this could produce NaN or 0 values passed as thresholds, potentially disabling loop detection silently or causing unexpected behavior. The downstream config/index.ts defaults are omitted, so the actual default application is unverifiable.

  • [LOW] src/auth/orchestrator.ts:_authorizeHeadlessCore — The loop detection reason string directly embeds toolName and loopResult.count into a user-facing message. If toolName is attacker-influenced (e.g., via a malicious MCP tool registration with a crafted name), this could inject misleading content into the denial message shown to the user, potentially causing confusion or social engineering of the human reviewer. Not a direct code execution risk, but a trust boundary concern.


Automated security review by node9

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