Skip to content

Cover a secret the rules miss

rtb-redact recognises shapes it has been taught. A bespoke token, a short secret, or a credential inside a JSON payload can pass straight through. This is how to find out, and what to do about it.

Confirm whether your secret is actually caught

Do not guess from the rule list — run a realistic line through string and look at the output. A one-off test is the quickest way:

#[test]
fn our_session_token_is_redacted() {
    let line = format!("session refresh failed for {}", sample_token());
    let out = rtb_redact::string(&line);
    assert!(!out.contains(&sample_token()), "token survived redaction: {out}");
}

Two traps make this test worth writing rather than reasoning about:

  • Punctuation changes the answer. The opaque-run rule needs whitespace or a string end on both sides, so the same token is redacted bare and survives in quotes or before a ;.
  • Neighbouring characters change the answer. A string containing none of @ = ? - _ ., no bearer/basic/token and no 40-character run is returned untouched without any rule running at all. Test the line you will actually emit, not the token on its own.

Redact it at the source instead

The reliable fix is not to let the secret into the string. In order of preference:

  1. Hold it in a type that will not format. secrecy::SecretString, which is what rtb-credentials uses, makes the accidental {} impossible.
  2. Substitute before you build the line. Replace the credential with a placeholder where you assemble the message, so the redactor never has to recognise it.
  3. Log an identifier, not the value. A key id, a fingerprint or a last-four is usually what the log line actually needed.

Then keep string at the boundary anyway, for the strings you did not assemble.

Make the line anchorable if you cannot do either

If a token has to appear and you cannot mask it upstream, you can at least make it reachable by the rules. Both of these hold today:

  • Put it in a recognised assignment — api_key=<value> or token=<value> — and the query-parameter rule masks the value whatever shape it is.
  • Prefix it with Bearer, Basic or Token and the scheme rule masks whatever follows, up to the next whitespace.

This is a workaround, not a design. It depends on the exact rule set, so pin it with a test.

Get the format added to the crate

A credential format that is widely used — a provider prefix other services will hit too — belongs in the catalogue rather than in your workaround. Raise it on the project on GitLab with a shape-matching sample that is not a real credential, and say roughly how long the token is; the prefix rules only fire at 20 characters or more.

There is no way to register a pattern at runtime, and that is deliberate — see Why there is nothing to configure.

Keep the fixture out of a secret scanner's way

Test fixtures for this kind of work have to look like real credentials, which is exactly what push-protection scanners block. The crate's own tests build them by concatenation so no whole literal appears in the committed source:

let sample = concat!("sk-", "abc123456789abcdef");

Do the same in yours.