- 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)
76 lines
1.9 KiB
Go
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()
|
|
}
|