Repository navigation
feat(tools): Tool-5 — rug-pull re-prompt for app tools - #436
Merged
Merged
Conversation
Most of the hardening this rung named shipped in Tool-2 (length caps, invisible-text refusal incl. Unicode Tags, reserved intent-verb names). What is genuinely missing is the rug-pull re-prompt, ported from the MCP path with the threat model adjusted: an app tool's descriptors are manifest-derived, so the analogue is an app UPDATE silently rewriting a tool the user already approved and inheriting the friction the old wording earned. A tool now carries a fingerprint of the approved surface — title, description, effect, the full input declaration, and `surfaces` (adding `agent` moves a tool from human-clicked to autonomous, which is a change in what it does). Excluded: `appliesTo` and `registeredAt`, which change without changing anything a person read; object-valued modifiers are flattened to tuples so a manifest key reorder is not a "change". A Changed or New tool is forced to confirm regardless of its declared effect, and `tools.list` stamps the state onto each record so a caller asks the right question BEFORE clicking rather than discovering the refusal by being refused. The security review found this shipping a regression that would have made every app tool permanently uncallable: the only caller derived `confirmed` from `effect` alone, so a `pure` tool sent none, the approval was never recorded, and the row failed on every click forever — and no tool could ever become agent-callable. My tests missed it because the fixture pre-approved everything. Fixed, and pinned by a test that starts from an unapproved tool. Also from review: fail CLOSED on an unreadable approvals store (both a failed open and a throwing read); pass the REASON to the confirm seam so a rug-pull reads differently from a first approval; key approvals per CALLER, because `confirmed` is an assertion a caller cannot prove and a global row would turn a forged flag from a one-call problem into a standing one; and re-baseline only after the call actually succeeded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Most of what this rung named already shipped in Tool-2 — length caps, invisible-text refusal (incl. Unicode Tags), reserved intent-verb names, and ids that cannot collide with a curated verb or an
mcp.*id. Those are now pinned by tests rather than assumed. What was genuinely missing is the rug-pull re-prompt.The threat, adjusted for app tools
MCP's rug-pull is a remote server swapping a benign description for a malicious one after approval. An app tool's descriptors are manifest-derived, so the analogue is narrower but real: an app update silently rewrites a tool you already approved, and it keeps whatever friction the old wording earned. A
puretool becomesexternal; a tool grows a required argument; a description is rewritten.A tool now carries a fingerprint of the approved surface — title, description, effect, the full input declaration, and
surfaces(addingagentmoves a tool from human-clicked to autonomous, which is a change in what it does, not merely where it appears). Excluded:appliesToandregisteredAt, which change without changing anything a person read. Object-valued modifiers are flattened to tuples so a manifest key reorder is not a "change" — spurious re-prompts are exactly the click-through training this mechanism exists to avoid.A
ChangedorNewtool confirms regardless of its declared effect, andtools.liststamps the state onto each record, so a caller asks the right question before clicking rather than discovering the refusal by being refused.The review caught a regression that would have shipped
Rated HIGH, and correctly: the check made every never-approved tool refuse server-side, while the only caller derived
confirmedfromeffectalone. So apuretool sent no confirmation, the approval was never recorded, and the row failed on every click forever — and since nothing could reach an approved state and agents are refused outright, no app tool would have been agent-callable at all, quietly undoing Tool-4.My tests missed it because the fixture pre-approved everything — they tested the harness, not the product. Now pinned from an unapproved starting state.
Four more, all fixed here:
Changedreason now reaches the human. It was computed, logged, and discarded, so a rug-pull showed the same dialog as a first approval.confirmedis an assertion a caller cannot prove; a global row turned a forged flag from a one-call problem into a standing one for every other caller.Known constraint, filed not hidden
An agent cannot mint an approval (agent-initiated confirms are refused outright since Tool-4), so agent tool-calling stays blocked until Tool-8's shell-owned approval. Tool-6 will project tools into the agent's layer, but its calls refuse until then. That ordering is now explicit in the plan.
Verification
Lint clean, typecheck clean, 3300 tests. Includes: no fingerprint collision constructible, no TOCTOU, approvals dropped only on uninstall (not by the update path), and the id-collision claim checked against the actual minting code.
🤖 Generated with Claude Code