- 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
64 lines
3.5 KiB
Markdown
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.
|