redaction

package
v1.107.1 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package redaction removes detected secrets from JSON documents before they leave the machine.

Detection and rewriting are deliberately split. Secrets are detected against the document rendered as indented JSON, because the patterns that catch generic credentials rely on seeing a key next to its value. The rewrite then happens structurally, on the decoded value tree, so the output cannot be malformed JSON. A convergence loop re-scans after every rewrite, which makes idempotency a property of the algorithm rather than of the placeholder format.

Index

Constants

View Source
const DefaultMaxPasses = 4

DefaultMaxPasses bounds the convergence loop. A successful redaction costs two passes: one that finds the secrets and one that confirms none are left.

Redaction is deliberately unbounded in size and time. A session that cannot be scanned cannot be stored, and failing an attestation over a large-but-honest transcript is worse than taking a while over it. Callers that need a bound can impose one through the context they pass to Redact.

Variables

View Source
var (
	// ErrNotConverged is returned when repeated passes keep detecting secrets,
	// which in practice means a placeholder is itself matching a rule.
	ErrNotConverged = errors.New("secret redaction did not converge")
	// ErrInvalidJSON is returned when the document is not a single JSON object.
	ErrInvalidJSON = errors.New("document is not a JSON object")
	// ErrDuplicateKey is returned when an object repeats a key. Decoding keeps
	// only the last value for a repeated key, so a secret in an earlier one would
	// never be scanned; redaction refuses the document rather than pass it
	// through unexamined.
	ErrDuplicateKey = errors.New("document contains a duplicate object key")
)

Functions

func DefaultPlaceholder

func DefaultPlaceholder(ruleID string) string

DefaultPlaceholder is the replacement text for a redacted secret. It is deterministic, so redacting the same document twice yields the same digest, and it names the rule so a reviewer can tell what kind of credential was present without being able to recover it.

func IsDefaultPlaceholder

func IsDefaultPlaceholder(s string) bool

IsDefaultPlaceholder reports whether s is a placeholder DefaultPlaceholder could have produced.

Types

type Finding

type Finding struct {
	// RuleID names the rule that matched. It ends up in the placeholder, so it
	// must never contain secret material.
	RuleID string
	// Secret is the matched credential, verbatim as it appeared in the scanned
	// text.
	Secret string
}

Finding is a located secret. It is deliberately decoupled from any particular scanning engine's types.

type Option

type Option func(*Redactor)

Option customises a Redactor.

func WithMaxPasses

func WithMaxPasses(n int) Option

WithMaxPasses bounds the convergence loop.

func WithPathFilter

func WithPathFilter(f PathFilter) Option

WithPathFilter restricts which string leaves may be rewritten. The default allows every leaf.

func WithPlaceholder

func WithPlaceholder(format func(ruleID string) string, matches func(string) bool) Option

WithPlaceholder overrides the replacement text derived from a rule id.

The two functions are supplied together on purpose. Some rules match a position rather than a value, so they report a placeholder as though it were a secret; recognising our own output is what stops redaction from rewriting it and keeps redacting an already-redacted document a no-op. A format without a matching recogniser would silently lose that.

type PathFilter

type PathFilter func(path string) bool

PathFilter reports whether the string leaf at the given path may be rewritten. Paths look like "/data/raw_session/main/0/content": a leading slash, object keys and array indices separated by slashes.

type Redactor

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

Redactor rewrites JSON documents, replacing detected secrets with placeholders. It is safe for concurrent use as long as the Scanner is.

func New

func New(s Scanner, opts ...Option) *Redactor

New builds a Redactor around the given Scanner.

func (*Redactor) Redact

func (r *Redactor) Redact(ctx context.Context, doc []byte) ([]byte, *Report, error)

Redact returns a copy of doc with every detected secret replaced. When nothing is detected the input is returned verbatim, so that a document without secrets keeps its original digest.

type Report

type Report struct {
	// Replacements is the total number of substitutions performed.
	Replacements int
	// ByRule counts substitutions per rule id.
	ByRule map[string]int
	// Unlocated counts, per rule id, secrets the scanner reported but which
	// could not be attributed to any eligible leaf. That happens when the match
	// lies in a path the filter protects, or when it spans the JSON punctuation
	// between two adjacent leaves.
	Unlocated map[string]int
	// Passes is the number of detection passes performed.
	Passes int
}

Report summarises a Redact call. It never contains secret material, so it is safe to log and to surface as material annotations.

func (*Report) Changed

func (r *Report) Changed() bool

Changed reports whether anything was redacted.

func (*Report) RuleIDs

func (r *Report) RuleIDs() []string

RuleIDs returns the sorted, deduplicated ids of the rules that actually redacted something.

type Scanner

type Scanner interface {
	Scan(ctx context.Context, text string) ([]Finding, error)
}

Scanner detects secrets in a text fragment.

func DefaultScanner

func DefaultScanner() (Scanner, error)

DefaultScanner returns the process-wide betterleaks-backed scanner. Constructing a detector compiles several hundred regexes and builds a keyword trie, so it is built once, lazily, and only for attestations that need it.

Jump to

Keyboard shortcuts

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