go-log/SECURITY_ATTACK_VECTOR_MAPPING.md
Virgil 318a948a33 docs(security): add attack vector mapping
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-23 13:01:11 +00:00

14 KiB

Security Attack Vector Mapping

  • CLAUDE.md reviewed.
  • CODEX.md was not present in this repository.
  • Scope: exported API entry points that accept caller-controlled data, plus direct OS/environment reads. Public field-based inputs (Logger.Style*, Err.Op, Err.Msg, Err.Err, Err.Code) are called out in the relevant rows because they are consumed by methods rather than setter functions.
  • Common sink: (*Logger).log in log.go:169-242 writes the final log line with fmt.Fprintf(output, "%s %s %s%s\n", ...) at log.go:242.
  • Existing controls in the common sink: string values in keyvals are %q-escaped at log.go:234-236, exact-match redaction is applied with slices.Contains(redactKeys, keyStr) at log.go:227-231, and Err-derived op / stack context is auto-added at log.go:178-211.
  • Gaps in the common sink: msg, key names, error values, and other non-string values are formatted without sanitisation at log.go:221-242.

Logger Configuration And Emission

Function File:Line Input source Flows into Current validation Potential attack vector
New(opts Options) log.go:111 Caller-controlled Options.Level, Options.Output, Options.Rotation, Options.RedactKeys; optional global RotationWriterFactory Initial Logger state; opts.Output or RotationWriterFactory(*opts.Rotation) becomes Logger.output; RedactKeys copied into Logger.redactKeys and later checked during logging Falls back to os.Stderr if output is nil; only uses rotation when Rotation != nil, Filename != "", and RotationWriterFactory != nil; RedactKeys slice is cloned Path-based write/exfiltration if untrusted Rotation.Filename reaches a permissive factory; arbitrary side effects or exfiltration through a caller-supplied writer; log suppression if an untrusted config sets LevelQuiet; redaction bypass if secret keys do not exactly match configured names
(*Logger).SetLevel(level Level) log.go:136 Caller-controlled log level Logger.level, then shouldLog() in all log methods None Audit bypass or loss of forensic detail if attacker-controlled config lowers the level or uses unexpected numeric values
(*Logger).SetOutput(w io.Writer) log.go:150 Caller-controlled writer Logger.output, then fmt.Fprintf in log.go:242 None Log exfiltration, unexpected side effects in custom writers, or panic/DoS if a nil or broken writer is installed
(*Logger).SetRedactKeys(keys ...string) log.go:157 Caller-controlled key names Logger.redactKeys, then slices.Contains(redactKeys, keyStr) during formatting Slice clone only Secret leakage when caller omits aliases, case variants, or confusable keys; CPU overhead grows linearly with large redact lists
(*Logger).Debug(msg string, keyvals ...any) log.go:246 Caller-controlled msg, keyvals, and mutable Logger.StyleDebug field StyleDebug("[DBG]") -> (*Logger).log -> fmt.Fprintf Level gate only; string keyvals values are quoted; exact-match redaction for configured keys; no nil check on StyleDebug Log forging or parser confusion via unsanitised msg, key names, error strings, or non-string fmt.Stringer values; secret leakage in msg or non-redacted fields; nil style function can panic
(*Logger).Info(msg string, keyvals ...any) log.go:253 Caller-controlled msg, keyvals, and mutable Logger.StyleInfo field StyleInfo("[INF]") -> (*Logger).log -> fmt.Fprintf Same controls as Debug Same attack surface as Debug: log injection, secret leakage outside exact-key redaction, and nil-style panic/DoS
(*Logger).Warn(msg string, keyvals ...any) log.go:260 Caller-controlled msg, keyvals, and mutable Logger.StyleWarn field StyleWarn("[WRN]") -> (*Logger).log -> fmt.Fprintf Same controls as Debug Same attack surface as Debug: log injection, secret leakage outside exact-key redaction, and nil-style panic/DoS
(*Logger).Error(msg string, keyvals ...any) log.go:267 Caller-controlled msg, keyvals, and mutable Logger.StyleError field StyleError("[ERR]") -> (*Logger).log -> fmt.Fprintf Same controls as Debug Same attack surface as Debug; higher impact because error logs are more likely to be consumed by SIEMs and incident tooling
(*Logger).Security(msg string, keyvals ...any) log.go:276 Caller-controlled msg, keyvals, and mutable Logger.StyleSecurity field StyleSecurity("[SEC]") -> (*Logger).log -> fmt.Fprintf Uses LevelError threshold so security events still emit when normal info logging is disabled; otherwise same controls as Debug Spoofed or injected security events if untrusted text reaches msg / keyvals; secret leakage in high-signal security logs; nil-style panic/DoS
Username() log.go:284 OS account lookup from user.Current() and environment variables USER / USERNAME Returned username string to caller Uses user.Current() first; falls back to USER, then USERNAME; no normalisation or escaping Username spoofing in untrusted/containerised environments where env vars are attacker-controlled; downstream log/UI injection if callers print the returned value without escaping
SetDefault(l *Logger) log.go:305 Caller-controlled logger pointer and its mutable fields (output, Style*, redact config, level) Global defaultLogger, then all package-level log functions plus LogError, LogWarn, and Must None Nil logger causes later panic/DoS; swapping the default logger can redirect logs, disable redaction, or install panicking style/output hooks globally
SetLevel(level Level) log.go:310 Caller-controlled log level defaultLogger.SetLevel(level) None beyond instance setter behaviour Same as (*Logger).SetLevel, but process-global
SetRedactKeys(keys ...string) log.go:315 Caller-controlled redact keys defaultLogger.SetRedactKeys(keys...) None beyond instance setter behaviour Same as (*Logger).SetRedactKeys, but process-global
Debug(msg string, keyvals ...any) log.go:320 Caller-controlled msg and keyvals routed through the global logger defaultLogger.Debug(...) -> (*Logger).log -> fmt.Fprintf Same controls as (*Logger).Debug; additionally depends on defaultLogger not being nil Same as (*Logger).Debug, plus global DoS if defaultLogger was set to nil
Info(msg string, keyvals ...any) log.go:325 Caller-controlled msg and keyvals routed through the global logger defaultLogger.Info(...) -> (*Logger).log -> fmt.Fprintf Same controls as (*Logger).Info; additionally depends on defaultLogger not being nil Same as (*Logger).Info, plus global DoS if defaultLogger was set to nil
Warn(msg string, keyvals ...any) log.go:330 Caller-controlled msg and keyvals routed through the global logger defaultLogger.Warn(...) -> (*Logger).log -> fmt.Fprintf Same controls as (*Logger).Warn; additionally depends on defaultLogger not being nil Same as (*Logger).Warn, plus global DoS if defaultLogger was set to nil
Error(msg string, keyvals ...any) log.go:335 Caller-controlled msg and keyvals routed through the global logger defaultLogger.Error(...) -> (*Logger).log -> fmt.Fprintf Same controls as (*Logger).Error; additionally depends on defaultLogger not being nil Same as (*Logger).Error, plus global DoS if defaultLogger was set to nil
Security(msg string, keyvals ...any) log.go:340 Caller-controlled msg and keyvals routed through the global logger defaultLogger.Security(...) -> (*Logger).log -> fmt.Fprintf Same controls as (*Logger).Security; additionally depends on defaultLogger not being nil Same as (*Logger).Security, plus global DoS if defaultLogger was set to nil

