redact

package
v0.26.21 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 4 Imported by: 0

README

redact

Package redact declares the SDK's display-redaction port: the Redactor that renders a Go value, a JSON document, a text or log attributes for DISPLAY with every secret it recognises replaced, within a byte bound, never touching what it is given.

type Redactor interface {
    Name(name string) bool
    Text(s string, maxBytes int) string
    JSON(document []byte, maxBytes int) (DocumentValue, error)
    Value(v any, maxBytes int) (DocumentValue, error)
    Attrs(attrs []corelogger.AttrValue, maxBytes int) iter.Seq2[string, string]
}

It also holds the values every implementation writes and every caller compares against — Placeholder ([redacted]), Ellipsis (…), MinBytes (the floor every bound is raised to), Unencodable and DocumentValue — and the domain's two codes, DOCUMENT_INVALID (0.3.73.1) and VALUE_UNENCODABLE (0.3.73.2).

It is a display filter, not an access control: what no rule recognises is shown. The engine and its configuration live in internal/service/security/redact; facade: pkg/v1/security/redact. ADR 0101, ADR 0160. See CLAUDE.md.

The Redactor port is generated from design/security/redact.yaml into design_gen.go (ADR 0163): it changes in the design, then kit gen.

Documentation

Overview

Package redact — range 0.3.73.* (ADR 0101), declared here since ADR 0160.

Package redact — declares the sentinel *errs.Error outcomes. Each var's name equals its errs.Define Reason in SCREAMING_SNAKE form.

Neither refusal carries a byte of what it refused: this domain exists to keep secrets out of what is shown, and an error message is shown. They are raised by the engine in internal/service/security/redact — their Private names that package, the one that raises them — and declared here since ADR 0160, so every code of the domain is in one place.

Package redact declares the SDK's display-redaction port: the Redactor that renders a Go value, a JSON document, a text or log attributes for DISPLAY with every secret it recognises replaced, within a byte bound, never touching what it is given — and the values every implementation shares: the Placeholder a secret becomes, the Ellipsis a cut ends in, the MinBytes floor under every bound, the Unencodable marker, and the DocumentValue a JSON rendering returns. A core sibling since ADR 0160, for the domain ADR 0101 admitted.

It is for showing data to a person: a developer console, a trace viewer, a log panel, an error page. It is not an access control and not a sanitiser for data that goes back into a system: a redacted document is a different document, and a secret spelled in a way no rule recognises is shown.

The engine — the three rules (a NAME carrying a configured word, a field DECLARED secret, a URL's CREDENTIALS), the writer that counts every byte before writing it, and the per-type plan cache — lives in internal/service/security/redact, which implements Redactor. Its construction parameters are the engine's (ADR 0074). This package owns the contract, the values a caller compares results against, and the domain's codes.

Index

Constants

View Source
const CodeDocumentInvalid errs.Code = 0x00_03_49_01 // 0.3.73.1

CodeDocumentInvalid identifies a document handed to JSON that is not one well-formed JSON value, so there is nothing to redact into.

View Source
const CodeValueUnencodable errs.Code = 0x00_03_49_02 // 0.3.73.2

CodeValueUnencodable identifies a value handed to Value that encoding/json refuses to encode: a channel, a function, a cycle, a failing Marshaler.

View Source
const Ellipsis string = "…"

Ellipsis ends a string, a text or a document cut to fit its bound. It is one character, three bytes of UTF-8, and it is counted inside the bound.

View Source
const MinBytes int = 16

MinBytes is the smallest bound a Redactor honours; a smaller or non-positive one is raised to it. Below it a cut document could not hold its own marker and the closers of the containers it cut — and a bound is the display's size, so raising a nonsensical one is safer than refusing to show anything (ADR 0031's clamp: the floor is obvious).

View Source
const Placeholder string = "[redacted]"

Placeholder replaces every secret a Redactor recognises. A caller that shows a redacted value compares against it, so every implementation writes exactly this text.

View Source
const Unencodable string = "[unencodable]"

Unencodable is the text a log attribute holding a value encoding/json refuses is rendered as by Redactor.Attrs: the marker, never the value.

Variables

View Source
var (
	// DocumentInvalid refuses a document that is not one well-formed JSON
	// value. Nothing is returned for it: a partial copy of a document that
	// does not parse cannot be trusted to have had its secrets recognised.
	DocumentInvalid = errs.Static(CodeDocumentInvalid, "DOCUMENT_INVALID",
		"The document is not valid JSON and was not redacted",
		"service/security/redact: the JSON reader refused the document; the offset field locates where",
		0,
		0)

	// ValueUnencodable refuses a value encoding/json will not encode, so
	// there is no JSON form to redact.
	ValueUnencodable = errs.Static(CodeValueUnencodable, "VALUE_UNENCODABLE",
		"The value cannot be encoded as JSON and was not redacted",
		"service/security/redact: encoding/json refused the value; the type field names its Go type",
		0,
		0)
)

Functions

This section is empty.

Types

type DocumentValue

type DocumentValue struct {
	// JSON is always one well-formed JSON value, never longer than the bound.
	JSON json.RawMessage
	// Truncated reports that members, elements or the tail of a string were
	// left out to fit the bound. The containers that were cut are still
	// closed, so JSON stays well-formed.
	Truncated bool
}

DocumentValue is a JSON document with its secrets replaced, and whether it had to be cut to fit its bound. It is what Redactor.JSON and Redactor.Value return.

type Redactor

type Redactor interface {
	// Name reports whether a member, header or attribute name names a
	// secret.
	Name(name string) bool
	// Text returns s with the credentials of every URL in it replaced by
	// [Placeholder], then cut to the bound at a rune boundary, ending in
	// [Ellipsis] when cut. The credentials are replaced BEFORE the cut, so a
	// cut never lands between a password and the "@" that marks it as one.
	Text(s string, maxBytes int) string
	// JSON returns document with its secrets replaced, within the bound. A
	// document that is not exactly one JSON value is refused with
	// [DocumentInvalid], and nothing is returned for it.
	JSON(document []byte, maxBytes int) (redacted DocumentValue, err error)
	// Value returns v encoded as encoding/json would put it on the wire, with
	// its secrets replaced, within the bound. A value encoding/json refuses is
	// refused with [ValueUnencodable].
	Value(v any, maxBytes int) (redacted DocumentValue, err error)
	// Attrs renders log attributes as display text, one (key, text) pair at a
	// time, in order, a group flattened into dotted keys; each text is cut to
	// the bound. It is an iterator so the caller bounds the COUNT by breaking.
	Attrs(attrs []corelogger.AttrValue, maxBytes int) iter.Seq2[string, string]
}

Redactor renders data for display with its secrets replaced. Every method takes a byte bound, raised to MinBytes, and never returns more than it; none of them modifies what it is given. An implementation is safe for concurrent use.

The port is FROZEN at these five methods (ADR 0039): a published interface that grew a sixth would break every implementation and every test double a consumer wrote. A new capability is a sibling interface, type-asserted where it is needed.

Jump to

Keyboard shortcuts

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