Skip to content

fix(ai-code-mode): reject tools that require approval - #1949

Merged
AlemTuzlak merged 2 commits into
mainfrom
fix/code-mode-approval-guard
Oct 29, 2026
Merged

AlemTuzlak merged 2 commits into
mainfrom
fix/code-mode-approval-guard

Conversation

@jherr

@jherr jherr commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Mode could run a tool that needs approval without asking. A tool marked needsApproval: true expects a human to approve each call before it runs. Code Mode converted such a tool into a sandbox binding and ran it from inside execute_typescript, so no approval interrupt fired. This PR rejects those tools instead. Addresses GHSA-wmh6-35qp-cc2x.

🎯 Changes

The CodeModeTool type already pins needsApproval to false, so typed callers were blocked at compile time. This adds the matching runtime guard for callers that bypass the types (JavaScript, as casts, or dynamically built tools):

  • createCodeModeTool and createCodeMode throw at setup when any tool has needsApproval: true, and name every offending tool.
  • toolToBinding and toolsToBindings throw too, as a lower-level backstop. Every binding path funnels through them.

A note on the behavior went into the Code Mode docs. A changeset is included.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Root cause

Issue. A host tool marked needsApproval: true ran with no human approval when an app passed it to Code Mode and the model drove execute_typescript to call it.

Cause. toolToBinding in packages/ai-code-mode/src/bindings/tool-to-binding.ts dropped needsApproval and wrapped tool.execute directly. createCodeModeTool in packages/ai-code-mode/src/create-code-mode-tool.ts sent every tool through toolsToBindings with no check. chat() saw only the outer execute_typescript call, so it raised no per-tool approval interrupt for the nested host call.

Fix. Both functions now reject a tool whose needsApproval is true. createCodeModeTool throws at setup and names every offending tool. toolToBinding throws as the lower-level backstop, so no binding is ever produced for an approval-gated tool.

Possible alternatives

  • Guard only toolToBinding. The smaller change that kills the repro, because createCodeModeTool calls toolsToBindings, which calls toolToBinding. This PR also keeps the createCodeModeTool guard so setup fails fast with one clear error that names every offending tool, before any binding or secret-scan work. This is the two-layer fix the advisory recommended.
  • Approve the outer execute_typescript call. Rejected. One approval on the wrapper cannot carry per-tool or per-argument approval, so it would weaken the guarantee the app asked for.

Testing

Commands run (on the branch, after merging latest main):

  • nx run-many --targets=test:lib,test:types,test:oxlint,build --projects=@tanstack/ai-code-mode — pass (209 unit tests).
  • pnpm test:docs — pass (no broken links).
  • pnpm test:sherif — pass (0 errors; pre-existing warnings only).
  • pnpm test:knip — pass (exit 0; ai-code-mode not flagged).
  • I did not run the single pnpm test:pr command end to end. I ran its main targets for the affected package plus the doc, sherif, and knip checks above.

Gate 1 repro (agent-written, run this session). An agent-authored test asserts that toolsToBindings and createCodeModeTool reject a needsApproval tool and that the host tool never runs.

On clean main (the bug), it fails:

FAIL  tests/approval-repro.test.ts > ... > toolsToBindings rejects a needsApproval tool
AssertionError: expected [Function] to throw an error
FAIL  tests/approval-repro.test.ts > ... > createCodeModeTool rejects a needsApproval tool
AssertionError: expected [Function] to throw an error
 Test Files  1 failed (1)
      Tests  2 failed (2)

On this branch (fixed), the same test passes:

 Test Files  1 passed (1)
      Tests  2 passed (2)

Manual test:

  1. Define a server tool and set needsApproval: true on it.
  2. Pass it to createCodeMode({ driver, tools: [tool] }).
  3. On main: no error, and the tool runs inside execute_typescript with no approval.
  4. On this branch: createCodeMode throws and names the tool, so it never reaches the sandbox.

How this PR makes testing easy. The branch adds unit tests at every rejection point (toolToBinding, toolsToBindings, createCodeModeTool, createCodeMode), including the aggregated-name and lazy-tool cases, plus an "explicitly false still allowed" case. The E2E suite has no Code Mode tool-config harness, so this stays at the unit layer.

Risk / rollback

Low risk. The change only adds a throw for input the CodeModeTool type already rejected at compile time. Typed callers see no change. A JavaScript or cast caller that relied on the old unguarded behavior now gets a clear error. To roll back, revert this PR.

Public API change

No signature change. The runtime behavior changes for one invalid input: an approval-gated tool.

Before

// A needsApproval tool passed via JS or an `as` cast was accepted and ran
// unattended inside the sandbox.
createCodeMode({ driver, tools: [approvalTool] }) // no error

After

// The same call now throws at setup.
createCodeMode({ driver, tools: [approvalTool] })
// Error: Code Mode cannot expose tools that require approval: <name>...

