fix(ai): disable redirects on worker AI provider client (GHSA-5q4v) - #10122
Conversation
The worker AI request path issued provider requests with the shared HTTP_CLIENT, which follows up to 10 redirects without revalidating each hop. SSRF validation on the provider base_url is single-shot, so a public base_url could 3xx the worker into a private/internal host (e.g. cloud metadata), bypassing the private-endpoint protection. The API proxy was already hardened in #9370; the worker path was missed. Add a dedicated AI_HTTP_CLIENT with redirects disabled and use it for the user-controlled provider endpoint, mirroring the API proxy client. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @rubenfiszel's task in 1m 17s —— View job Review — GHSA-5q4v worker AI SSRF redirect fixGood to merge. I verified the fix against the codebase. It's a correct, well-scoped mirror of the already-hardened API proxy path. What I checked
AGENTS.md compliance
Test coverageBackend-only change. A Rust regression test ( No further manual verification strictly required; the optional e2e in the PR test plan (provider |
Pi ReviewGood to merge. The fix correctly mirrors the API proxy hardening from #9370 by creating a redirect-disabled No issues found. Checked for bugs, security, and AGENTS.md compliance. Test coverage
|
Codex Reviewcc @rubenfiszel Mergeable, but should ideally address nits: duplicated overlong redirect invariant comments
Test coverageThe new Tokio unit test directly verifies that Manual verification remains: run an AI agent job against a valid public provider URL that redirects to a controlled private listener, confirming the redirected target receives no request or forwarded headers, then verify a direct provider endpoint still streams normally. |
Off by default (redirects stay disabled). When set, restores redirect following on the AI HTTP client for debugging non-standard/self-hosted gateways, with a startup warning that it weakens SSRF protection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Now I have the full picture. Here's my review. Pi ReviewGood to merge. The fix correctly mirrors the API proxy hardening from #9370 by creating a redirect-disabled Minor observation (not blocking):
Test coverage
|
Codex Reviewcc @rubenfiszel Mergeable, but should ideally address nits: escape hatch leaves the worker call-site invariant false
Test coverageThe Tokio unit test covers the secure default. The new redirect-enabled branch lacks unit coverage, and the existing test unconditionally expects a 302, so it is incompatible with test environments setting the new override. No worker-level integration test is included; no DB or API integration coverage is needed. Manually run an AI agent against a provider redirecting to a controlled listener. By default, the listener should receive nothing. With the override enabled, it should receive the redirected request and the worker should emit the warning. |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pi Reviewcc @rubenfiszel Mergeable, but should ideally address nits: duplicated overlong redirect invariant comments What I checked
Findings[P2] duplicated overlong redirect invariant comments — The
Both this comment and the worker call-site comment explain the single-shot SSRF validation rationale — that a validated public base_url can 3xx into a private host. The worker comment already ends with "See AI_HTTP_CLIENT," so the detailed rationale belongs at the Test coverage
|
Codex Reviewcc @rubenfiszel Mergeable, but should ideally address nits: duplicated overlong redirect invariant comments
Test coverageThe added Tokio test directly covers the secure default redirect policy. The escape-hatch branch and worker call site lack integration coverage; tests were not executed during this read-only review. Manually run an AI agent against a provider that redirects to a controlled private listener. By default, the listener should receive no request and the worker should surface the redirect. With the override enabled, the redirect should be followed and the warning emitted. |
Anchor the SSRF rationale to ALLOW_AI_BASE_URL_REDIRECTS (the knob that would break it) and shorten the AI_HTTP_CLIENT and call-site comments to avoid restating it at multiple sites (AGENTS.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Fixes the SSRF advisory GHSA-5q4v-c4v3-v7wr. A low-privileged AI-provider resource writer could bypass private-endpoint SSRF protection because the worker AI request path followed HTTP redirects into private hosts.
SSRF validation on a provider
base_urlis single-shot (AIProvider::get_base_url→validate_url_for_ssrf), but the worker then issued the request with the sharedwindmill_common::utils::HTTP_CLIENT, which sets no redirect policy. reqwest's default follows up to 10 redirects without revalidating each hop, so a public-lookingbase_urlthat 3xx-redirects to127.0.0.1/169.254.169.254(cloud metadata) got followed into the internal host, forwarding attacker-chosen headers.ALLOW_PRIVATE_AI_BASE_URLSbeing off did not help.The API proxy was already hardened in #9370 (
windmill-api/src/ai.rsusesreqwest::redirect::Policy::none()); the worker path was missed. This PR mirrors that fix.Changes
AI_HTTP_CLIENTlazy_static inbackend/windmill-ai/src/utils.rs, built viaconfigure_client(...).redirect(reqwest::redirect::Policy::none())(also preservesFORCE_IPV4). Redirects are disabled unconditionally, which holds even forALLOW_PRIVATE_AI_BASE_URLSdeployments since AI APIs respond directly.backend/windmill-worker/src/ai_executor.rsbuild_http_request— the only worker call targeting the user-controlled providerendpoint— now usesAI_HTTP_CLIENTinstead of the redirect-followingHTTP_CLIENT.302to the link-local metadata address and asserts the client surfaces the302rather than following it.Left unchanged (verified internal-only)
ai_executor.rs:397— Hub-script fetch against Windmill's own hub, not the userbase_url.ai/utils.rsOAuth token refresh — targetswindmill_common::BASE_URL(internal).handle_google_ai_chat_proxy/handle_google_ai_models_proxytake a client param and are only called fromwindmill-api/src/ai.rswith the already-fixed API client. The worker's Google path goes throughbuild_http_request, so it is covered. Bedrock uses the AWS SDK (region-scoped endpoints), a separate surface.Test plan
cargo check -p windmill-ai -p windmill-worker --tests --features quickjs— cleancargo test -p windmill-ai ai_http_client_does_not_follow_redirects— passesbase_urlreturns a 3xx to an internal host; confirm the worker AI step surfaces the redirect response instead of connecting onward.No SQL / API-surface changes (no
update_sqlx/ openapi regeneration needed). No*_ee.rsfiles touched, so no EE companion PR.🤖 Generated with Claude Code
Summary by cubic
Disables HTTP redirects for worker requests to user‑configured AI providers to close the SSRF bypass (GHSA-5q4v-c4v3-v7wr). Redirects now return 3xx to the caller instead of being followed to private hosts.
AI_HTTP_CLIENTwith redirects disabled (reqwest::redirect::Policy::none()) and switched worker providerendpointrequests to use it, mirroring the API proxy policy.ALLOW_AI_BASE_URL_REDIRECTSescape hatch to re-enable redirects for debugging; default off with a startup warning. Condensed comments and anchored the SSRF rationale to this override.Written for commit a7f5693. Summary will update on new commits.