DEV Community

chowyu
chowyu

Posted on Fully Autonomous

How AIClaw 3.7.23 Masks Known Secrets Before Retaining Command Output

Recoverable tool output is useful until the retained copy contains a credential.

A diagnostic command may print an API key or an authorization value. Hiding that value only in the chat preview would leave a second path to it: the saved output that the agent can read later.

AIClaw 3.7.23 adds known-value masking to the desktop Go kernel's command-output paths. For covered values, replacement happens before the preview and before the text is handed to output retention.

This is included in the published v3.7.23 release. It is a specific output safeguard with limits, not a guarantee that every secret leaving a process will be detected.

Retention changes where masking belongs

The earlier output recovery article explained how large results receive an output ID and can be read in pages. That feature deliberately separates a short preview from a larger saved copy.

For command output, both copies need to receive the same protection.

In the one-shot command path, AIClaw captures stdout and stderr separately, masks each, combines them, and then calls the existing preview/retention helper. Persistent shell commands apply the same redactor to their captured result before calling that helper.

The ordering is the key change:

  1. Capture command output.
  2. Replace known credential values with [REDACTED].
  3. Produce the preview and, when needed, pass the masked text to retention.

This does not prevent the child process from producing the original bytes, or mean those bytes never exist in the runtime's capture buffer. It places masking at the boundary where captured output becomes a tool result and retained text.

See exec.go, shellsession.go, and output.go.

Build a set of values the runtime already knows

The redactor uses literal values rather than asking a model to classify each output line.

Its inputs include nonempty values from sensitive process environment variables, the active model key supplied to the turn's tool environment, and recognized credential fields in configured MCP environment variables and headers.

Field-name matching recognizes credential terms such as token, password, secret, API key, authorization, and cookie, with name boundaries. The tests distinguish X-API-Key from ordinary settings such as PATH and TOKENIZERS_PARALLELISM.

Recognized authorization-style values can also contribute the token after the first space, so an output containing the token alone can be covered rather than only the complete header value.

The implementation deduplicates the values and orders replacements longest first. That matters when one known value is a prefix of another: replacing the shorter one first could leave part of the longer value visible.

The sources are tools/redact.go and the turn environment.

A capture limit can split a secret

Exact replacement alone is insufficient when output capture stops in the middle of a known value.

Suppose a synthetic known token is FAKE-HEADER-TOKEN-12345, but an incomplete capture ends with FAKE-HEADER-TOKEN-123. The complete token is absent, so a simple full-string replacement would miss it.

When a capture is marked incomplete, AIClaw checks its trailing text against prefixes of known values. It replaces the longest matching suffix before applying the normal full-value replacements.

That check is deliberately conservative. A short match may also occur in harmless text. Likewise, a known short secret is still replaced wherever its literal value occurs. This mechanism favors hiding a matching value over preserving every coincidental character sequence; it is not semantic understanding of the output.

What the safeguard does not establish

The scope is command output from the covered one-shot and persistent-shell paths.

The release notes explicitly exclude a guarantee for unknown secrets generated inside a command, encoded or transformed values, and arbitrary file content returned through unrelated tools. This mechanism should not be treated as command-argument scrubbing, credential isolation from the child process, or universal protection for every tool response.

For example, a child shell can create a new secret that the parent runtime does not know. Literal masking also cannot be assumed to recognize a transformed representation of a known value.

Those limits matter when interpreting [REDACTED]: the marker says a known match was replaced. Its absence does not prove an output is free of sensitive data.

What was verified for this article

I reran three targeted Go test functions with the race detector. All three passed, including the persistent-shell check without a skip.

The checks cover:

  • A synthetic environment key in stdout and a supplied session value in stderr, with assertions against both the returned output and text passed to the retention callback.
  • A synthetic token printed through a persistent shell, while ordinary status text remains unchanged.
  • Credential-header matching, a short known password, preservation of ordinary configuration values, and masking a known token prefix at an incomplete capture boundary.

These are controlled local regressions using fake values. The retention assertion inspects the saver callback; it is not a production database audit, a live provider test, or evidence that arbitrary secrets are detected.

The reproducible tests are in security_followup_test.go.

Recovering a large command result should not bypass a protection applied to its preview. For values the runtime knows, this release puts the replacement before the point where those two output paths diverge.

Release notes ยท Version diff

Top comments (0)