Documentation / VFX-D-1207

VFX-D-1207

Title

SecretLiteralInSuite

Explanation

A value in the suite looks like a credential written out in full, where a secret reference belongs. This is the only semantic code that can be reported at severity error, and the only one that makes validate_suite's valid field false — a suite that ships a live credential in its source is unfit as written, not merely untidy.

The call still succeeds (isError stays false): a diagnostic is data, and the finding tells you what to change.

The severity depends on which shape matched, so read it off the finding rather than assuming. Three of the four shapes are structural — each one is a secret or is nothing — and report at error:

  • a PEM block header for a private key (-----BEGIN PRIVATE KEY, -----BEGIN ENCRYPTED PRIVATE KEY, -----BEGIN OPENSSH PRIVATE KEY, and the algorithm-qualified spellings such as -----BEGIN RSA PRIVATE KEY);
  • an AWS access key id (AKIA or ASIA followed by sixteen upper-case alphanumerics);
  • a connection string carrying an inline password (Password= / pwd= followed by a real value).

The fourth shape is an inference rather than a fact, and reports at warning, which never changes valid:

  • one long, unbroken, mixed-case, high-entropy token — at least 40 base64/URL-safe characters at 4.5 bits of Shannon entropy per character or above.

That split is deliberate. An opaque high-entropy token can be a JWT or it can be a base64 message payload, and nothing textual separates the two — so the entropy arm reports what it sees and leaves the verdict alone. Only a structural match fails a suite.

Public PEM material is never reported. A certificate, a public key, and a certificate signing request are all artefacts designed to be handed out, so a suite that pins one to assert a TLS handshake is doing something correct. Only the private forms match.

A placeholder-shaped password is never reported. Password=, Password={pw}, Password=<your-password> and Password=%s are all templates, not credentials. Password=hunter2 is a credential and is reported at error.

A ${secret:...} reference is never reported. That is the correct practice and the thing this finding tells you to adopt, so any value containing ${ is skipped before any heuristic runs — a connection string whose password is already parameterised is correct, not suspicious.

The offending value is never reproduced. The finding names the shape that matched and points at the node with its path and location; it never quotes the value into a message, a path, or a fix. This server also never resolves a secret reference and never reads its own environment (see the secret-hygiene invariant in docs/overview.md).

Common causes

  • A credential pasted in while debugging locally and committed by accident.
  • A connection string copied from a local development configuration file, password and all.
  • A private key inlined into a step's body or headers rather than referenced. (A certificate or public key inlined the same way is not reported — it is public material.)
  • A long generated API token placed in an authorization header value.

Fixes

  • Replace the literal with a secret reference — the ${secret:<source>/<path>} form documented in the language reference — so the engine resolves it at run time and this server never sees the value.
  • Treat the literal as compromised: rotate it. It was in a file, and it may be in version control.
  • For a value that is not actually secret but happens to match the entropy shape (a generated fixture id, a base64 message payload): nothing is broken and there is nothing you must do. That shape reports at warning, valid stays true, and the suite validates and runs exactly as it did. Filter the finding by severity if the noise is unwanted. Note that moving the value — into the root variables block, say — does not silence it: this rule walks every string value in the document, wherever it sits.
  • There is deliberately no bypass for a structural match. A private-key PEM header, an AKIA/ASIA key id, or a Password= with a real value beside it is a credential in your suite's source; the fix is to replace it with a reference and rotate it, not to silence the finding.