NextMyDay/internal/core/httpx/redact.go
Claus Lohmar d11e14295b feat(core): add the store, identity plumbing and the door (SPEC-003/004)
- store: SQLite (WAL, foreign keys ON, CGO-free driver) + the embedded
  migration runner; each module's migrations are name-prefixed (D-012)
- audit: the append-only audit log; the actor threads via context (R-1)
- users: the user_id/GUID/email anchors (D-013)
- config: the YAML settings file (D-035) + validation
- httpx: cookies · loopback detection · trusted-proxy XFF · CSRF · security
  headers · redaction
- middleware: correlation id · audit · rate limiting
- mail: the invite/recovery mailer seam
- server: the core DOOR — the resolution switch, the dispatch registry
  (RegisterModule), the role gates, /setup · enroll/login · recovery flows,
  the reserved `static` workflow, and the loopback-only bootstrap (D-025)
2026-10-14 10:53:51 +00:00

76 lines
1.9 KiB
Go

package httpx
import (
"net/url"
"strings"
)
// Redacted is the placeholder that replaces a credential in logs.
const Redacted = "<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/<cred>)
// 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()
}