BoundBench

Vercel AI SDK UI

Framework-agnostic chat and agent UI layer of the Vercel AI SDK: useChat/useCompletion/useObject hooks, the UI message stream protocol, client-side tools and tool-approval flows.

github.com/vercel/ai › packages/react · 2026-10-04 · 08d7f0a

Defense-in-depth score

3.6 / 10

Minimal

AI SDK UI ships a careful tool-approval design, with exact-input approval prompts, schema re-validation and optional HMAC-signed approvals, but approval is off by default: in the documented chat route every tool the model calls runs immediately with the server's full credentials. The server also trusts the message history the browser posts, so without the experimental approval secret a modified client can replay fabricated tool calls and approvals to run any registered tool. Prompt injection through tool results is the dominant risk; deployers should enable toolApproval and experimental_toolApprovalSecret, authenticate the route, forward req.signal, and cap automatic resubmissions.

Key gaps (2)

  1. Server tools run with the server's ambient credentials for any caller, and a client-fabricated tool call plus approval in the posted history executes any registered tool unless the opt-in approval secret is set. C1 · Identity & least privilege
  2. Approval is off by default, so content injected through a tool result can drive any registered tool, including exfiltration and irreversible actions, with no human involved. C5 · Untrusted input blast radius

Criteria

C1 Identity & least privilege

Minimal 0.00 / 1.00

AI SDK UI has no notion of who is asking. The documented chat route reads whatever message history the browser posts and runs the developer's server-side tools with the server process's own credentials (provider API keys from environment variables, plus whatever the tools hold), with no per-request authorization hook in the UI layer. Because the client supplies the whole history, a caller can also fabricate an assistant tool call with a matching approval and have the server execute any registered tool with inputs of its choosing, unless the developer opts into approval signing. Every tool therefore runs with the server's full authority on behalf of any caller who can reach the route.

C2 Approval gates

Minimal 0.38 / 1.00

A real per-call approval flow exists: the server can mark tools as needing user approval (statically, per tool, or by a function of the input), the browser receives the exact tool input in an approval-requested state, and denials are first-class. The server re-validates approved inputs against the tool schema and can verify an HMAC signature that binds the approval to the exact call. But approval is off by default: unless the developer configures it, every tool the model calls executes immediately. Signature checking is a separate experimental opt-in; without it, approvals are just fields in the client-posted history and a modified client can fabricate them. Client-side tools run in the browser through developer callbacks with no SDK gate.

C3 Tool & action scoping

Moderate 0.53 / 1.00

Every tool call the model makes is parsed against the tool's declared input schema before execution, unknown tool names are rejected, and approvals replayed from client history are re-validated against the schema. That is typed validation, not allowlisting: the framework has no path, URL or quantity checks of its own, and what a tool can do is whatever the developer's execute function does. The developer chooses the tool set and can narrow it per step with activeTools.

C4 Code-execution isolation

N/A · full credit 1.00 / 1.00

Nothing in the scoped code interprets model output as code: the hooks, the UI message stream and streamText contain no shell, eval or interpreter. streamText accepts an experimental sandbox handle but only passes it through to developer tools; sandbox packages are separate and outside this scope. Developers who add code-running tools get no isolation from AI SDK UI.

C5 Untrusted input blast radius

Minimal 0.00 / 1.00

Tool results, MCP tool output and any fetched content enter the model's context as ordinary tool messages with nothing marking them as untrusted, and nothing in the framework changes what the model may do after reading them. With approval off by default, an injected instruction can drive any registered tool, including ones that send data out. The one structural help is that client-posted system-role messages are rejected by default. The MCP App bridge can forward app-originated chat messages to the host if the developer wires that handler.

C6 Memory, context & configuration integrity

Minimal 0.25 / 1.00

The UI layer keeps no memory store of its own, but its core loop re-injects the whole client-held conversation, including past tool results, into the model every turn, and the documented persistence pattern saves that history server-side for later sessions. The server trusts that history: assistant turns, tool results and approvals in it are taken as genuine unless the developer opts into approval signing or validateUIMessages. One real guard: system-role messages posted by the client are rejected by default. The persistence guide keys chats by a client-supplied id and explicitly leaves authorization out.

C7 Third-party extensions

Minimal 0.35 / 1.00

The default chat setup loads no third-party code. @ai-sdk/react does export an experimental MCP App renderer that runs HTML supplied by an MCP server inside a double iframe; the inner frame has no same-origin access, tool calls from the app are denied unless the host allow-lists them, and device permissions are deny-by-default. The HTML is whatever the server serves at render time, with no pinning or integrity check, and a restrictive content security policy is only built when the server itself supplies CSP metadata. Enforcement of the inner sandbox depends on the developer-hosted proxy page.

C8 Secrets & sensitive-data protection

Minimal 0.45 / 1.00

Provider API keys stay on the server and never go to the browser in the documented setup. Errors from local tools and the stream are masked to a generic message before reaching the client by default, telemetry sends nothing unless an integration is registered, and per-request context values are excluded from telemetry unless explicitly included. There is no redaction of what goes to the model provider, and the provider keys are long-lived environment variables.

C9 Audit & traceability

Minimal 0.35 / 1.00

The framework emits structured lifecycle events, including the start and end of every tool execution, to telemetry integrations and callbacks, and the browser holds a structured record of each call in the message parts. None of it is kept by default: no integration is registered, so nothing is recorded server-side unless the developer adds one. Approvals and denials are not separate audit events, and client-side tool runs are only seen by the server as results in the next request.

C10 Limits & kill switch

Minimal 0.30 / 1.00

Each server request runs one model step by default, which keeps a single request small. But the multi-step loop in AI SDK UI runs from the browser: with sendAutomaticallyWhen set, as the docs recommend for tools and approvals, the client resubmits after every completed tool round with no cap. Timeouts and token limits are opt-in. The stop() helper aborts the browser request, but the server keeps running the model call and any in-flight tool unless the developer forwards the request's abort signal, which the default route does not.