redact

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package redact removes credentials from everything PayCLI emits: stdout, stderr, log lines, audit records, cache files and the manifest (§5.3).

Two rules drive the whole package:

  1. Payload returns credentials inside ordinary documents. GET /api/{auth}/me hands back apiKey, hash and salt in plaintext, and a bulk write against the auth collection echoes every touched user, so any response body may contain a secret at any depth.
  2. error.raw and --output raw must stay byte-faithful *after* redaction (§11.1). JSON is therefore spliced, not re-encoded: when nothing matched, the caller gets the original bytes back with zero normalisation, and when something did match only the matched value ranges are replaced. Key order, indentation, number formatting and duplicate keys all survive.

Index

Constants

View Source
const FingerprintDomain = "paycli-key-v1\x00"

FingerprintDomain is §4.4's domain separator. It exists so a PayCLI fingerprint can never be confused with a bare sha256 of the same key computed elsewhere.

View Source
const FingerprintLen = 16

FingerprintLen is §4.4's "16 hex everywhere" rule.

View Source
const Mask = "<redacted>"

Mask replaces every redacted value.

Variables

This section is empty.

Functions

func AuthorizationValue

func AuthorizationValue(value string) string

AuthorizationValue turns any Authorization header value into "<redacted:fp=a1b2c3d4e5f60718>" — the exact literal §5.3 requires the transport to log. The fingerprint covers the credential only (the last whitespace-separated field), so "users API-Key K" and "JWT K" fingerprint identically for the same K.

func Fingerprint

func Fingerprint(secret string) string

Fingerprint is §4.4's stable, non-reversible tag for a secret: the first 16 hex characters of sha256("paycli-key-v1\x00" + secret). It is safe to print, log, store in the manifest (identity.key_fingerprint) and compare across runs.

This is THE formula. internal/secret.Fingerprint and cache.KeyFingerprint both delegate here, because §4.4 requires every producer — credentials.json, the manifest, `pay auth status`, `pay cache ls`, the §8.1 scope input and the transport's `Authorization: <redacted:fp=…>` log line — to emit the identical 16 characters for the same credential.

func Header(name string) bool

Header reports whether a header's value must never be emitted.

func HeaderValue

func HeaderValue(name, value string) string

HeaderValue returns the value that may be printed for a header. Authorization is special-cased into the fingerprint form mandated by §5.3's hard transport rule, so logs can prove *which* credential was used without revealing it.

func Headers

func Headers(h http.Header, alsoSecret ...string) http.Header

Headers returns a copy of h safe to log or embed in a dry-run envelope. alsoSecret names additional headers to mask; the transport passes the profile's configured [profiles.X.headers] keys, all of which are secret.

func IsJWT

func IsJWT(s string) bool

IsJWT reports whether s is shaped like a JSON Web Token.

func Key

func Key(name string) bool

Key reports whether a JSON object key (or a query-parameter name) names a value that must never be emitted.

func MaskSecret

func MaskSecret(secret string) string

MaskSecret renders a secret as "<redacted:fp=...>". An empty secret yields the plain mask so the output never implies a credential existed.

func Text

func Text(s string) string

Text scrubs credentials out of free-form text: log lines, server messages, error strings and command echoes.

func URL

func URL(raw string) string

URL strips credentials from a URL before it crosses any boundary. §5.3 makes it mandatory on meta.base_url, error.http.url, §12.2's request.url, Event.BaseURL, Event.Path, the manifest's meta.base_url and source.base_url, and references/PROJECT.md.

Three things are removed:

  • userinfo ("https://user:pass@host" -> "https://host"), removed outright rather than masked, because a masked userinfo re-encodes to "%3Credacted%3E@host" and stops being a usable URL;
  • any query parameter whose name matches the value or header matchers;
  • any path segment shaped like a JWT (reset-password links).

Percent-encoding, parameter order and the rest of the URL are preserved byte-for-byte: the raw query is rewritten in place, not re-encoded.

func URLWithSecrets added in v0.3.0

func URLWithSecrets(raw string, secrets []string) string

URLWithSecrets is URL for a request that carries a secret somewhere the structural rules cannot see it — a preview secret in a path segment, or under a query-parameter name that does not say "secret" (F9's preview_path is a user template). Every literal, and its query- and path-escaped forms, is replaced with Mask before the structural rules run, so the value never reaches a log line, an error or an envelope in any encoding.

Literals shorter than the Scrubber's minimum are ignored for the same reason they are there: replacing a 3-character "secret" would shred unrelated URL text. The structural query-name rule still masks ?previewSecret=… whatever its length.

func Value

func Value(v any) (any, []string)

Value redacts an already-decoded JSON-ish value in place-by-copy. It is the entry point for cache and audit writers that hold a map rather than bytes. The returned paths use the same notation as Result.Paths.

Types

type Result

type Result struct {
	Data    []byte
	Changed bool
	Paths   []string
}

Result is the outcome of redacting a JSON document.

Data is the original slice when Changed is false — literally the same bytes, so `--output raw` and error.raw stay byte-faithful on the overwhelmingly common path where there was nothing to hide. Paths lists the redacted locations in "docs[0].apiKey" form for the raw_redacted warning (§11.1).

func JSON

func JSON(b []byte) Result

JSON redacts secret values inside a JSON document, preserving every byte it did not have to change. A body that is not valid JSON (an HTML error page, a truncated response) falls back to text scrubbing rather than being dropped.

type Scrubber

type Scrubber struct {
	// Literals are exact strings to replace wherever they appear. Empty and
	// very short entries are ignored, because replacing a 1-character literal
	// would shred unrelated output.
	Literals []string
}

Scrubber adds known literal secret values (the resolved API key, a minted JWT) to the structural rules. The zero Scrubber applies the structural rules only and is what the package-level functions use.

func (Scrubber) JSON

func (s Scrubber) JSON(b []byte) Result

JSON implements the splice-based redaction described in the package doc.

func (Scrubber) StoredDoc added in v0.3.0

func (s Scrubber) StoredDoc(v any, authRoot bool) (any, []string)

StoredDoc redacts a document PayCLI keeps on disk in order to write it back later (a §12.8 backup). It is deliberately narrower than Value, whose §5.3 rules exist for output a human or an LLM reads: every value masked here is a value a restore can never bring back, so only credentials are masked —

  • apiKey, apiKeyIndex, resetPasswordToken(/Expiration), _verificationToken at any depth;
  • hash, salt, password, sessions inside an auth document: the top-level object when authRoot is true (the collection has auth), and any nested object that carries "email" or "username" (a user populated into a relationship, which Payload does even at depth 0);
  • any string containing one of the Scrubber's literal secrets (the resolved API key, the preview secret).

The §5.3 heuristics — keys matching (?i)token|secret, JWT-shaped strings — are NOT applied: they match content (designTokens, an embed token) far more often than credentials in a document body, and a backup that silently lost that content could not undo the write it was taken for.

func (Scrubber) Text

func (s Scrubber) Text(in string) string

Text scrubs credentials out of free-form text.

func (Scrubber) Value

func (s Scrubber) Value(v any) (any, []string)

Value redacts a decoded value (map[string]any, []any, scalars).

Jump to

Keyboard shortcuts

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