Conversation
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>
🔍 node9 Code ReviewReview of AI ChangesThe 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 ( 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:
Conclusion: The AI failed to produce any fix. The original code has a concrete null-dereference bug on Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
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>
🔍 node9 Code ReviewReview of AI ChangesThe 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:
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 |
🔒 node9 Security ReviewLooking at the visible diff, which is test code for a loop detector that reads/writes state files based on
Root cause pattern across all findings: The loop-state file path is entirely derived from 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>
🔍 node9 Code ReviewReviewThe 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:
Verdict: The AI fix is a no-op. No changes were made, no issues were addressed. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Primary concern: The production 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>
🔍 node9 Code ReviewReview of AI ChangesThe 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:
However, the original diff itself warrants noting a few concerns since no fixes were applied to address them:
The AI failed to produce any fix, leaving any issues in the original diff unaddressed. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Summary of highest priority: Validate and canonicalize the resolved state file path against an expected base directory before any read/write operation in Automated security review by node9 |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🔍 node9 Code ReviewReviewThe AI diff is empty — no changes were actually applied. The "tests after" output appears to be a fragment of type definitions (likely from 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 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 |
🔒 node9 Security ReviewSecurity Findings
Automated security review by node9 |
Changes on
devTests: ✗ failing
PR opened automatically by node9 CI agent