Files
summercms/modules/sunscreen/README.md
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

64 lines
3.5 KiB
Markdown

# 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:
```go
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:
```go
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
```sh
go test ./modules/sunscreen/...
```
The tests log through the handler into a buffer and need no external services.