Documentation
¶
Overview ¶
Package redact removes known secret values from text on its way somewhere that keeps it.
Why this exists ¶
A task that holds a leased credential can echo it. Not maliciously — a `set -x` in a build script, a debug flag, an HTTP library that dumps its request headers, a stack trace carrying an argv. The credential then lands in three places that outlive the lease: the task artifact under .cloop/tasks/, the step log in state.db, and the live-log frame broadcast to every attached browser. The lease expires in minutes; the transcript does not.
pkg/audit already scans for this, which is the tell: the leak was expected and only reported afterwards. This package prevents it instead, at the point output is captured.
What it is not ¶
It does not hunt for secret-shaped strings. There is no entropy heuristic and no regex for "looks like a token", because both are on a hot streaming path and both mangle ordinary text — a base64 blob in a diff is not a credential. It matches only values the caller already knows are secret, which for cloop means the material a lease just injected.
The short-value guard ¶
A value shorter than MinLen is never matched. A four-character credential is not one, and replacing every occurrence of a short string would corrupt unrelated output for no security benefit. This is the same guard executor.RedactSecrets has always applied; it lives here now so there is one implementation rather than one per call site.
Index ¶
Constants ¶
const EnvKey = "CLOOP_REDACT_ENV"
EnvKey names the environment variable through which a lease declares which *other* variables hold credentials.
It carries names, never values — exactly like executor.SecretBinding.EnvKeys and for the same reason: it is inherited by every child process and would otherwise be one more copy of the secret. The indirection is what lets a workload scrub its own output without a heuristic: GITHUB_TOKEN holds a credential and CLOOP_GITHUB_REPO_ALLOWLIST holds a repository list, and nothing about the two values themselves says which is which.
const LeaseDirKey = "CLOOP_LEASE_DIR"
LeaseDirKey names the variable holding the lease's own credential directory. It is set by pkg/secretbroker; this package only reads it.
const Marker = "[redacted]"
Marker replaces a matched secret. It is the literal pkg/audit's scanners and the existing git-path redaction already use, so a transcript reads the same whichever layer caught the value.
const MinLen = 8
MinLen is the shortest value worth matching; see the package doc.
Variables ¶
This section is empty.
Functions ¶
func IsPlaceholder ¶
IsPlaceholder reports whether v is something a redactor already left behind rather than a credential.
It matters because a Spec is stored with its leased variables replaced by a placeholder — see executorstore's redactLeasedEnv, which writes "<redacted:leased>" — and a spec rehydrated from that store after a hub restart would otherwise hand this package its own output as a secret to match. The result would not be a disclosure, but it would be a Set that exists to replace "[redacted]" with "[redacted]", doing per-chunk work on the streaming path for nothing and mangling a diagnostic string that is supposed to be legible.
A credential that genuinely looked like this would be skipped. That is the right trade: the value is already published in the source of two packages.
Types ¶
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Set is a collection of secret values to remove. A nil *Set is valid and redacts nothing, so callers with no lease need no special case.
Safe for concurrent use: it is immutable after New.
func FromEnviron ¶
FromEnviron builds a Set from a process environment: the values of every variable EnvKey names, plus the contents of every file in the lease directory.
This is how a workload redacts its *own* output. The hub knows what it injected and scrubs at the executor boundary, but the harness runs inside the sandbox and its output is captured there first — by the orchestrator, into an artifact — so the guarantee has to hold on both sides of that boundary.
Unreadable files are skipped rather than reported. A missing lease directory is the ordinary case for a project with no grants, and failing to redact is not a reason to fail a run.
func New ¶
New returns a Set over values, ignoring those shorter than MinLen and any duplicates. It returns nil when nothing survives that filter, which callers may use to skip redaction entirely.
func (*Set) Bytes ¶
Bytes is String over a byte slice. The input is not modified; a copy is returned only when something matched.
func (*Set) Contains ¶
Contains reports whether text holds any value in the Set. It is the cheap question a test or an audit asks; callers scrubbing output want String.
func (*Set) Holdback ¶
Holdback returns how many trailing bytes of text a streaming caller must withhold because they could still turn out to be the start of a secret.
It is exported for callers that do their own buffering rather than writing through a Writer — a log bus fanning one chunk out to many subscribers cannot hand its bytes to an io.Writer and still number them. Zero means the whole of text is safe to publish now, which is the usual answer.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer wraps an io.Writer so that nothing carrying a known secret reaches it.
Chunk boundaries are handled: a value split across two Writes is still caught, because the tail that could be the start of one is withheld until the next Write resolves it. Flush releases any such remainder, and callers that own the stream's end must call it or risk losing a trailing partial line.
Safe for concurrent use, because the streams it wraps — a driver's output pump and its reaper — race by nature.
func NewWriter ¶
NewWriter returns a Writer forwarding scrubbed output to dst. A nil Set yields a Writer that forwards verbatim, so the caller needs no branch.