feat: MCP companions for skill v8 (pin bump + SET-LEVEL + Inv #1.a allowlist) - #480
Merged
Conversation
… mirror Coordinated MCP-side companion for vaultpilot-security-skill#19 (skill v0.6.0 — adversarial-smoke-test batch). Pin bump (skill-pin-drift.ts): - EXPECTED_SKILL_SHA256: b70085df…0799 → 01d8d68d…1414 - EXPECTED_SKILL_SENTINEL_B: _v7_ → _v8_ - EXPECTED_SKILL_SENTINEL_C: 8e252312c08c415b → 4aac027a9df315a9 [SET-LEVEL ENUMERATION] block on get_token_allowances (Q1.B, #450): - New src/security/set-level-enumeration.ts renders verbatim row table - tokenAllowancesHandler wrapper appends the block to every response - Skill v8 §14 mandates the block; missing block is an Inv #4 compromise signal (the rogue agent silently filtered the row set) Inv #1.a outer dispatch-target allowlist mirror (Q7.B, #461): - New src/security/canonical-dispatch.ts derives the (toolFamily, chain) → expected `to` map from src/config/contracts.ts (single source of truth, no hand-mirrored table) - Tool families covered: prepare_aave_*, prepare_compound_*, prepare_lido_stake/_unstake, prepare_morpho_*, prepare_uniswap_swap, prepare_uniswap_v3_*, prepare_eigenlayer_deposit - Regression test asserts every allowlist address resolves to a real CONTRACTS entry — the lookup cannot silently fall out of sync - Per-handler wiring (calling assertCanonicalDispatchTarget on each prepare_* result) is deferred to a follow-up PR — different prepare flows have different tx-chain shapes (approve+action multi-leg, single-tx, etc.) so wiring needs per-flow review. Skill-side check remains the load-bearing defense for rogue-MCP threat; this module's job is to exist as the MCP-side mirror per Q7.B and to fail-fast in unit tests if the table drifts. Q3.C (CONTACT-CHAIN MISMATCH on preview_send) intentionally NOT included — the contacts schema is per-chain-family (btc/evm/solana/ tron), not per-EVM-chain, so the check has no schema-level signal to fire on for the smoke-test scenario. Filed as a follow-up: extend contact metadata with per-EVM-chain tag, then add the warning. The skill-side Inv #2.5 positive-chain-naming rule remains the load-bearing defense. MUST land AFTER skill v8 PR (vaultpilot-security-skill#19) merges. Closes vaultpilot-mcp#450, #461 (skill+MCP load-bearing defense for both is now in place). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced Apr 28, 2026
szhygulin
marked this pull request as ready for review
April 28, 2026 05:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Coordinated MCP-side companion for vaultpilot-security-skill#19 (skill v0.6.0 — adversarial-smoke-test batch). DRAFT until the skill PR merges.
Three bundled changes:
1. Pin bump (
src/diagnostics/skill-pin-drift.ts)EXPECTED_SKILL_SHA256b70085df…079901d8d68d…1414EXPECTED_SKILL_SENTINEL_B_v7__v8_EXPECTED_SKILL_SENTINEL_C8e252312c08c415b4aac027a9df315a92.
[SET-LEVEL ENUMERATION]block onget_token_allowances(Q1.B, closes #450)New
src/security/set-level-enumeration.tsrenders a verbatim markdown row table the agent surfaces under skill v8's Invariant #14.tokenAllowancesHandlerwrapper appends the block to every response. Missing block on a realget_token_allowancesresponse = Invariant #4 compromise signal.3. Inv #1.a canonical-contract allowlist mirror (Q7.B, closes #461)
New
src/security/canonical-dispatch.tsexposesassertCanonicalDispatchTarget(toolName, chain, to). The allowlist is derived fromsrc/config/contracts.ts— single source of truth, no hand-mirrored table. Regression test asserts every allowlist entry resolves to a realCONTRACTSaddress.Coverage:
prepare_aave_*,prepare_compound_*,prepare_lido_stake/_unstake,prepare_morpho_*,prepare_uniswap_swap,prepare_uniswap_v3_*,prepare_eigenlayer_deposit.Per-handler wiring deferred to a follow-up PR — different prepare flows have different tx-chain shapes (approve+action multi-leg vs single-tx vs other). Per the smallest-solution discipline in CLAUDE.md, this PR ships the helper module + regression test; wiring is a separate per-flow review.
What's NOT included (intentional)
Q3.C (
CONTACT-CHAIN MISMATCHonpreview_send) — the contacts schema is per-chain-family (btc | evm | solana | tron), not per-EVM-chain, so the check has no schema-level signal to fire on for the smoke-test scenario (Carol-on-Arbitrum sent on Ethereum). Filed as a follow-up: "extend contact metadata with per-EVM-chain tag." Skill-side Inv #2.5 positive-chain-naming remains the load-bearing defense.This PR is DRAFT. Merge skill#19 first; otherwise every signing flow halts with
vaultpilot-preflight skill integrity check FAILEDwhile master shows v7 and the pin expects v8.Test plan
tsc --noEmit)canonical-dispatch.test.ts: 11 cases — drift detection, family-prefix matching, most-specific-tool wins, multi-Comet allowlist, Lido Ethereum-only, Uniswap router/NPM splitset-level-enumeration.test.ts: 3 cases — header + table, empty list, truncation warningskill-pin-drift.test.ts: 17 cases against new constants🤖 Generated with Claude Code