mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
fix(config): strip userinfo from a scheme-less base URL before fingerprinting
`base_url_fingerprint`'s doc comment states that "userinfo, query
credentials, and fragments never enter the digest function at all". The
scheme-bearing branch of `secret_free_fingerprint_input` honours that: it
splits the authority and drops everything before `@`. The fall-through
branch — taken by any `base_url` typed without a scheme, which is a shape
users do write — only split off `?`/`#`:
secret_free_fingerprint_input("u:p4ssw0rd@host/v1") == "u:p4ssw0rd@host/v1"
so the password was hashed verbatim into a value that is stored in the
catalog cache, embedded in `LivePricingDefect::EndpointMismatch`
receipts, and logged as a non-secret. The doc comment directly above it
promised the opposite — the same shape as the "never contains a secret"
comment found over a URL interpolation earlier today.
The fall-through now strips userinfo exactly as the scheme branch does.
That also makes the branch's `unwrap_or(REDACTED)` reachable in spirit:
`split` always yields at least one (possibly empty) piece, so it never
fired, and an empty base URL was fingerprinting SHA-256 of the empty
string (`e3b0c442…`) rather than the redacted sentinel. An empty
authority now returns the sentinel, matching the scheme branch.
This changes the fingerprint of scheme-less and empty base URLs. They are
cache keys only, so the cost is one refetch per affected scope.
Evidence: `fingerprint_strips_userinfo_from_a_scheme_less_base_url` and
`fingerprint_of_an_empty_base_url_is_the_redacted_constant` both fail on
the pre-change fall-through and pass after.
`cargo test -p codewhale-config --lib` 578 pass.
Implemented with agent assistance.
Signed-off-by: Hunter Bown <[email protected]>
This commit is contained in:
1 parent
8e105845de
commit
3881254912
2 files changed
+39
-1
No files matched your search
@@ -711,7 +711,19 @@ fn secret_free_fingerprint_input(base_url: &str) -> String {
|
||||
.unwrap_or_default();
|
||||
return normalize_base_url(&format!("{scheme}://{authority}{path}"));
|
||||
}
|
||||
normalize_base_url(trimmed.split(['?', '#']).next().unwrap_or(REDACTED))
|
||||
// Scheme-less input still has an authority, and it can still carry
|
||||
// `user:pass@` userinfo. Strip it exactly as the scheme branch does, so the
|
||||
// digest input never contains a credential.
|
||||
let without_query = trimmed.split(['?', '#']).next().unwrap_or_default();
|
||||
let authority_end = without_query.find('/').unwrap_or(without_query.len());
|
||||
let authority = &without_query[..authority_end];
|
||||
let authority = authority
|
||||
.rsplit_once('@')
|
||||
.map_or(authority, |(_, host)| host);
|
||||
if authority.is_empty() {
|
||||
return REDACTED.to_string();
|
||||
}
|
||||
normalize_base_url(&format!("{authority}{}", &without_query[authority_end..]))
|
||||
}
|
||||
|
||||
fn normalize_base_url(base_url: &str) -> String {
|
||||
|
||||
@@ -306,6 +306,32 @@ fn fingerprint_never_hashes_secret_bearing_url_text() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fingerprint_strips_userinfo_from_a_scheme_less_base_url() {
|
||||
// A base_url typed without a scheme took the fall-through branch, which
|
||||
// only split off `?`/`#` — so `user:pass@host` went into SHA-256 verbatim,
|
||||
// against the documented "userinfo never enters the digest function".
|
||||
let expected = base_url_fingerprint("api.example.com/v1");
|
||||
for url in [
|
||||
"user:[email protected]/v1",
|
||||
"user:[email protected]/v1",
|
||||
"[email protected]/v1",
|
||||
] {
|
||||
assert_eq!(base_url_fingerprint(url), expected, "{url}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fingerprint_of_an_empty_base_url_is_the_redacted_constant() {
|
||||
// The fall-through's `unwrap_or(REDACTED)` never fired — `split` always
|
||||
// yields at least one (possibly empty) piece — so an empty base URL
|
||||
// fingerprinted the empty string instead of the redacted sentinel.
|
||||
let redacted = base_url_fingerprint("ftp://api.example.com");
|
||||
for url in ["", " ", "?api_key=secret"] {
|
||||
assert_eq!(base_url_fingerprint(url), redacted, "{url:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ttl_marks_entries_stale_and_excludes_them_from_fresh() {
|
||||
let fp = base_url_fingerprint("https://api.example.com");
|
||||
|
||||
Reference in new issue
Block a user