Summary by CodeRabbit

  • Behavior Changes
    • Code Mode now rejects tools that require approval, including approval-gated lazy tools. Setup fails immediately with an error identifying the affected tools.
    • Tools that do not require approval continue to work. To use approval-gated tools, configure them outside Code Mode, where approval interruptions are supported.
  • Documentation
    • Added guidance on Code Mode’s tool approval restrictions and where to configure approval-gated tools.

jherr and others added 2 commits October 29, 2026 07:13
A tool marked `needsApproval: true` expects a human approval interrupt
before it runs. Code Mode invokes tools from inside the sandbox, where
the model only ever sees the outer `execute_typescript` call, so the
per-tool interrupt is never raised and the tool would run unattended.

The `CodeModeTool` type already pins `needsApproval` to `false`, so typed
callers were blocked at compile time. This adds the matching runtime guard
for callers that bypass the types (JavaScript, `as` casts, dynamically
built tools):

- `createCodeModeTool` / `createCodeMode` throw synchronously when any
  supplied tool has `needsApproval: true`, naming every offending tool.
- `toolToBinding` / `toolsToBindings` enforce the same rule as a
  lower-level backstop, so every binding path is covered.

Approving only the outer `execute_typescript` call would not preserve
per-tool/per-argument approval semantics, so these tools are rejected
rather than silently approved.

Addresses GHSA-wmh6-35qp-cc2x.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-mode-approval-guard

# Conflicts:
#	docs/code-mode/code-mode.md
#	docs/config.json
#	packages/ai-code-mode/tests/create-code-mode-tool.test.ts
#	packages/ai-code-mode/tests/tool-to-binding.test.ts
@changeset-bot

changeset-bot Bot commented Oct 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 914f5aa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/ai-code-mode Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@nx-cloud

nx-cloud Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 914f5aa

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 6s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-29 14:31:28 UTC

@github-actions

Copy link
Copy Markdown
Contributor

Coverage

✅ Coverage held across 8 compared package(s).

Each package is measured twice in this job — on this PR and on its merge-base with main — and compared. A metric fails only when it is at or above 60% on the merge-base and under 60% on this PR. Other drops are listed but do not fail. Packages your PR doesn't affect are not measured and not listed.

package statements branches functions lines
ai-code-mode 93.58% (+0.11) 85.79% (+0.16) 92.22% (+0.18) 94.52% (+0.08) ok
ai-code-mode-snippets 87.13% (0.00) 75.67% (0.00) 91.11% (0.00) 88.39% (0.00) ok
ai-isolate-cloudflare 94.11% (0.00) 70.66% (0.00) 100.00% (0.00) 93.96% (0.00) ok
ai-isolate-daytona 90.25% (0.00) 80.59% (0.00) 100.00% (0.00) 90.15% (0.00) ok
ai-isolate-e2b 92.11% (0.00) 81.48% (0.00) 97.36% (0.00) 95.05% (0.00) ok
ai-isolate-node 84.00% (0.00) 43.39% (0.00) 100.00% (0.00) 83.78% (0.00) ok
ai-isolate-quickjs 89.07% (0.00) 76.41% (0.00) 90.90% (0.00) 90.30% (0.00) ok
ai-isolate-quickjs-bun 7.76% (0.00) 8.08% (0.00) 5.00% (0.00) 8.33% (0.00) ok

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between caaa97b and 914f5aa.

Files selected for processing: 8
  • .changeset/code-mode-reject-needs-approval.md
  • docs/code-mode/code-mode.md
  • docs/config.json
  • packages/ai-code-mode/src/bindings/tool-to-binding.ts
  • packages/ai-code-mode/src/create-code-mode-tool.ts
  • packages/ai-code-mode/tests/create-code-mode-tool.test.ts
  • packages/ai-code-mode/tests/create-code-mode.test.ts
  • packages/ai-code-mode/tests/tool-to-binding.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Walkthrough

Code Mode now rejects tools marked needsApproval: true during binding conversion and tool creation. Tests cover these checks, and the documentation describes how to configure approval-required tools outside Code Mode.

Changes: Approval-tool rejection

