AEV-2026-0045
Praxis Proxy's OpenAI Responses API MCP resolver normalizes a malformed allowed_tools restriction to unrestricted
GitHub issue 939 against praxis-proxy/ai, human reviewed and triage accepted for milestone v0.4.0, states that a malformed allowed_tools value on an MCP tool entry reaches AllowedTools::unrestricted, the same value the resolver returns for an absent restriction. This entry independently confirmed the mechanism by copying the real, unmodified extraction and filtering functions from the repository's current default branch into a standalone program and running them against a deterministic local fixture, with no MCP server of any kind contacted.
Affected
- Organisation
- praxis-proxy
- Product
- Praxis Proxy (praxis-proxy/ai)
- Component
- The OpenAI Responses API MCP tool resolver, specifically extract_allowed_tools and extract_from_filter_object in apis/src/openai/responses/openai_mcp_tool_resolve/mod.rs
- Versions
- The repository's current default branch as of 7 September 2026, commit 84ddcc4ef0e33a6c6006a2d5264069fde96c5dfc. No released package version was confirmed by this entry, and the exact commit issue 939's own review cites, 3cfddbcbf4d6adcab02d8257bbdb272981c433c7, resolves to no such commit against the repository as read directly by this entry.
- Configurations
- An OpenAI Responses API request carrying an MCP tool entry whose allowed_tools value is present but is neither the documented string array nor the documented filter object, such as a bare string.; An MCP tool entry whose allowed_tools value is the documented filter object, but whose tool_names or read_only member is present with the wrong JSON type.
Execution authority facts
Fields the evidence does not establish are shown as unknown rather than guessed.
- Intended task
- Resolve an MCP server's own tools/list result into concrete OpenAI Responses API function tool definitions, applying whatever restriction the caller's own allowed_tools value expressed
- Agent
- Praxis Proxy's own OpenAI Responses API MCP tool resolver, mediating between the caller and the connected MCP server
- Delegating actor
- The caller of the OpenAI Responses API who authored the request's own allowed_tools field, attempting to restrict which of the MCP server's tools may become callable
- Action
- Resolved and exposed as callable every tool the connected MCP server's own tools/list returned, confirmed directly by this entry's own execution of the real, unmodified extraction and filtering functions against a deterministic local fixture carrying a read only tool and a non read only tool
- Target resource
- Every tool the connected MCP server exposes, including a tool the caller's own malformed allowed_tools value was written to exclude
- Environment
- Any OpenAI Responses API deployment proxied through Praxis Proxy's own MCP tool resolver
- Credentials used
- unknown
- Privileges available
- Whatever the connected MCP server itself grants to a call it accepts, unconstrained further by Praxis Proxy once the malformed restriction resolves to the same value as no restriction
- Authority presented
- A present, non empty allowed_tools value that fails to match the documented array or filter object shape, or a filter object carrying a tool_names or read_only member of the wrong JSON type
- Authority required
- The narrower tool set, or read only constraint, the caller's own present allowed_tools value was written to express
- Applicable policy
- extract_allowed_tools's own three branch shape, confirmed directly by this entry's own reading and execution of the source: an absent field and a value matching neither the array nor the object branch both return AllowedTools::unrestricted(), the identical value
- Approval mechanism
- none
- Required approver
- unknown
- Independent approval
- no
- Action binding
- The rewritten request's own function tool definitions are computed from the AllowedTools value the malformed field produced; confirmed directly by this entry's own reading of rewrite_request_body and rewrite_tools_array, the caller's original, malformed field is never carried into the request Praxis Proxy forwards
- Sequence context
- Resolution happens once, before the model receives any tool definitions to select among; nothing in the source this entry read re evaluates the caller's own original allowed_tools value once the request has been rewritten
Impact
- Consequence
- Every tool the connected MCP server exposes becomes selectable by the model within the resolved request, including a tool the caller's own malformed allowed_tools value named or was otherwise written to exclude, confirmed directly by this entry's own execution of the real extraction and filtering functions against a local fixture carrying a read only tool named safe and a non read only tool named destructive
- Reach
- single
- Reversibility
- unknown
- Detectability
- No error, warning or rejected request is produced. The caller's request completes normally with a wider tool set exposed than the malformed field was written to allow, confirmed directly by this entry's own reading of the resolver's own return path and its own execution against the local fixture
- Propagation
- potential
- Recovery
- No merged fix was found on the repository's current default branch as of this entry. Issue 939 is accepted for milestone v0.4.0 but remains open and unclosed by a shipped change as of this entry's own verification.
Evidence
Primary sources
- Malformed MCP allowed_tools restrictions fail open to all resolved tools (praxis-proxy/ai (GitHub Issues))
Supporting sources
- openai_mcp_tool_resolve/mod.rs on the current default branch (extract_allowed_tools, extract_from_filter_object, AllowedTools, apply_allowed_tools_filter) (praxis-proxy/ai (GitHub, source))
- praxis-proxy/ai repository (current default branch head) (praxis-proxy/ai (GitHub, repository))
- Reproduction status
- Independently reproduced by Moona Intelligence. This session copied extract_allowed_tools, extract_from_filter_object, extract_string_list, the AllowedTools struct and apply_allowed_tools_filter, unaltered, from the repository's current default branch into a standalone program and ran them directly against a deterministic local fixture, a read only tool and a non read only tool, with no MCP server of any kind, production or otherwise, contacted. A bare string allowed_tools value, and each of a wrong typed tool_names and a wrong typed read_only member inside the documented filter object, were each confirmed to resolve identically to the field's own absence, while the documented array form correctly resolved only the one tool it named.
- Evidence state
- Reproduced
Known unknowns
- Whether any real Praxis Proxy deployment has been exploited using this mechanism.
- Whether the exact commit issue 939's own review cites, 3cfddbcbf4d6adcab02d8257bbdb272981c433c7, exists under a different remote, a private fork or a deleted branch this session's own read access could not reach.
- Whether a maintainer has begun work on a fix beyond accepting the issue into milestone v0.4.0.
Limitations
- This session's own execution used a standalone program copying the affected functions verbatim rather than compiling and running the full praxis-proxy/ai crate, whose own build requires dependencies and configuration outside this entry's own scope. The copied functions are unaltered from the source this session read, with no logic simplified or changed in the copy.
- This session's access to the repository is read only and unauthenticated; issue metadata (labels, milestone, state) was read through GitHub's own rendered page rather than its API, and could change after this entry's own verification date.
Claim provenance
- verified
Fetched directly on 7 September 2026. Confirmed opened the same day, open, carrying labels human reviewed, triage accepted and area ai, targeted at milestone v0.4.0, stating that a present value of the wrong top level type converts to AllowedTools::unrestricted and naming the affected file and line ranges.
The Caller Tried to Restrict the MCP Tools. The Parser Turned the Bad Restriction Into No Restriction at All. - verified
Downloaded directly through a shallow git clone of the repository's current default branch. Confirmed extract_allowed_tools's own three branch shape, extract_from_filter_object's own per member and_then chains, apply_allowed_tools_filter's own unfiltered return when no name list and no read only flag are set, and rewrite_request_body's own removal of the original MCP entry from the outgoing request.
The Caller Tried to Restrict the MCP Tools. The Parser Turned the Bad Restriction Into No Restriction at All. - verified
Confirmed directly as the current tip of the repository's default branch by this session's own shallow clone. This session separately attempted to resolve the commit issue 939's own review cites, 3cfddbcbf4d6adcab02d8257bbdb272981c433c7, and found no such commit against this repository, a discrepancy recorded rather than explained.
The Caller Tried to Restrict the MCP Tools. The Parser Turned the Bad Restriction Into No Restriction at All. - verified
This session copied extract_allowed_tools, extract_from_filter_object, extract_string_list, the AllowedTools struct and apply_allowed_tools_filter, unaltered, into a standalone program and ran them directly against a deterministic local fixture (a read only tool named safe and a non read only tool named destructive), with no MCP server of any kind contacted. Confirmed a bare string allowed_tools value, and each of a wrong typed tool_names and a wrong typed read_only filter object member, resolve both fixture tools as eligible, identically to the field's own absence, while the documented array form resolves only the one tool it names.
The Caller Tried to Restrict the MCP Tools. The Parser Turned the Bad Restriction Into No Restriction at All.