Error Construction And Emission

Function File:Line Input source Flows into Current validation Potential attack vector
(*Err).Error() errors.go:25 Public Err.Op, Err.Msg, Err.Err, and Err.Code fields, including direct struct literal construction by callers Returned error string via fmt.Sprintf; if the error is later logged as a non-string value, the text reaches (*Logger).log -> fmt.Fprintf None Log injection or response spoofing via attacker-controlled op/message/code text; secret disclosure via underlying error text; machine-readable code spoofing
E(op, msg string, err error) errors.go:56 Caller-controlled op, msg, err New Err instance, then (*Err).Error() when rendered None Same as (*Err).Error(): attacker-controlled context and underlying error text can be surfaced in logs or responses
Wrap(err error, op, msg string) errors.go:67 Caller-controlled err, op, msg New Err wrapper; preserves existing Code from wrapped *Err if present Returns nil when err == nil; preserves code if wrapped error is an *Err Untrusted wrapped errors can carry misleading Code / Op context forward; op/msg text is not sanitised before later logging or presentation
WrapCode(err error, code, op, msg string) errors.go:86 Caller-controlled err, code, op, msg New Err with explicit code, then (*Err).Error() when rendered Returns nil only when both err == nil and code == "" Error-code spoofing; untrusted message/op text can be pushed into logs or external error responses
NewCode(code, msg string) errors.go:99 Caller-controlled code, msg New Err with no underlying error None Same as WrapCode without an underlying cause: spoofed codes and unsanitised user-facing text
NewError(text string) errors.go:119 Caller-controlled text errors.New(text) return value, then any downstream logging or response handling None Log / response injection or sensitive text disclosure when callers pass untrusted strings through as raw errors
Join(errs ...error) errors.go:125 Caller-controlled error list errors.Join(errs...), then downstream Error() / logging Standard library behaviour only Size amplification and multi-message disclosure if attacker-controlled errors are aggregated and later surfaced verbatim
LogError(err error, op, msg string) errors.go:235 Caller-controlled err, op, msg; global defaultLogger state Wrap(err, op, msg) return value plus defaultLogger.Error(msg, "op", op, "err", err) -> fmt.Fprintf Returns nil when err == nil; otherwise inherits logger controls (quoted string values, exact-key redaction) Log injection through msg, op, or err.Error(); sensitive error disclosure; global DoS/exfiltration if defaultLogger was replaced or nil
LogWarn(err error, op, msg string) errors.go:250 Caller-controlled err, op, msg; global defaultLogger state Wrap(err, op, msg) return value plus defaultLogger.Warn(msg, "op", op, "err", err) -> fmt.Fprintf Returns nil when err == nil; otherwise inherits logger controls Same as LogError, with the additional risk that warnings may be treated as non-critical and reviewed less carefully
Must(err error, op, msg string) errors.go:265 Caller-controlled err, op, msg; global defaultLogger state defaultLogger.Error(msg, "op", op, "err", err) -> fmt.Fprintf, then panic(Wrap(err, op, msg)) Only executes when err != nil; otherwise same logger controls as LogError Attacker-triggerable panic/DoS if external input can force the error path; same log injection and disclosure risks as LogError before the panic

