feat: second-agent verification artifact - #48
Merged
Conversation
New MCP tool returns a sparse JSON artifact (raw calldata, chain, to, value, payloadHash, preSignHash when pinned) plus a canned prompt so the user can paste it into an independent LLM session for an adversarial from-scratch decode. Narrows the honestly-conceded coordinated-agent- compromise gap without adding keys, signatures, or setup ceremony — the Ledger screen remains the fabrication anchor via preSignHash. Artifact deliberately omits humanDecode / swiss-knife URL / 4byte cross-check so the second agent cannot parrot them. Routes by handle origin exactly like get_tx_verification; reuses existing store APIs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the second-agent verification offer at two touchpoints so the coordinator surfaces `get_verification_artifact` to the user instead of leaving it as a dormant tool: - preview_send agent-task block: adds a FIFTH trust-boundary option (second-agent verification) alongside the existing FOURTH (viem hash recompute). The fifth is the only check that survives a coordinated compromise where both this agent and the MCP are lying together. - server-instructions SECOND-AGENT VERIFICATION section: now instructs the agent to ACTIVELY VOLUNTEER the artifact at prepare time for high- risk flows (unknown 4-byte selectors, unfamiliar contracts, large approvals, >$1k value, or when the 4byte cross-check returned no-signature/error). Includes explicit "do NOT pre-decode in the same reply" guard — the second agent must decode from scratch without shared context from the first, or the whole verification loses its adversarial independence. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-decode Before: option (4) at preview_send read like a restatement of option (b) at prepare — both started with "decode the calldata yourself". That hides the actual unique value of (4). After: option (4) is framed as the `(pinnedTuple, preSignHash)` pair- consistency check it really is — narrower than (b), catches the specific attack the on-device hash match can't detect on its own (MCP reports tuple T with preSignHash = hash(Y≠T) and forwards Y to WalletConnect; chat hash and device hash agree at hash(Y), so only a local hash(T) recompute catches the inconsistency). The decode step is now a conditional prerequisite — skipped if the user already ran (b) at prepare time — not a duplicate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two trust-boundary cleanups to the prepare-time agent-task block and
the cross-check summary:
- Drop option (c) ("agent WebFetches swiss-knife"). It collapsed to (b)
in practice because swiss-knife is a client-side SPA and WebFetch
can't execute JS — the agent's only fallback was to pull the calldata
query-param out of the URL and decode with its own ABI knowledge,
which is just (b). There is no agent-driven equivalent of (a) that
actually crosses the trust boundary with current tooling, so listing
(c) created the illusion of a third independent path.
- Extend (b): when the agent's weights don't recognize a 4-byte
selector, it may WebFetch the 4byte.directory or openchain.xyz JSON
signature-lookup endpoint as a fallback — but must say so
explicitly. The arg-decode step is still in the agent's weights,
so it's still (b), just with an extended selector dictionary.
- Cross-check summary now leads with the trust-boundary flag
("MCP-side — same trust boundary as the local decode; NOT an
external check") instead of burying it at the end of ~100 words of
explanation. The existing <500-char length contract is preserved.
- De-number the preview-time options (was "FOURTH OPTION" / "FIFTH
OPTION" counting across touchpoints, which the agent parroted as
"Optional fourth trust-boundary check" in live chats and shifted
when the prepare-time option count changed). Each preview-time
option is now a named "OPTION —" block, self-describing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…steableBlock
Live test surfaced the real UX problem: the user pasted not just the
prompt + JSON into the second agent, but also the first agent's
surrounding commentary ("Paste the block below..." / "Reply with what
the second agent said..."). Without visible boundaries, the second
agent couldn't tell where the paste target ended and where chat
commentary began.
Fix: the artifact now exposes a single `pasteableBlock: string` field
in place of the previous `instructionsForSecondAgent`. pasteableBlock
is a self-contained paste target with explicit START / END markers:
===== COPY FROM THIS LINE TO THE "END" MARKER INTO A SEPARATE LLM SESSION =====
===== (ideally a different LLM provider — the point is no shared context) =====
You are auditing a transaction...
[steps 1–5]
PAYLOAD:
{
"chain": "ethereum",
"to": "0x...",
...
"preSignHash": "0x..."
}
===== END — STOP COPYING HERE =====
Structured fields (chain, to, value, data, payloadHash, preSignHash)
stay on the artifact for programmatic consumers; the embedded JSON
inside the paste block is the second-agent-facing view and omits
internal plumbing (handle, artifactVersion) that would only invite
the second agent to comment on shape rather than semantics.
The preview-time agent-task block now tells the agent to relay ONLY
pasteableBlock verbatim — do not dump the full artifact JSON, do not
wrap the paste target with commentary between the markers, do not
reformat. Chat to the user goes outside the markers.
README section updated to describe the new shape.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Live transcripts showed the agent parroting the technical wording suggestion verbatim — "keccak256(eip1559_tuple)", "pair-consistency check", "second, independent LLM session" — which is opaque to a non- technical user. Added an explicit no-jargon rule and rewrote both wording suggestions in everyday terms: receipt re-check (cashier analogy) for pair-consistency, "second opinion from a different AI" for second-agent verification. Technical names stay in the agent- only directive so the agent still knows what to execute. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same live-transcript jargon leak that hit the preview-time block also
hits the prepare-time one — the agent forwards "swiss-knife decoder
URL", "MCP trust boundary", "built-in ABI knowledge", "preview_send",
"Ledger blind-sign hash", "dispatching" straight into user chat.
Added the same PLAIN-ENGLISH RULE at prepare time with an explicit
deny-list of jargon, plus plain-English wording suggestions for (a)
("open the decoder link in your own browser, runs separately from my
server") and (b) ("ask me to re-read it from my own knowledge of how
these contracts work, different source than the server's reading").
Technical descriptions kept inline as agent-only context.
Also covers the "I looked up an unknown selector in an online registry"
disclosure in user-facing terms.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two live-transcript issues on the same PR:
(1) When the user picked (b) at prepare time and the agent's re-decode
matched the server, the verdict "Agrees with the MCP cross-check" was
buried at the END of a six-bullet technical breakdown. Users who just
asked for a second opinion need the ✓ pass headline first; the
function/args walkthrough is supporting evidence, not the answer.
(2) Both the prepare-time (a)/(b) offer and the preview-time (1)/(2)
offer were being formatted as a single running sentence tucked next
to "reply send to continue". That layout invites skimming right past
the checks. Added a PRESENTATION RULE requiring a labeled section
("EXTRA CHECKS YOU CAN RUN BEFORE REPLYING "SEND":") with vertical
bullets, separated from the send prompt by blank lines.
Paired with a RESULT REPORTING RULE at both layers: lead the next
reply with a one-line ✓/✗ verdict on its own line before any
technical detail.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ification-artifact
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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
get_verification_artifact(handle)MCP tool returning a sparse, copy-paste-friendly JSON artifact (raw calldata / TRON rawDataHex, chain, recipient, value,payloadHash, and — whenpreview_sendhas pinned gas — the Ledger blind-signpreSignHash) plus a canned prompt for an independent second LLM to decode from scratch.humanDecode, swiss-knife URL, and 4byte cross-check so the second agent cannot parrot them — adversarial independence is the whole point.preSignHashon the artifact must match the hash on-device before signing, which is what catches artifact fabrication by a compromised intermediary.Threat this narrows
Closes the honestly-conceded gap in the existing security model: a fully-coordinated agent compromise that truthfully calls
prepare_*with malicious args and then lies in chat about what those args were. A second LLM session (different provider, no shared context) decodes the raw bytes independently — if the first agent lied, the two descriptions contradict and the user bails.What is unchanged
Existing signing path, fingerprint functions, schema guards, allowlists, handle lifecycle (15-min TTL, single-use), and TRON USB path are all untouched. This is a read-only, additive tool — no changes to
prepare_*,preview_send, orsend_transaction.Test plan
npm run typecheckcleannpm test— all 449 tests pass, including 5 new cases intest/verification-artifact.test.ts(EVM happy path, EVM w/ pinned gas, TRON happy path, unknown handle, retired handle)prepare_aave_supply→get_verification_artifact(handle)→ confirm shape; thenpreview_send(handle)→ artifact again haspreSignHash🤖 Generated with Claude Code