GitHub Security Advisory: GHSA-9857-6mw7-fq2m
Package: gix-transport (RUST)
Severity: MODERATE (CVSS 6.8)
CWE: CWE-522: Insufficiently Protected Credentials
Vulnerable versions: >= 0.25.4, <= 0.55.0
Patched in: 0.56.0

Summary: gix-transport: HTTP credentials leaked to redirected host in curl backend

Details:
## 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://") {
    retu