security: reject foreign-host URLs in `api` to prevent token leak
Reject foreign-host URLs in api to prevent bearer-token leak
Severity: High ā credential exfiltration primitive. (Threat: T1 malicious/compromised API target.)
parsePath only extracted the path from absolute URLs whose first segment was a numeric account ID. Any other absolute URL (e.g. https://evil.example/projects.json) failed the regex, fell through the no-op TrimPrefix, and was returned verbatim to app.Account().Get/Post/Put/Delete ā the SDK then attached the Authorization: Bearer header and sent the request to the foreign host.
Fix
parsePath now returns (string, error) and accepts an absolute http(s):// URL only when its host matches the API base host we're authenticated against (case-insensitive). URLs on any other host are rejected before any request is made, so the OAuth token can never be sent to a foreign host. Same-host URLs are used as given: the path (plus query) is passed through with no account-segment shape requirement ā a leading /<account-id> segment, if present, is dropped since the SDK re-prefixes the configured account. Error propagated through all four api handlers.
Tests
api_test.go adds a table for relative / leading-slash / valid-account / foreign-host inputs (TestParsePathRejectsForeignHosts). Legitimate https://3.basecampapi.com/999/... still extracts correctly.
Part 1/6 of a stacked security-hardening series (audit remediation). This one is independent and safe to land alone; the rest stack on top in order. Base: main.
š Stack (merge bottom-up)
- #478 ā reject foreign-host URLs in
api(basemain) - #479 ā strip ANSI/OSC escapes from output
- #480 ā harden OAuth discovery / token endpoints
- #481 ā close config trust-boundary gaps + completion gate
- #482 ā tighten config-dir perms + plugin scope argv
- #483 ā bump toolchain/x-net + CI gates
Each is independent except #482 depends on #481 (shared root.go). #478 can land first/alone.