log

package
v0.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package log is Brigade's one redacting slog handler (plan 7.3, T12): every diagnostic line an adapter or the harness writes to stderr or a log file goes through it, because the harness captures adapter stderr at debug level and a leak here puts a credential in a 0600-but-still- on-disk log file (U-09, U-23).

Four layers, applied to every attribute value, attribute key and the record message:

  1. Key list: an attribute whose key names a secret (token, secret, authorization, apikey/api_key, password — which contains the plan 1315 list: access_token, refresh_token, join_secret; matched case-insensitively, '-' folded to '_', by containment) has its value replaced whatever its kind or content.
  2. Format patterns inside string values: JWTs (eyJ….…​.…), the brg1.<team_ref>.<secret> join-secret format (D5), sb_secret_ keys and "Bearer …" authorization credentials. Matches are collected as spans on the original string and merged before replacement, and the prefix-literal formats are re-anchored at every occurrence of their literal, so an overlapping earlier match can never consume a real token's prefix and leave its tail exposed.
  3. Exact tokens: secrets known at run time (the messaging token, a refresh token) registered on the Redactor are replaced wherever they appear, including in the middle of a longer string such as a URL query or a wrapped error message.
  4. Scalar-only policy: ReplaceAttr cannot see inside a struct, map or slice value (a struct passed through slog.Any prints its fields verbatim [verified, plan A.7]), so slog.Any is banned outside this package by forbidigo (7.3) — the static half — and, as the runtime half, this handler replaces any KindAny value that is not an error with a fixed marker instead of printing it. Errors are logged through Err (or any string attr); everything else is logged as a scalar: slog.String, Int, Bool, Duration, Time. Attributes inside slog.Group are scalars in disguise — the JSON handler walks them and hands each leaf to ReplaceAttr — and the tests pin that walk.

What the handler cannot protect: a secret used as a group NAME in WithGroup (ReplaceAttr never sees group names — group names are compile-time constants here) and text that never passes through slog. For the latter, Redact is exported so raw text (a server error body logged at debug) can be scrubbed before any other sink.

Index

Constants

View Source
const Redacted = "[redacted]"

Redacted is the marker every redaction layer writes in place of the text it removed. Lowercase to match protocol.JoinSecret's own String and LogValue redaction.

View Source
const Suppressed = "[suppressed non-scalar value: log scalars, or adapterkit/log helpers (plan 7.3)]"

Suppressed is the marker written in place of a non-scalar (non-error) slog.Any value. ReplaceAttr cannot redact inside struct, map or slice values, so they are never printed at all (the scalar-only policy of plan 7.3; forbidigo's slog.Any ban is the static half of the same rule).

Variables

This section is empty.

Functions

func Err

func Err(err error) slog.Attr

Err is the typed replacement for slog.Any("err", err), which forbidigo bans outside this package: an error is logged as its message string, which the handler then redacts like any other string. A nil error logs as "<nil>".

func New

func New(w io.Writer, level slog.Leveler, r *Redactor) *slog.Logger

New returns a JSON slog logger writing to w through the redacting handler. level nil means slog.LevelInfo (the HandlerOptions default); pass a *slog.LevelVar to change it later. r nil means a fresh Redactor with no exact tokens — pass your own to register secrets (the messaging token, a refresh token) before or after construction.

The handler never writes to os.Stdout by this package's choice of w: stdout is protocol output only (plan 7.3); diagnostics go to stderr or a 0600 log file, both of which the harness may capture at debug level, which is exactly why everything is redacted.

func ParseLevel

func ParseLevel(s string) (slog.Level, error)

ParseLevel parses the wire grammar of --log-level and BRIGADE_LOG_LEVEL: exactly one of "error", "warn", "info", "debug" (plan 4.1). Anything else is an error the caller maps to its own taxonomy (usage for a flag, config for the environment); the message never echoes the input, which could be a mis-pasted secret.

func SecretShaped added in v0.7.0

func SecretShaped(s string) bool

SecretShaped reports whether s contains a credential in one of the prefix-literal formats the redactor knows — a JWT, a join secret, a Supabase secret key — matched exactly as Redact matches them (every literal occurrence, the anchored pattern applied there). It exists so a caller that must REFUSE such text rather than mask it (the doing line of card 25, plan 5.1) shares this one pattern list instead of keeping a second. It deliberately does not include the bearer rule: over-redaction is safe in a log, but a refusal that fires on "fixing bearer token parsing" silently costs a feature.

Types

type Redactor

type Redactor struct {
	// contains filtered or unexported fields
}

A Redactor holds the exact secret values registered at run time and applies every value-level redaction layer. The zero value and nil are usable (patterns only); NewRedactor adds exact tokens. One Redactor is shared between a logger and the code that learns secrets later (Add is safe under concurrent logging).

func NewRedactor

func NewRedactor(secrets ...string) *Redactor

NewRedactor returns a Redactor with the given exact secret values registered. Empty strings are ignored.

func (*Redactor) Add

func (r *Redactor) Add(secrets ...string)

Add registers exact secret values to be replaced wherever they appear, including in the middle of longer strings. Empty strings and duplicates are ignored. Safe to call while the logger is in use.

func (*Redactor) Redact

func (r *Redactor) Redact(s string) string

Redact replaces every secret found in s — format patterns and registered exact tokens — with the Redacted marker. All matches are collected as spans on the original string and merged, so overlapping matches cannot split a secret and leave a fragment. Nil-safe.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL