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>
🔍 node9 Code ReviewReviewThe AI applied no changes (empty diff). The original diff contains two modifications:
These are documentation-only changes with no security, logic, or correctness implications. There is nothing to fix here, and the AI correctly made no changes. Note: The test failure ( Everything looks fine. Automated review by node9 |
🔒 node9 Security Review✅ No security issues found. The diff only contains README.md changes (license badge update, whitespace addition) and notes that Automated security review by node9 |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🔍 node9 Code ReviewReviewThe AI diff is empty — no actual code changes were applied by the AI fix. The commit shown is the original diff itself (the Reviewing the original changes for issues: README.md — License badge change (Apache-2.0 → MIT):
Overall: No security issues. The license badge change is the only real correctness concern worth verifying. The AI applied no changes, so nothing was introduced or broken by the fix itself. Automated review by node9 |
🔒 node9 Security Review✅ No security issues found. The diff contains only cosmetic/documentation changes (README badge update, console.log output formatting in the init command). The Automated security review by node9 |
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>
🔍 node9 Code ReviewReviewThe AI applied no changes (empty diff), which is the correct response here. Assessment: Correct behavior. The original diff contains only:
Neither change introduces security issues, logic errors, or correctness problems that require fixing. The The test failure ("unsafe characters" in test command) is unrelated to the code changes in the diff — it's a test runner configuration issue with the No issues to flag. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Automated security review by node9 |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🔍 node9 Code ReviewReviewThe AI applied no changes (empty diff). There is nothing to review. The original diff itself contains a few noteworthy items worth flagging for the broader review: Regex anchor removal ( License change ( The AI fix is empty — it neither addressed these issues nor introduced new ones. No correctness or security problems were introduced by the AI's (non-)changes. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Automated security review by node9 |
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>
🔍 node9 Code ReviewReviewThe AI applied no changes (empty diff). There is nothing to review — the AI produced an empty fix against the original diff. No code was modified, added, or removed. What this means practically:
Concerns with the original diff that remain unaddressed:
Verdict: No AI changes to evaluate. The empty fix is a no-op. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Automated security review by node9 |
🔍 node9 Code ReviewReviewThe AI applied no changes (the diff is empty). The original diff shows meaningful functionality being added (MCP tool pinning for rug-pull defense, a new CLI command, license badge update), but the AI produced zero fixes. This could mean:
On the visible changes: The README and CLI registration changes look structurally correct. The license badge change (Apache-2.0 → MIT) is a substantive claim that should be verified against the actual Verdict: The AI produced no actionable output. The review is incomplete due to omitted files containing all security-relevant implementation details. Cannot approve. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Automated security review by node9 |
🔍 node9 Code ReviewReviewThe 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. Regarding the original diff under review: The original diff itself is reasonable at the surface level (license badge update, README documentation for MCP tool pinning, CLI command registration), but 8 critical files were omitted due to size — including the actual implementation ( The test rejection ( Bottom line: The AI did nothing. No fix was applied, no logic was changed, and no security issues were introduced — but equally, nothing was corrected. If there was an actual problem to fix, it was left unaddressed. Automated review by node9 |
🔒 node9 Security ReviewSecurity Findings
Automated security review by node9 |
## [1.9.1](v1.9.0...v1.9.1) (2026-04-11) ### Bug Fixes * dev → main ([#82](#82)) ([8f55caf](8f55caf))
|
🎉 This PR is included in version 1.9.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Changes on
devTests: ✗ failing
PR opened automatically by node9 CI agent