Error Inspection Helpers

Function File:Line Input source Flows into Current validation Potential attack vector
(*Err).Unwrap() errors.go:43 Public Err.Err field Returned underlying error None No direct sink in this package; risk comes from whatever the wrapped error does when callers continue processing it
Is(err, target error) errors.go:107 Caller-controlled err, target errors.Is(err, target) return value Standard library behaviour only Minimal direct attack surface here; any custom Is semantics come from attacker-supplied error implementations
As(err error, target any) errors.go:113 Caller-controlled err, target errors.As(err, target) return value No wrapper-side validation of target Invalid target values can panic through errors.As, creating an application-level DoS if misuse is reachable
Op(err error) errors.go:133 Caller-controlled error chain Extracted Err.Op string Type-check via errors.As; otherwise none Spoofed operation names if callers trust attacker-controlled *Err values for audit, metrics, or access decisions
ErrCode(err error) errors.go:143 Caller-controlled error chain Extracted Err.Code string Type-check via errors.As; otherwise none Spoofed machine-readable error codes if callers trust attacker-controlled wrapped errors
Message(err error) errors.go:153 Caller-controlled error chain Extracted Err.Msg or err.Error() string Returns "" for nil; otherwise none Unsanitised attacker-controlled message text can be re-used in logs, UIs, or API responses
Root(err error) errors.go:166 Caller-controlled error chain Returned deepest unwrapped error Returns nil for nil; otherwise none Minimal direct sink, but can expose a sensitive root error that callers later surface verbatim
AllOps(err error) errors.go:181 Caller-controlled error chain Iterator over all Err.Op values Traverses errors.Unwrap chain; ignores non-*Err nodes Long attacker-controlled wrap chains can increase CPU work; extracted op strings remain unsanitised
StackTrace(err error) errors.go:198 Caller-controlled error chain []string of operation names None beyond AllOps traversal Memory growth proportional to chain length; untrusted op strings may later be logged or displayed
FormatStackTrace(err error) errors.go:207 Caller-controlled error chain Joined stack string via strings.Join(ops, " -> ") Returns "" when no ops are found; otherwise none Log / UI injection if untrusted op names are later rendered without escaping; size amplification for deep chains