Files
summercms/modules/sunscreen
Jakub Zych 7241704e93 feat(14-01): sunscreen redacting slog handler installed by every generated main
- Wrap redacts sensitive keys at any depth and scrubs Bearer, sk- and x-api-key shapes
- InstallDefault is the first statement of the generated run; hello main regenerated
- surf test pins that recovered panics echo no credential
- sunscreen README, root modules row and the logging docs page
2026-10-03 20:01:36 +02:00
..

sunscreen

Credential-redacting slog handler that keeps API keys, tokens and passwords out of application logs.

import "git.golem15.com/golem15/summercms/modules/sunscreen"

Overview

sunscreen wraps any log/slog handler and rewrites every record before it is written: values of sensitive attribute keys become [REDACTED], and credential shapes inside messages and string values are scrubbed. It is the SummerCMS counterpart of a Monolog tap that a WinterCMS application wires into its log channels to scrub contexts and messages. Every application main that summer build generates calls sunscreen.InstallDefault(os.Stderr) as its first statement, so the default logger, the standard log package and every plugin that falls back to slog.Default log through it from the start.

Features

  • Sensitive keys (sunscreen.RedactedKeys): api_key, apikey, authorization, bearer, password, secret, token, webhook_secret, admin_password, openai_api_key, anthropic_api_key and perplexity_api_key, compared case-insensitively at any group depth. The value of such an attribute becomes sunscreen.Redacted, whatever its kind.
  • Scrub patterns (sunscreen.Scrub): Bearer <token> becomes Bearer [REDACTED], sk- followed by 20 or more key characters (dashed keys included) becomes sk-[REDACTED], and x-api-key: <value> becomes x-api-key: [REDACTED]. The Bearer and x-api-key matches are case-insensitive.
  • Applied to the record message, string values, error values, byte slices and the formatted text of any other value; maps with string keys, such as http.Header, are redacted key by key.
  • slog.LogValuer values are resolved before redaction, groups are walked recursively, and attributes added with WithAttrs are redacted before they reach the wrapped handler.
  • sunscreen.InstallDefault builds a fresh text handler instead of wrapping the existing default, whose output goes through the log package that slog.SetDefault redirects back into the new handler.

Usage

The generated main already installs it:

func run(args []string, out io.Writer) error {
	sunscreen.InstallDefault(os.Stderr)
	cfg, err := compass.Load("config")
	// ...
}

To redact another handler, for example a JSON handler in a custom binary, wrap it:

logger := slog.New(sunscreen.Wrap(slog.NewJSONHandler(os.Stderr, nil)))
logger.Error("vendor call failed", "api_key", key, "err", err)
// {"level":"ERROR","msg":"vendor call failed","api_key":"[REDACTED]","err":"..."}

Redaction is a safety net, not a licence: log ids and outcomes, never request bodies, tokens or arguments that carry them.

API reference

Identifier Description
sunscreen.Wrap Returns a slog.Handler that redacts every record and every WithAttrs attribute before the wrapped handler sees it. Wrapping a sunscreen handler again returns it unchanged.
sunscreen.InstallDefault Makes a redacting text handler writing to the given writer the process default logger.
sunscreen.Scrub Replaces the Bearer, sk- and x-api-key: credential shapes in a string.
sunscreen.Redacted The replacement text, [REDACTED].
sunscreen.RedactedKeys Returns a copy of the attribute keys whose values are always redacted.

Dependencies

  • SummerCMS modules: none.
  • Third-party: none.
  • Standard library: context, fmt, io, log/slog, reflect, regexp, slices, strings.

Testing

go test ./modules/sunscreen/...

The tests log through the handler into a buffer and need no external services.