14 KiB
14 KiB
Security Attack Vector Mapping
CLAUDE.mdreviewed.CODEX.mdwas 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).loginlog.go:169-242writes the final log line withfmt.Fprintf(output, "%s %s %s%s\n", ...)atlog.go:242. - Existing controls in the common sink: string values in
keyvalsare%q-escaped atlog.go:234-236, exact-match redaction is applied withslices.Contains(redactKeys, keyStr)atlog.go:227-231, andErr-derivedop/stackcontext is auto-added atlog.go:178-211. - Gaps in the common sink:
msg, key names,errorvalues, and other non-string values are formatted without sanitisation atlog.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 |