Secret-bearing argv flags outside the built-in three reach CLIDetail.Argv unscrubbed

A secret-bearing argv flag that is neither one of three built-in names nor declared by its driver reaches CLIDetail.Argv verbatim, and no later layer scrubs it. argv renders to stdout, the JUnit XML and the detail view, so the value lands in a CI artifact.

oras push --api-key hunter2 is the shape. Nothing in the chain catches it:

Layer What it does Catches --api-key?
Executor redact.RedactArgv(argv, e.secretFlags...) (pkg/conformance/cliexec/executor.go:214) Only if the driver declared it via WithSecretFlags
Built-in set secretFlags is an exact-match set of --password, --auth, --token (pkg/conformance/redact/redact.go:68-72) No
Render boundary redact.RedactArgv(c.Argv) (internal/report/detail.go:120), called with no driver flags, so it re-applies the built-in three only No
Five-pattern scan ScanContent runs on stdout and stderr (internal/report/detail.go:130) Never runs on argv at all

So the defense-in-depth story that covers every other secret-bearing output field has a hole at exactly one field. RedactField (pkg/conformance/redact/redact.go:74-88) matches password, token, secret, api[_-]?key and authorization case-insensitively as substrings, and sits twenty lines from the three-name exact-match set that guards argv.

pkg/client/npm/executor.go:80 declares --_auth and pkg/conformance/maven/cliexec.go:229 declares mavenSecretFlags(), so this is a latent gap rather than a confirmed leak in the two drivers that exist today. Whether those two lists are complete is worth checking as part of the fix. The next driver is where it bites.

Why it went unnoticed: S04 states the rule twice, with two different sets

The narrow reading, in §CLI drivers / Invocation contract (docs/specs/S04-contracts.md:1632), §Security Considerations (:2752) and AC #51 (:2364):

replacing recognized secret-bearing flag values (--password, --auth, --token, and driver-specific equivalents declared by the implementation) with [REDACTED]

The five-pattern reading, in §Security Considerations / Field-name redaction (:2767) and §Resolutions / Cross-spec consistency (:3112):

The same five-pattern set is enforced at the CLI driver layer (RunOpts.ExtraEnv keys and argv flag names).

Resolved by adopting the five-pattern set everywhere — CLI driver scrubbing, RunOpts.ExtraEnv keys, slog field-name redaction, and failure_detail content scanning — so the rule reads the same regardless of which layer applies it.

These are not the same rule and not the same mechanism: one is exact match on a whole flag name, the other is a case-insensitive substring pattern. A driver author who reads :2767 or :3112 concludes argv flag names are already pattern-matched and skips the WithSecretFlags call. That is the decision this contradiction changes.

This is the same field an earlier review flagged as the one secret-bearing output field left undefended at the boundary. The fix landed then was RedactArgv at the render boundary, which closed the "a caller built Argv by hand" case but not the pattern case, because RedactArgv carries the three-name set.

Fix

Decide which reading is normative, then make all five passages and the code agree:

  • (a) Pattern-match argv. Give the argv layer the same five patterns as RedactField, matched against the flag name with the --/- prefix and any =value stripped. This is what :2767 and :3112 already promise, and it subsumes most WithSecretFlags declarations. Apply it at the render boundary too, so a hand-built Argv is covered.
  • (b) Keep exact match. Reword :2767 and :3112 to describe the argv layer as it is, an exact-match name list plus driver declarations, and say why it differs from the field-name layer. Then add a guard that a driver declaring a CLI executor cannot ship without an explicit secret-flag review, because under (b) nothing catches an omission.

Either way, AC #51's fixture should exercise a representative outside {--password, --token}. --api-key also covers the [_-]? alternation no current fixture reaches.

How this was found

By reading S04 end to end and enumerating its repeated facts, rather than by reviewing a diff. The contradiction is old and is not visible from any single MR: every edit to S04 so far has been narrow and single-anchor (an amendment row from another spec, a review finding, a targeted fix), and each was verified at its own anchor. No MR has ever owned the question "does S04 still agree with itself".