mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-23 19:16:14 +00:00
fix(security): remove credential-log bypass and sanitise header values in proxy
Two alerts at proxy.go:87: - go/clear-text-logging (alert 294): the UnsafeLogCredentialHeaders escape hatch allowed credential-bearing headers (Authorization, Cookie, …) to reach log.Printf in plaintext when LOG_PROXY_CREDENTIALS=true. CodeQL traces the taint regardless of the conditional. Remove UnsafeLogCredentialHeaders entirely. The field, env-var init, and the 'No redaction' branch in formatHeaders are all deleted. Credentials are now always redacted unconditionally. Developers who need to inspect live credentials can use a tool like mitmproxy or Wireshark instead. - go/log-injection (alert 295): header values assembled by formatHeaders were passed to log.Printf without newline stripping, allowing a malicious response to inject fake log lines. Apply sanitizeLog(val) to every non-redacted header value before it is added to the string builder. Redacted values stay as the literal string "[REDACTED]" which needs no further sanitisation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
79eecb4c2e
commit
e6bfcd1265
@@ -36,15 +36,6 @@ type LoggingProxy struct {
|
|||||||
RecordEnabled bool
|
RecordEnabled bool
|
||||||
MaxBodySize int64
|
MaxBodySize int64
|
||||||
Recorder *Recorder
|
Recorder *Recorder
|
||||||
|
|
||||||
// UnsafeLogCredentialHeaders disables the otherwise-unconditional
|
|
||||||
// redaction of credential-bearing headers (Authorization, Cookie, …) in
|
|
||||||
// LogRequest / LogResponse output. This is an explicit
|
|
||||||
// "I-know-what-I'm-doing" escape hatch for local debugging only — never
|
|
||||||
// enable it in production. Defaults to false; the env-var
|
|
||||||
// LOG_PROXY_CREDENTIALS=true flips it on so a developer can opt in
|
|
||||||
// without recompiling.
|
|
||||||
UnsafeLogCredentialHeaders bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLoggingProxy creates a lightweight logger for HTTP requests/responses.
|
// NewLoggingProxy creates a lightweight logger for HTTP requests/responses.
|
||||||
@@ -53,7 +44,6 @@ func NewLoggingProxy(_ string, redact bool) *LoggingProxy {
|
|||||||
return &LoggingProxy{
|
return &LoggingProxy{
|
||||||
Redact: redact,
|
Redact: redact,
|
||||||
LogBody: os.Getenv("LOG_PROXY_BODY") == "true",
|
LogBody: os.Getenv("LOG_PROXY_BODY") == "true",
|
||||||
UnsafeLogCredentialHeaders: os.Getenv("LOG_PROXY_CREDENTIALS") == "true",
|
|
||||||
MaxBodySize: 1024 * 10, // 10KB default limit for logging
|
MaxBodySize: 1024 * 10, // 10KB default limit for logging
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +55,7 @@ func (lp *LoggingProxy) SetRecorder(r *Recorder) {
|
|||||||
|
|
||||||
// LogRequest prints an abbreviated request with optional header/body redaction.
|
// LogRequest prints an abbreviated request with optional header/body redaction.
|
||||||
func (lp *LoggingProxy) LogRequest(r *http.Request) {
|
func (lp *LoggingProxy) LogRequest(r *http.Request) {
|
||||||
headers := formatHeaders(r.Header, lp.Redact, lp.UnsafeLogCredentialHeaders)
|
headers := formatHeaders(r.Header, lp.Redact)
|
||||||
|
|
||||||
bodyStr := "[HIDDEN]"
|
bodyStr := "[HIDDEN]"
|
||||||
|
|
||||||
@@ -89,7 +79,7 @@ func (lp *LoggingProxy) LogRequest(r *http.Request) {
|
|||||||
|
|
||||||
// LogResponse prints an abbreviated response with optional header/body redaction.
|
// LogResponse prints an abbreviated response with optional header/body redaction.
|
||||||
func (lp *LoggingProxy) LogResponse(r *http.Response) {
|
func (lp *LoggingProxy) LogResponse(r *http.Response) {
|
||||||
headers := formatHeaders(r.Header, lp.Redact, lp.UnsafeLogCredentialHeaders)
|
headers := formatHeaders(r.Header, lp.Redact)
|
||||||
|
|
||||||
bodyStr := "[HIDDEN]"
|
bodyStr := "[HIDDEN]"
|
||||||
|
|
||||||
@@ -115,24 +105,24 @@ func (lp *LoggingProxy) LogResponse(r *http.Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatHeaders(h http.Header, redact, unsafeLogCredentials bool) string {
|
func formatHeaders(h http.Header, redact bool) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
// In Go, http.Header is a map[string][]string.
|
// In Go, http.Header is a map[string][]string.
|
||||||
// Iterating over the map directly allows us to see the actual keys
|
// Iterating over the map directly allows us to see the actual keys
|
||||||
// stored in the map, which might not be canonical if set directly.
|
// stored in the map, which might not be canonical if set directly.
|
||||||
for k, vv := range h {
|
for k, vv := range h {
|
||||||
val := strings.Join(vv, ", ")
|
val := strings.Join(vv, ", ")
|
||||||
// Credentials (Authorization, Cookie, …) are redacted by default.
|
// Credential-bearing headers are always redacted; the broader Redact
|
||||||
// unsafeLogCredentials lifts that floor entirely — explicit opt-in
|
// toggle covers additional sensitive fields. Header values are passed
|
||||||
// for local debugging only. When the floor is in place, the
|
// through sanitizeLog to strip any embedded newlines before they reach
|
||||||
// caller's broader Redact toggle adds further coverage.
|
// the log sink (go/log-injection, alert 295).
|
||||||
switch {
|
switch {
|
||||||
case unsafeLogCredentials:
|
|
||||||
// No redaction.
|
|
||||||
case isAlwaysSensitive(k):
|
case isAlwaysSensitive(k):
|
||||||
val = "[REDACTED]"
|
val = "[REDACTED]"
|
||||||
case redact && isSensitive(k):
|
case redact && isSensitive(k):
|
||||||
val = "[REDACTED]"
|
val = "[REDACTED]"
|
||||||
|
default:
|
||||||
|
val = sanitizeLog(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&sb, " %s: %s\n", k, val)
|
fmt.Fprintf(&sb, " %s: %s\n", k, val)
|
||||||
|
|||||||
Reference in New Issue
Block a user