diff --git a/.changeset/redirect-policy.md b/.changeset/redirect-policy.md deleted file mode 100644 index c577594..0000000 --- a/.changeset/redirect-policy.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@smooai/fetch': minor ---- - -Make redirect handling configurable in all five languages - -Redirects were followed unconditionally everywhere, and TypeScript went further: -`merge({}, init, { redirect: 'follow' })` put the literal last, so a caller -passing `redirect: 'manual'` had it **silently overwritten**. Python hardcoded -`follow_redirects=True` into the httpx kwargs; Rust, Go and .NET set nothing and -inherited platform defaults that follow up to 10 hops. - -That is a security gap, not just an ergonomic one. A caller who resolves a -hostname and checks it against an SSRF allowlist has that guard defeated by a 302 -to an internal address, because the check was performed on the original host. And -RFC 8461 forbids following redirects when fetching an MTA-STS policy. - -- **TypeScript** — `redirect` is honoured (defaults first, caller last) -- **Python** — `FetchOptions.follow_redirects` -- **Rust** — `RequestInit.follow_redirects: Option` (`None` inherits, so a - client-level default survives a per-request `..Default::default()`) -- **Go** — `ClientBuilder.WithFollowRedirects`, applied to a caller-supplied - `*http.Client` too -- **.NET** — `SmooFetchOptions.FollowRedirects` / `WithFollowRedirects` - -Honouring the option was not sufficient on its own: in TS, Rust, Go and .NET a -3xx is neither "ok" nor "redirected", so it was raised as an error and the option -was undone a line later. Each now returns a deliberately-unfollowed 3xx as an -ordinary response. Defaults are unchanged — everything still follows unless a -caller says otherwise. diff --git a/CHANGELOG.md b/CHANGELOG.md index ee43d0c..ecd7574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # @smooai/fetch +## 3.7.0 + +### Minor Changes + +- 43ca80a: Make redirect handling configurable in all five languages + + Redirects were followed unconditionally everywhere, and TypeScript went further: + `merge({}, init, { redirect: 'follow' })` put the literal last, so a caller + passing `redirect: 'manual'` had it **silently overwritten**. Python hardcoded + `follow_redirects=True` into the httpx kwargs; Rust, Go and .NET set nothing and + inherited platform defaults that follow up to 10 hops. + + That is a security gap, not just an ergonomic one. A caller who resolves a + hostname and checks it against an SSRF allowlist has that guard defeated by a 302 + to an internal address, because the check was performed on the original host. And + RFC 8461 forbids following redirects when fetching an MTA-STS policy. + - **TypeScript** — `redirect` is honoured (defaults first, caller last) + - **Python** — `FetchOptions.follow_redirects` + - **Rust** — `RequestInit.follow_redirects: Option` (`None` inherits, so a + client-level default survives a per-request `..Default::default()`) + - **Go** — `ClientBuilder.WithFollowRedirects`, applied to a caller-supplied + `*http.Client` too + - **.NET** — `SmooFetchOptions.FollowRedirects` / `WithFollowRedirects` + + Honouring the option was not sufficient on its own: in TS, Rust, Go and .NET a + 3xx is neither "ok" nor "redirected", so it was raised as an error and the option + was undone a line later. Each now returns a deliberately-unfollowed 3xx as an + ordinary response. Defaults are unchanged — everything still follows unless a + caller says otherwise. + ## 3.6.2 ### Patch Changes diff --git a/dotnet/SmooAI.Fetch/SmooAI.Fetch.csproj b/dotnet/SmooAI.Fetch/SmooAI.Fetch.csproj index c69172a..8a57654 100644 --- a/dotnet/SmooAI.Fetch/SmooAI.Fetch.csproj +++ b/dotnet/SmooAI.Fetch/SmooAI.Fetch.csproj @@ -10,7 +10,7 @@ $(NoWarn);CS1591 SmooAI.Fetch - 3.6.2 + 3.7.0 SmooAI SmooAI Resilient HTTP client for .NET with Polly-based retry, timeouts, typed JSON responses, and auth token injection. Port of @smooai/fetch. diff --git a/go/fetch/version.go b/go/fetch/version.go index e252ca2..1f28cf2 100644 --- a/go/fetch/version.go +++ b/go/fetch/version.go @@ -1,4 +1,4 @@ package fetch // Version is the current version of the smooai-fetch Go package. -const Version = "3.6.2" +const Version = "3.7.0" diff --git a/package.json b/package.json index fc9ae2d..0cb113d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@smooai/fetch", - "version": "3.6.2", + "version": "3.7.0", "description": "A powerful fetch client library built on top of the native `fetch` API, designed for both Node.js and browser environments. Features built-in support for retries, timeouts, rate limiting, circuit breaking, and Standard Schema validation.", "homepage": "https://github.com/SmooAI/fetch#readme", "bugs": { diff --git a/python/pyproject.toml b/python/pyproject.toml index 38ead0d..439bef3 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "smooai-fetch" -version = "3.6.2" +version = "3.7.0" description = "A resilient HTTP fetch client with retries, timeouts, rate limiting, and circuit breaking." # readme = "README.md" authors = [{ name = "SmooAI", email = "brent@smooai.com" }] diff --git a/python/src/smooai_fetch/__init__.py b/python/src/smooai_fetch/__init__.py index bd49d66..0eeba12 100644 --- a/python/src/smooai_fetch/__init__.py +++ b/python/src/smooai_fetch/__init__.py @@ -4,7 +4,7 @@ and circuit breaking. """ -__version__ = "3.6.2" +__version__ = "3.7.0" # Core client # Builder diff --git a/rust/fetch/Cargo.lock b/rust/fetch/Cargo.lock index 3d38325..6bcaa42 100644 --- a/rust/fetch/Cargo.lock +++ b/rust/fetch/Cargo.lock @@ -1230,7 +1230,7 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smooai-fetch" -version = "3.6.2" +version = "3.7.0" dependencies = [ "opentelemetry", "opentelemetry_sdk", diff --git a/rust/fetch/Cargo.toml b/rust/fetch/Cargo.toml index 9052d92..1196be3 100644 --- a/rust/fetch/Cargo.toml +++ b/rust/fetch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "smooai-fetch" -version = "3.6.2" +version = "3.7.0" edition = "2021" description = "A resilient HTTP fetch client with retries, timeouts, rate limiting, and circuit breaking." license = "MIT"