[![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) …ave the origin The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) AI lite review requested due to automatic review settings [August 4, 2026 21:50](#event-28976425627) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) [![lukekim](https://avatars.githubusercontent.com/u/80174?s=60&v=4)](/lukekim) ** lukekim ** previously approved these changes [Aug 5, 2026](#pullrequestreview-4860820008) [![@lukekim](https://avatars.githubusercontent.com/u/80174?s=40&u=14c19f2d8c543ab1c47c44212f59d8e88160680f&v=4)](/lukekim) [![@phillipleblanc](https://avatars.githubusercontent.com/u/879445?s=40&v=4)](/phillipleblanc) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) Resolve the conflict in bin/spice/src/context.rs by deferring to the shared redirect policy trunk now provides. Both sides added a helper next to the same constants: this branch a local same_origin_redirect_policy, trunk a normalize_credential. Trunk also landed spice_cloud_client::redirect::same_origin_redirect_policy, so the local copy became a duplicate -- and the weaker of the two. It compares scheme, host and effective port by hand, which reads two hostless URLs (file:///a and file:///b) as one origin; the shared helper defers to Url::origin, where a scheme with no host gets an opaque origin that never compares equal and so is always refused. Drop the local copy, import the shared one, and keep normalize_credential. What remains of this branch is the part still unique to it: the CLI's RuntimeContext client is built with that policy, and a client that cannot be built is now an error rather than unwrap_or_default -- a defaulted client would silently carry no policy at all. The unit test over the removed local helper goes with it (the shared crate covers the origin comparison). In its place are two tests through RuntimeContext::with_args, so the client under test is the one RuntimeContext::new builds: a cross-origin 307 is refused and the off-origin listener is never contacted, and a same-origin redirect is still followed. [Copilot](/apps/copilot-pull-request-reviewer) AI review requested due to automatic review settings [August 5, 2026 14:04](#event-29013428612) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) [![@lukekim](https://avatars.githubusercontent.com/u/80174?s=40&u=14c19f2d8c543ab1c47c44212f59d8e88160680f&v=4)](/lukekim) [![@lukekim](https://avatars.githubusercontent.com/u/80174?s=40&u=14c19f2d8c543ab1c47c44212f59d8e88160680f&v=4)](/lukekim) [lukekim](/lukekim) deleted the fix/12495-same-origin-redirect-policy branch [August 6, 2026 15:03](#event-29075717295) [pull](/apps/pull) Bot pushed a commit to TheRakeshPurohit/spiceai that referenced this pull request [Aug 6, 2026](#ref-commit-9ca1124) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) …ave the origin ([spiceai#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [spiceai#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [spiceai#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 7, 2026](#ref-commit-d40069f) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 7, 2026](#ref-commit-e1f77a3) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 10, 2026](#ref-commit-6360674) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 10, 2026](#ref-commit-bf950ef) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 10, 2026](#ref-commit-44d31ed) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 11, 2026](#ref-commit-bced5e5) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 11, 2026](#ref-commit-fd251b8) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) Merged 3 tasks done [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 12, 2026](#ref-commit-6981a4d) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 12, 2026](#ref-commit-b7f9066) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) [github-actions](/apps/github-actions) Bot pushed a commit that referenced this pull request [Aug 12, 2026](#ref-commit-8c28093) [![@grokspice](https://avatars.githubusercontent.com/u/312203538?s=40&v=4)](/grokspice) ![Spice Snapshot Update Bot](https://github.githubassets.com/images/gravatars/gravatar-user-420.png) …ave the origin ([#12503](https://github.com/spiceai/spiceai/pull/12503)) The CLI sends its API key as an X-API-Key header on a reqwest client that used the default redirect policy, which follows up to 10 redirects. reqwest sanitises headers when a redirect changes origin, but only the standard set -- Authorization, Cookie, Cookie2, Proxy-Authorization, WWW-Authenticate. A custom header is not in it, so a runtime, proxy or ingress answering with a cross-origin Location had the key forwarded to whatever that named. Follow same-origin redirects only, comparing scheme, host and effective port so that http://host and http://host:80 remain one origin. A cross-origin Location stops instead, which hands the 3xx back to the caller as a response rather than failing the transport. The policy is consulted before the redirected request goes out, so the credential never reaches the new origin. Set on the context client, which every request-building call site shares: the get/post/post_json helpers, and commands/nsql/mod.rs, search.rs, chat.rs, trace.rs, acceleration.rs and add.rs, none of which builds its own client. `spice query` and `spice nsql analyze` are NOT covered: they build their own credentialed client from the pinned spiceai SDK, which pins its own reqwest, so they cannot inherit this policy. Whether they are exposed depends on which header the SDK uses for the key -- filed as [#12502](https://github.com/spiceai/spiceai/issues/12502), since it needs an SDK-side change either way. The client build no longer falls back with unwrap_or_default(). That fallback is the wrong shape for this fix: a default reqwest::Client has the default redirect policy, so the one path that silently produced a client would produce the leaking one. RuntimeContext::new already returned Result, so it propagates Error::HttpClientBuild instead. The depth bound is compared the way Policy::limited compares it -- previous() counts the initial URL, so the bound is exclusive. Fixes [#12495](https://github.com/spiceai/spiceai/issues/12495) 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](https://github.co/hiddenchars) [Show hidden characters]({{ revealButtonHref }})