fix(crypto): redact the types that hold key plaintext

What

Gives NamespaceKey, LocalProvider and DefaultNamespaceKeyManager the redaction method set RedactedString established — String, GoString, Format, MarshalJSON, LogValue — so each renders [REDACTED] in every fmt, slog and JSON sink.

Why

The three types hold key plaintext. %v on *LocalProvider prints the raw root key; %+v on the manager prints every cached KEK. Both are live variables in cmd/artifact-registry today. A root key that reaches a log aggregator cannot be un-leaked — it forces a root-key rotation and puts every backup in scope.

Nothing formats these types today: every verb in the package and in cmd/artifact-registry is %q on a URI, %s on a UUID, %d on a length, or %w on an error. This is defense in depth against a future caller. It cannot be a lint rule — go vet rejects %s on a type with no String method, but %v is valid for every type, so the method set is the only mechanism that expresses the rule.

Ciphertext-bearing types are deliberately untouched. Redacting NamespaceKeyRecord would blank what the rotation tooling reports when a re-wrap fails, and its contents are useless without the root key.

Receiver kinds differ, deliberately

LocalProvider's methods take a value receiver, because a copy of it is legal Go: with a pointer receiver, fmt.Printf("%v", *provider) finds no Format method, falls back to struct reflection, and prints the root keys. TestLocalProvider_RedactsTheRootKey/dereferenced_value pins that case — against a pointer receiver it fails with the key in decimal bytes.

DefaultNamespaceKeyManager keeps a pointer receiver, because it holds a named sync.Mutex: copying it is a copylocks vet error, so the dereference path is already refused. A value receiver where a copy is legal, a pointer receiver where a copy is already refused.

That mix makes recvcheck fire on LocalProvider, suppressed at the type with the reasoning inline. The alternative — moving WrapKey, UnwrapKey and KeyURI to value receivers as well — is signature churn on a security-critical type for a change scoped to adding redaction.

TestNamespaceKey_LeaksFromAnUnexportedField pins the one sink redaction cannot reach: fmt reflects past methods on an unexported field, the same limitation RedactedString documents.

Diff size

810 insertions across four files, past the 500-line threshold: 143 production, 503 test, 163 documentation, and 1 line of local_provider.go (the recvcheck suppression).

The test-to-production ratio is high because the surface is a matrix, not a function: five methods across three types, each reachable through fmt verbs, encoding/json, encoding.TextMarshaler and slog, plus the decode guards and the two boundary cases the design rests on (a dereferenced value, and a nil pointer). Splitting would cut across that matrix rather than along it, and the documentation carries rationale the repository's comment caps keep out of the source file, following known_answer.md's precedent in the same package.

Edited by Suleimi Ahmed

Merge request reports

Loading
Loading