Skip to content

Privacy and masking

bddkit hides secrets in everything it prints or writes about a run: the failure text on the console, both reports, the event stream, the debug output on stderr, and the text of bddkit doctor and bddkit resource add. There is one set of secret values and one filter that replaces every occurrence of a value with ***.

A value enters the set in five ways.

  1. The step marks a parameter secret. {key:secret} in a step declaration or a macro template matches what {key} matches, and the value joins the set when the step runs. A literal value is written *** in the step text as well. The Hawk key, the AES key and the SRP password are secret.
  2. A variable’s name has a privacy word. A variable is sensitive when secret is one of the _-separated words of its name, in any position and any case: access_token_secret and reg_secret_verifier are, secretary_id and sessionSecret are not. To mask a token an API returned, save it as as "login_secret" and write "Bearer <<login_secret>>" in the step that uses it.
  3. An API resource names its sensitive headers. authorization, cookie and set-cookie are masked by default. sensitive_headers: [x-api-key] adds to them; sensitive_headers_only: [x-api-key] replaces them.
  4. The system under test names a field. A JSON key, form field or query parameter is sensitive when password, secret or token is one of its words, split on _, - and lower-to-upper case: access_token and refresh-token are, tokenizer is not. The value is hidden where it stands; the text around it is unchanged. A string at least min_length characters long joins the set.
  5. The configuration. Once bddkit.yaml is parsed and its ${…} references are expanded, the value under every key with a field word in its name (password, client_secret) and the password of every URL with credentials join the set. A secret that arrives through ${DB_PASSWORD} or a .env file is caught the same way.
privacy:
words: [sensitive, gdpr] # more words that make a variable name sensitive
fields: [otp] # more field words for names the system under test chose
min_length: 7 # shortest value that joins the set (default 7)
reveal: false # true shows every value

A list given as words, fields or sensitive_headers adds to the default. words_only, fields_only and sensitive_headers_only replace it, and fields_only: [] switches that rule off. A value shorter than min_length is hidden where its position is known, but never joins the set, because hiding every 42 in the output would destroy it.

Every option has three forms, and the highest wins: flag, then environment variable, then config key, then the default.

Config key Flag Environment variable
privacy.words / words_only --privacy-words / --privacy-words-only BDDKIT_PRIVACY_WORDS / BDDKIT_PRIVACY_WORDS_ONLY
privacy.fields / fields_only --privacy-fields / --privacy-fields-only BDDKIT_PRIVACY_FIELDS / BDDKIT_PRIVACY_FIELDS_ONLY
privacy.min_length --privacy-min-length BDDKIT_PRIVACY_MIN_LENGTH
privacy.reveal --privacy-reveal (or --reveal) BDDKIT_PRIVACY_REVEAL

A list flag or variable takes comma-separated words, and an empty variable is unset. A boolean accepts true, false, 1 and 0.

--reveal, privacy.reveal: true or BDDKIT_PRIVACY_REVEAL=1 turns masking off for a run, and says so on stderr. A reveal: true committed for one debugging session would turn masking off in CI too, so CI should set BDDKIT_PRIVACY_REVEAL=false, since the environment wins over the config.

The event stream’s run_started records what was in force, so a consumer can flag a run that arrived unmasked. bddkit doctor has a privacy row that states whether masking is on and which layer decided it.

  • It works forwards. A value is hidden from the moment it enters the set. A secret in a body that is not JSON, or under a name no field word matches, stays visible until a later step saves it into a sensitive variable.
  • A field word can be too eager. With token in the list, next_page_token is hidden wherever it appears. Use fields_only when that hurts.
  • Database steps are not covered by fields. A column called password in I have "users" with "password: …" is not recognised. Pass the value through a sensitive variable.
  • Derived values are not recognised. A hash, a signature or a ciphertext of a secret is a different string.
  • Marking is by name. _secert hides nothing. A family is hidden whole: as "secret_reg" hides every SRP variable of that login, the public ones included.
  • Some places hide a value only when it is long enough: a multipart text part, the message of an assertion that quotes a body, and Print response body as "<path>".
  • A literal in a step’s own text reaches the event stream unmasked unless its parameter is secret, because the stream records the step before the request that would register the value.
  • Images are not filtered.
  • A masked assertion is harder to debug. Use --reveal.

Failure dumps changed for existing suites: Authorization, Cookie, Set-Cookie, and any JSON key, form field or query parameter with password, secret or token as a word, are now ***. Anyone who read those values from a failure needs --reveal or a fields_only list of their own.