You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(ai,policy-opa): use own-property checks when resolving per-tool approvals (#16900)
## What
Tool sets, tool contexts, and per-tool approval maps are indexed by
names that come from model output or client-supplied message history.
Plain bracket access (`tools[name]`) resolves names that match inherited
object properties (`constructor`, `toString`, `valueOf`, `__proto__`) to
values on `Object.prototype` instead of `undefined`, which can slip past
the "unknown tool" / "unconfigured approval" guards.
This routes those lookups through a new `getOwn` helper (own-property
check via `Object.hasOwn`), builds the human-in-the-loop
approval-matching maps with a null prototype, and adds the same
own-property guards in policy-opa `wrapMcpTools` and `shadow`.
Behavior is identical for every real tool set, since tool names are
always own properties. The only change is that an inherited-property
name now reads as absent.
## Flow
Happy path (real tool named `weather`, unchanged):
1. Model or client references `weather`.
2. `getOwn(tools, "weather")` finds it as an own property and returns
the tool.
3. Approval, validation, and execution proceed exactly as before.
Unhappy path (name collides with a prototype member, e.g.
`constructor`):
1. Model or client references `constructor`, which is not a registered
tool.
2. Before: `tools["constructor"]` returned
`Object.prototype.constructor` (a function), passed the `!= null` guard,
and could be treated as a tool or invoked as an approval callback.
3. After: `getOwn(tools, "constructor")` returns `undefined`, so the
name routes to the safe "no such tool" / "unconfigured, apply fallback"
branch (`NoSuchToolError`, denied/user-approval fallback, etc.).
## Notes
Covers the approval path (per-tool resolution and replay re-validation),
tool-call parsing, execution, streaming callbacks, and UI message
conversion/validation. Includes a `getOwn` unit test plus regression
tests in `wrap-mcp-tools`, `shadow`, `resolve-tool-approval`, and
`collect-tool-approvals`.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Use own-property checks when resolving per-tool approvals so tool names and approval ids that match inherited object properties (e.g. `constructor`, `toString`, `valueOf`, `__proto__`) are treated as unconfigured/absent.
7
+
8
+
-`@ai-sdk/policy-opa`: `wrapMcpTools` builds its per-tool map with a null prototype and reads supplied approvals via an own-property check, and `shadow` guards its per-tool map lookup the same way.
9
+
-`ai`: tool and tool-context lookups keyed by a model- or client-supplied name now go through an own-property check (`getOwn`), so a name matching an inherited object property resolves to "no such tool"/"unconfigured" instead of a prototype value. This covers the approval path (per-tool approval resolution and replay re-validation) as well as tool-call parsing, execution, streaming callbacks, and UI message conversion/validation. The human-in-the-loop approval matching (`collectToolApprovals`) and streaming tool-name maps are built with a null prototype so a client-supplied id that matches an inherited property no longer slips past the "unknown approval" / "tool call not found" guards.
0 commit comments