package httpx import ( "net/url" "strings" ) // Redacted is the placeholder that replaces a credential in logs. const Redacted = "" // sensitiveNames are the segment / parameter names whose VALUE is treated as a // credential (SPEC-003 §8, A8/A11). The GUID is a public username (D-001) and // is deliberately NOT in this set — it is never redacted. var sensitiveNames = map[string]bool{ "token": true, "secret": true, "key": true, "code": true, "password": true, "passwd": true, "invite": true, "reset": true, "session": true, "csrf": true, } // RedactPath masks credential-bearing path segments so the access log can // record the raw path shape without leaking a credential (SPEC-003 §8). // // A segment that follows a sensitive name (e.g. /setup/token/) // A `name=value` segment whose name is sensitive (e.g. /x/token=abc) // // The GUID is public and is preserved verbatim. Segment count is preserved so // the logged path stays structurally comparable. func RedactPath(p string) string { if p == "" { return p } segs := strings.Split(p, "/") redactNext := false for i, seg := range segs { if redactNext { segs[i] = Redacted redactNext = false continue } if name, _, ok := strings.Cut(seg, "="); ok { if sensitiveNames[strings.ToLower(name)] { segs[i] = name + "=" + Redacted continue } } if sensitiveNames[strings.ToLower(seg)] { redactNext = true } } return strings.Join(segs, "/") } // RedactQuery masks the values of sensitive query parameters. An unparseable // query is redacted wholesale rather than risking a leak. func RedactQuery(raw string) string { if raw == "" { return "" } vals, err := url.ParseQuery(raw) if err != nil { return Redacted } for name := range vals { if sensitiveNames[strings.ToLower(name)] { vals.Set(name, Redacted) } } return vals.Encode() }