Conversation
franciscojavierarceo
approved these changes
Sep 15, 2026
leseb
force-pushed
the
leseb/fix-958-from-pr-1166-approved
branch
3 times, most recently
from
September 18, 2026 08:38
3bd521c to
fcf60f8
Compare
leseb
marked this pull request as ready for review
September 18, 2026 13:01
Migrate the shared web-search `SearchClient` so that both `openai_web_search` (OpenAI Responses) and `anthropic_web_search` (Anthropic Messages) execute their provider requests through a prebuilt outbound filter chain via the reusable filtered-subrequest executor, instead of a raw client that bypasses the filter pipeline. Key changes: - Add an optional `outbound_chain` config reference (`ChainRef`) to the shared web-search config and both provider filters, matching `openai_file_search_callout`. When omitted it defaults to an empty inline chain (pure passthrough) via `default_outbound_chain`, so the callout still runs through the executor with every central protection but needs no configuration. When set, an inline chain binds for the IRR step placement used by the agentic loop, and a named top-level `filter_chains` entry (e.g. `web_search_outbound`) resolves where the placement allows it. - Execute the callout with praxis 0.5.6 native caller-staging: `StagedUpstream`/`StagedUpstreamFallback::from_prepared_target` plus `FilteredSubrequestExecutor::run_classified`, so the bound chain runs against a pinned, pre-resolved upstream target. - Remove the `allow_private_base_url` opt-in (config field, the `validate_base_url` private-address branch, per-filter SSRF unit tests, and example lines). SSRF is now enforced centrally by the executor at connect time, gated by `insecure_options.allow_private_upstreams` and propagated into the bound outbound chains. `base_url` is retained. - Defer provider credentials off the in-chain request: Brave's `x-subscription-token` and You's `x-api-key` are staged as host-wildcard `DeferredCredential`s and injected by the executor only after upstream resolution, so a chain filter cannot observe or retarget them. Combined with the staged-upstream pin, this also protects Tavily's body key against chain retargeting. - Fail the callout closed on `CalloutOutcome::ResponseTooLarge` (`SearchOutcome::Failed`) rather than surfacing an HTTP 413: a web-search callout feeds a tool result into the agentic loop and never returns a provider status to the client, so the model continues with a truthful "search unavailable" result. - Preserve provider translation, failure policy, deadlines, response limits, aggregate budgets, destination authority, DNS/SSRF, TLS/SNI, and `Host`. DNS multi-address failover is retained via the staged fallback. Unit, functional-integration, docs, and example coverage are included for both providers (outbound-filter execution proof, omitted-chain default passthrough, outbound-chain reject coverage, chain-retargeting credential-exfiltration proof, and config validation). Closes praxis-proxy#958 Refs praxis-proxy#879 Signed-off-by: Sébastien Han <seb@redhat.com>
leseb
force-pushed
the
leseb/fix-958-from-pr-1166-approved
branch
from
September 18, 2026 13:12
fcf60f8 to
0d54528
Compare
leseb
enabled auto-merge
September 18, 2026 13:13
The agentic-loop example config already declares `allow_private_upstreams: true` in its `insecure_options`, so the test helpers that additionally injected the key produced a duplicate field. praxis-core's strict YAML parser rejects duplicate keys, failing the config load with "insecure_options: duplicate field allow_private_upstreams" and breaking the coverage and vLLM Responses integration suites (regression latent in the outbound-chain migration that replaced the removed per-filter `allow_private_base_url` opt-in with an `insecure_options` injection). Remove the now-redundant injection from both Rust helpers (`load_web_search_config`, `load_unified_dispatch_config`) and the Python `_write_agentic_config`, replacing each with a comment noting the operator opt-in already ships in the base config. The `_write_web_search_chat_streaming_config` injection is retained because its base config (web-search-chat-completions.yaml) does not declare the key. Refs praxis-proxy#958 Signed-off-by: Sébastien Han <seb@redhat.com>
github-merge-queue
Bot
removed this pull request from the merge queue due to no response for status checks
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrates the shared web-search
SearchClientso that bothopenai_web_search(OpenAI Responses) and
anthropic_web_search(Anthropic Messages) executetheir provider requests through a prebuilt outbound filter chain via the
reusable filtered-subrequest executor, instead of a raw client that bypassed
the filter pipeline. This is part of epic #879 (per-filter SSRF opt-in removal).
What changed
outbound_chainconfig reference to theshared web-search config and both provider filters. Because these filters run
as IRR steps whose step-pipeline chain map is empty, the chain must be
inline; named top-level
filter_chainsrefs only resolve at non-IRRplacements.
allow_private_base_url(config field, thevalidate_base_urlprivate-address branch, per-filter SSRF unit tests, andexample lines). SSRF is now enforced by the executor at connect time and
gated by
insecure_options.allow_private_upstreams, propagated into thebound outbound chains via
set_allow_private_upstreams.base_urlis kept.address, downstream TLS, peer identity, request start) and the outbound
depth into the executor. Depth is read from the IRR-owned
IterationState,not from the
x-praxis-*depth header (which the IRR strips before buildingthe step context).
request: Brave's
x-subscription-tokenand You'sx-api-keyare staged ashost-wildcard
DeferredCredentials and injected by the executor only afterupstream resolution, so a chain filter cannot observe or retarget them.
Combined with the staged-upstream pin, this also protects Tavily's body key
against chain retargeting.
aggregate budgets, destination authority, DNS/SSRF, TLS/SNI, and
Host. DNSmulti-address failover is retained via the staged fallback.
Testing
Unit, functional-integration, docs, and example coverage for both providers:
callout) for OpenAI and Anthropic.
move a staged credential to an alternate backend).
outbound_chainfails the build).Dependency
Consumes the chain-binding infrastructure released in praxis-proxy 0.5.5
(
FilteredSubrequestExecutorchain binding,StagedUpstream/StagedUpstreamFallback, deferred credentials, IRR chain-aware step build).The dependency bump to 0.5.5 is included in this PR.
Closes #958
Refs #879