Skip to content

feat: require server-side approval for every generated DELETE tool - #247

Merged
spark2ignite merged 1 commit into
mainfrom
vadim/gate-delete-tools
Sep 2, 2026
Merged

spark2ignite merged 1 commit into
mainfrom
vadim/gate-delete-tools

Conversation

@spark2ignite

Copy link
Copy Markdown
Collaborator

What changed

  • Every auto-generated DELETE tool (17 in the bundled spec, including delete_user and delete_account_role) now goes through the existing two-phase approval flow: the first call returns an approval_required envelope with a human-readable summary and a single-use token, and the call only executes after confirm_action is invoked with that token.
  • GeneratedTool gains an optional summary(args); generateTools sets it for DELETE operations only, built from the operation's OpenAPI summary, the resolved path params, and the customer scope (for example Delete user (id="abc") for customer xyz. This cannot be undone: DELETE /iam/v1/users/{id}.).
  • executeToolHandler resolves the gate from WRITE_GATED_SUMMARIES or the generated tool's summary, so no dispatch code changed.
  • confirm_action is exposed again. It joins HAND_WRITTEN_TOOLS (with coversEndpoint: null) so server.ts and the integration tool-surface test pick it up without a special case.
  • Hand-written creates and updates stay ungated. They are reversible and clients already prompt on destructiveHint, which is the reason the create_ticket gate was removed in Remove approval token dialog for create ticket #170. That decision is preserved and documented in the registry comment.

Why

An external researcher pointed out that the approval mechanism was wired to zero tools while every non-GET generated tool relied on destructiveHint, which is an advisory client-side annotation. A prompt-injected agent that ignores the hint could delete a platform user or disconnect an AWS account in one call. This is defense in depth (the API still enforces the caller's own permissions), but deletes are the irreversible subset and are the right place to draw the line. Credit: Syed Anas Mohiuddin.

Transport notes

The stdio server enforces the gate as before (MemoryApprovalStore, userKey = "stdio-local"). The remote Worker in the separate private repo already binds userKey to the OAuth identity and passes generatedTools, so it picks the gate up when it bumps to this version. It must also re-list confirm_action in its tool surface, otherwise clients there receive an envelope they cannot confirm. That change is needed before this ships to the hosted endpoint.

How validated

  • toolsHandler.test.ts: the three previously skipped gate tests are replaced with live ones against a synthetic generated DELETE tool (envelope without API call, confirm runs exactly once, distinct tokens, non-DELETE generated tools run directly).
  • generateTools.test.ts: summary shape, and an assertion over the bundled spec that every DELETE has a summary and nothing else does.
  • test/integration/stdio/approvalFlow.test.ts: un-skipped and rewritten around delete_alert with an msw DELETE handler that records hits, proving no request leaves before confirmation and exactly one after.
  • yarn check:dev, yarn test (911 passed), and yarn test in test/integration (184 passed) all green locally.

🤖 Generated with Claude Code

@spark2ignite
spark2ignite merged commit d9d58fe into main Sep 2, 2026
3 checks passed
@spark2ignite
spark2ignite deleted the vadim/gate-delete-tools branch September 2, 2026 19:00
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