redaction

package
v1.0.185 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 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

func ScrubDetailDefault added in v1.0.179

func ScrubDetailDefault(s string) (out string, names []string, n int)

ScrubDetailDefault runs ScrubDetail with the process-wide default scrubber. It is the package-level entry point the MCP DLP classifier consumes so it never needs a Scrubber handle or a second scrubber instance.

func SecretClass added in v1.0.179

func SecretClass(name string) string

SecretClass reports the coarse DLP classification for a scrubber detector name. It is a pure, fixed mapping (no allocation, no I/O) the MCP inspection layer uses to turn a scrubber hit into a stable data classification without re-deriving secret shapes. Unknown names fail closed to a generic credential secret classification (a hit is a hit).

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.

func (*Scrubber) ScrubDetail added in v1.0.179

func (sc *Scrubber) ScrubDetail(s string) (out string, names []string, n int)

ScrubDetail is Scrub plus the ordered detector names that fired. It returns the redacted string, the stable detector names for every redaction (duplicates preserved, one per redaction), and the redaction count (len(names) == n except on the whole-leaf fail-closed sentinels, where n==1 and names carry the single sentinel). The raw matched secret is NEVER returned. Bounded by the same maxScanBytes fail-closed cap as Scrub.

Jump to

Keyboard shortcuts

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