Skip to content

feat: second-agent verification artifact - #48

Merged
szhygulin merged 9 commits into
mainfrom
feat/second-agent-verification-artifact
Apr 21, 2026
Merged

feat: second-agent verification artifact#48
szhygulin merged 9 commits into
mainfrom
feat/second-agent-verification-artifact

Conversation

@szhygulin

Copy link
Copy Markdown
Collaborator

Summary

  • Adds get_verification_artifact(handle) MCP tool returning a sparse, copy-paste-friendly JSON artifact (raw calldata / TRON rawDataHex, chain, recipient, value, payloadHash, and — when preview_send has pinned gas — the Ledger blind-sign preSignHash) plus a canned prompt for an independent second LLM to decode from scratch.
  • Deliberately omits the server's own humanDecode, swiss-knife URL, and 4byte cross-check so the second agent cannot parrot them — adversarial independence is the whole point.
  • Trust anchor stays the Ledger device screen: preSignHash on 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, or send_transaction.

Test plan

  • npm run typecheck clean
  • npm test — all 449 tests pass, including 5 new cases in test/verification-artifact.test.ts (EVM happy path, EVM w/ pinned gas, TRON happy path, unknown handle, retired handle)
  • Manual: prepare_aave_supplyget_verification_artifact(handle) → confirm shape; then preview_send(handle) → artifact again has preSignHash
  • Manual: paste artifact + instructions into a second LLM (different provider) — confirm independent decode produces a plausible English description

🤖 Generated with Claude Code

szhygulin and others added 9 commits April 21, 2026 21:04
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>
@szhygulin
szhygulin merged commit b5641db into main Apr 21, 2026
3 checks passed
@szhygulin szhygulin mentioned this pull request Apr 21, 2026
3 tasks
@szhygulin
szhygulin deleted the feat/second-agent-verification-artifact branch April 25, 2026 02:15
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.

1 participant