Layer / File(s) Summary
Rejection checks and guidance
packages/ai-code-mode/src/bindings/tool-to-binding.ts, packages/ai-code-mode/src/create-code-mode-tool.ts, packages/ai-code-mode/tests/*, docs/code-mode/code-mode.md, .changeset/code-mode-reject-needs-approval.md, docs/config.json
Binding conversion and Code Mode tool creation throw errors for tools marked needsApproval: true. Tests cover rejection, error names, lazy tools, and tools marked false. Documentation and the changeset describe the restriction and configuration guidance. The Code Mode documentation date was updated.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: alemtuzlak

Merge Risk: Minimal · up to 914f5

Approval-required tools are rejected during setup. No merge-blocking risk was identified.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title "fix(ai-code-mode): reject tools that require approval" names the main change: Code Mode now throws on tools marked needsApproval: true. It follows conventional-commit form and names the aff…
Description check Passed The description follows the template. It includes the Changes section with the reasoning and the rejection behavior. The checklist is filled in. The description adds a root cause, alternatives, testin…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "fix(ai-code-mode): reject tools that require approval" names the main change: Code Mode now throws on tools marked needsApproval: true. It follows conventional-commit form and names the affected package. The title is specific and matches the changeset, the runtime guards in createCodeModeTool and toolToBinding, and the tests.

Full details: Description check

Explanation

The description follows the template. It includes the Changes section with the reasoning and the rejection behavior. The checklist is filled in. The description adds a root cause, alternatives, testing evidence, risk and rollback, and a public API note. One checklist item is unchecked: the author did not run pnpm run test:pr end to end. The author explains this in the Testing section, along with the targets that did run. The Release Impact section is marked for a changeset, and the changeset file is present in the summary.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 5 files. (3 skipped: 3 unsupported.)

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Oct 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@1949

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@1949

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@1949

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@1949

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@1949

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@1949

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@1949

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@1949

@tanstack/ai-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-cloudflare@1949

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@1949

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@1949

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@1949

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@1949

@tanstack/ai-compaction

npm i https://pkg.pr.new/@tanstack/ai-compaction@1949

@tanstack/ai-computer-local

npm i https://pkg.pr.new/@tanstack/ai-computer-local@1949

@tanstack/ai-computer-playwright

npm i https://pkg.pr.new/@tanstack/ai-computer-playwright@1949

@tanstack/ai-computer-sandbox

npm i https://pkg.pr.new/@tanstack/ai-computer-sandbox@1949

@tanstack/ai-computer-use

npm i https://pkg.pr.new/@tanstack/ai-computer-use@1949

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@1949

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@1949

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@1949

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@1949

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@1949

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@1949

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@1949

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@1949

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@1949

@tanstack/ai-harness

npm i https://pkg.pr.new/@tanstack/ai-harness@1949

@tanstack/ai-harness-cli

npm i https://pkg.pr.new/@tanstack/ai-harness-cli@1949

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@1949

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@1949

@tanstack/ai-isolate-e2b

npm i https://pkg.pr.new/@tanstack/ai-isolate-e2b@1949

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@1949

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@1949

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@1949

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/@tanstack/ai-llmgateway@1949

@tanstack/ai-lovable

npm i https://pkg.pr.new/@tanstack/ai-lovable@1949

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@1949

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@1949

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@1949

@tanstack/ai-neon

npm i https://pkg.pr.new/@tanstack/ai-neon@1949

@tanstack/ai-octane

npm i https://pkg.pr.new/@tanstack/ai-octane@1949

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@1949

@tanstack/ai-ollaya

npm i https://pkg.pr.new/@tanstack/ai-ollaya@1949

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@1949

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@1949

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@1949

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@1949

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@1949

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@1949

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@1949

@tanstack/ai-reactor

npm i https://pkg.pr.new/@tanstack/ai-reactor@1949

@tanstack/ai-remix

npm i https://pkg.pr.new/@tanstack/ai-remix@1949

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@1949

@tanstack/ai-sandbox-blaxel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-blaxel@1949

@tanstack/ai-sandbox-boxd

npm i https://pkg.pr.new/@tanstack/ai-sandbox-boxd@1949

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@1949

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@1949

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@1949

@tanstack/ai-sandbox-e2b

npm i https://pkg.pr.new/@tanstack/ai-sandbox-e2b@1949

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@1949

@tanstack/ai-sandbox-railway

npm i https://pkg.pr.new/@tanstack/ai-sandbox-railway@1949

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@1949

@tanstack/ai-sandbox-upstash-box

npm i https://pkg.pr.new/@tanstack/ai-sandbox-upstash-box@1949

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@1949

@tanstack/ai-skills

npm i https://pkg.pr.new/@tanstack/ai-skills@1949

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@1949

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@1949

@tanstack/ai-typesafe

npm i https://pkg.pr.new/@tanstack/ai-typesafe@1949

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@1949

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@1949

@tanstack/ai-vertex

npm i https://pkg.pr.new/@tanstack/ai-vertex@1949

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@1949

@tanstack/ai-worldlabs

npm i https://pkg.pr.new/@tanstack/ai-worldlabs@1949

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@1949

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@1949

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@1949

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@1949

@tanstack/svelte-ai-devtools

npm i https://pkg.pr.new/@tanstack/svelte-ai-devtools@1949

commit: 914f5aa

@AlemTuzlak
AlemTuzlak merged commit 44f225f into main Oct 29, 2026
16 checks passed
@AlemTuzlak
AlemTuzlak deleted the fix/code-mode-approval-guard branch October 29, 2026 14:45
@github-actions github-actions Bot mentioned this pull request Oct 29, 2026
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.

2 participants