redaction

package
v1.0.126 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package redaction is the data-governance engine for support bundles (REDACTION-MODEL.md, ADR-0009). It classifies collected data by DataClass and redacts STRUCTURALLY at the source: SECRET/NEVER_EXPORT fields are dropped, SENSITIVE fields are masked, and any unclassified field fails closed to SENSITIVE (masked, never passed through). Regexes are a later backstop; the primary control is field-class-driven and deterministic.

Index

Constants

View Source
const DefaultClass = ClassSensitive

DefaultClass is the fail-closed class for any collected field with no explicit classification: masked, never leaked (REDACTION-MODEL §1/§9, P3).

View Source
const ShareableCeiling = ClassInternal

ShareableCeiling is the highest class permitted, post-redaction, in a shareable bundle. SENSITIVE data is only allowed in masked form (which the redactor treats as effectively INTERNAL), and SECRET/NEVER_EXPORT are dropped, so a redacted section's class_max never exceeds this by construction.

Variables

This section is empty.

Functions

This section is empty.

Types

type DataClass

type DataClass int

DataClass is the ordered sensitivity taxonomy. Higher = more restricted.

const (
	// ClassPublic is non-sensitive; safe for anyone (version, build, counts).
	ClassPublic DataClass = iota
	// ClassInternal is operationally revealing but not secret (rule names,
	// hostnames, health verdicts). Default ceiling for a shareable bundle.
	ClassInternal
	// ClassSensitive is identifying/confidential; masked before export
	// (usernames, client IPs, full URLs, DNs).
	ClassSensitive
	// ClassSecret is credential/authenticator material; dropped, never masked
	// and kept (passwords, HMACs, tokens, client secrets).
	ClassSecret
	// ClassNeverExport is key material that must never cross the process
	// boundary (KEK, CA/TLS private keys). The redactor only asserts, in tests,
	// that no collector can name such a field — the bytes are unreachable by
	// construction (internal/secret, ADR-0007).
	ClassNeverExport
)

func ParseClass

func ParseClass(s string) (DataClass, bool)

ParseClass maps a struct-tag/wire spelling to a DataClass (case-insensitive). The second return is false for an unknown token — callers fail closed.

func (DataClass) String

func (c DataClass) String() string

String is the canonical wire spelling used in manifests and reports.

type Redactor

type Redactor interface {
	// Struct returns the redacted, JSON-serializable form of v. Collectors call
	// this on every value before writing it to a section.
	Struct(v any) any
	// Classify is Struct plus the post-redaction class_max and mask/drop counts,
	// used by the runner for the manifest section entry and redaction report.
	Classify(v any) Result
}

Redactor redacts collected values structurally by DataClass. Field classes are declared with a `redact:"public|internal|sensitive|secret|never_export"` struct tag; an untagged (or unknown-tagged) exported field fails closed to SENSITIVE.

func New

func New() Redactor

New builds a Redactor with a fresh random per-bundle salt. The salt is NEVER_EXPORT — never written to a bundle — so masked tokens are irreversible and do not correlate across bundles (REDACTION-MODEL §4).

func NewWithSalt

func NewWithSalt(salt []byte) Redactor

NewWithSalt is the deterministic constructor for tests (fixed salt → fixed masked tokens), mirroring the injected-clock discipline of the engine tests.

type Result

type Result struct {
	Value    any       // JSON-serializable redacted form
	ClassMax DataClass // highest class present AFTER redaction (<= ShareableCeiling)
	Masked   int       // count of SENSITIVE fields masked
	Dropped  int       // count of SECRET/NEVER_EXPORT fields dropped
	Scrubbed int       // count of free-form secret-shapes redacted in KEPT strings
	// RetainedFreeForm is a BOUNDED, post-scrub sample of INTERNAL free-form
	// string values KEPT in the shareable output. The scrubber is precision-first
	// (no entropy rule, by design), so a bare shapeless secret typed into an
	// operator-controlled free-form field (a policy rule name, an upstream
	// endpoint, a diagnostic message) survives it — and the bundled
	// redaction-report is counts-only (P4/P6). These samples feed the
	// pre-export consent surface ONLY (server-side; never written to the tar) so
	// the approving admin can SEE what automated redaction structurally cannot
	// catch. Deterministic; capped by the maxRetained* constants below.
	RetainedFreeForm []string
}

Result is the outcome of redacting one value.

type Scrubber

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

Scrubber is the free-form secret backstop (REDACTION-MODEL: "Regexes are a later backstop"). Structural DataClass redaction masks/drops whole classified FIELDS; the scrubber is the second layer that catches a secret EMBEDDED inside a field that is legitimately KEPT — e.g. an INTERNAL diagnostic message "dial tcp: bad auth Bearer ghp_…", an audit Detail, a config value, a log URI.

Design (adversarially reviewed twice, M2 PR1): precision over recall. Every pattern is a fixed, self-identifying credential SHAPE; there is deliberately NO bare length/entropy rule, so git SHAs, SHA-256 hashes, base64 cert fingerprints, ULIDs, UUIDs, and X-Request-IDs pass through verbatim and bundles stay useful. Bare context-free secrets are left to the structural SECRET class (dropped).

The engine is bounded and deterministic (bundle hashing depends on it): Go regexp is RE2 (linear, no catastrophic backtracking); the clean path is at most two scans (format-rune check + one detector match) with no output allocation; a leaf over maxScanBytes or with more than maxReplacements matches is replaced WHOLE (fail-closed). The replacement token contains no trigger substring, so Scrub is idempotent: Scrub(Scrub(s))==Scrub(s).

func (*Scrubber) Scrub

func (sc *Scrubber) Scrub(s string) (out string, n int)

Scrub returns s with every recognized secret shape replaced by an "[redacted:<kind>]" token, plus the number of redactions. The clean path (no format runes, no match) returns s unchanged with no output allocation.

Jump to

Keyboard shortcuts

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