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:
- 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.
- 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
- func AuthorizationValue(value string) string
- func Fingerprint(secret string) string
- func Header(name string) bool
- func HeaderValue(name, value string) string
- func Headers(h http.Header, alsoSecret ...string) http.Header
- func IsJWT(s string) bool
- func Key(name string) bool
- func MaskSecret(secret string) string
- func Text(s string) string
- func URL(raw string) string
- func URLWithSecrets(raw string, secrets []string) string
- func Value(v any) (any, []string)
- type Result
- type Scrubber
Constants ¶
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.
const FingerprintLen = 16
FingerprintLen is §4.4's "16 hex everywhere" rule.
const Mask = "<redacted>"
Mask replaces every redacted value.
Variables ¶
This section is empty.
Functions ¶
func AuthorizationValue ¶
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 ¶
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 HeaderValue ¶
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 ¶
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 Key ¶
Key reports whether a JSON object key (or a query-parameter name) names a value that must never be emitted.
func MaskSecret ¶
MaskSecret renders a secret as "<redacted:fp=...>". An empty secret yields the plain mask so the output never implies a credential existed.
func Text ¶
Text scrubs credentials out of free-form text: log lines, server messages, error strings and command echoes.
func URL ¶
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
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.
Types ¶
type Result ¶
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).
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) StoredDoc ¶ added in v0.3.0
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.