skip to content

In REST Assured, which LogConfig call keeps an Authorization header out of the printed request?

level: juniorimportance: should knowfreq 45%

answer

  1. the log keeps the name, loses the value
  2. a fixed marker, not a hash
  3. three default names, all request-side
  4. one method adds, one method replaces

basics

~20 s

LogConfig.blacklistDefaultSensitiveHeaders() masks exactly Authorization, Proxy-Authorization and Cookie, and blacklistHeader(name, others...) adds any other name. REST Assured still prints the header name but replaces its value with the literal marker [ BLACKLISTED ]. The blacklist reaches the request log.

solid answer

~40 s

Header redaction lives on `LogConfig`, not on the log DSL. `blacklistDefaultSensitiveHeaders()` covers exactly three names — `Authorization`, `Proxy-Authorization` and `Cookie` — and `blacklistHeader(String header, String... others)` adds anything else, such as a club API key. Matching is case-insensitive, and the printed line keeps the header name while the value becomes the literal `[ BLACKLISTED ]`, so you can still see the header was sent. Apply it like any other config: `given().config(config().logConfig(logConfig().blacklistDefaultSensitiveHeaders()))`, or set it once on `RestAssured.config`. Two traps are worth naming out loud. `blacklistHeaders(Collection)` **replaces** the whole set rather than adding to it, and the blacklist is what the request specification hands to its `RequestLoggingFilter` — so treat it as protection for the request log, and never assume a response printed through a `ResponseLoggingFilter` has been redacted.

code

java · 16 lines
java
import static io.restassured.RestAssured.given;
import static io.restassured.config.LogConfig.logConfig;
import static io.restassured.config.RestAssuredConfig.config;

given()
    .config(config().logConfig(logConfig()
        .blacklistDefaultSensitiveHeaders()
        .blacklistHeader("X-Club-Key")))
    .log().ifValidationFails()
    .baseUri("https://ladder.curlingclub.test")
    .header("Authorization", "Bearer " + ladderToken)
    .header("X-Club-Key", clubKey)
.when()
    .get("/rinks/rink-3/ladder")
.then()
    .statusCode(200);

go deeper

for a junior

Know the two calls by name and what the output looks like: the header name survives and its value becomes [ BLACKLISTED ]. Be able to say the request still carries the real value.

for a middle

Explain that LogConfig is immutable, that blacklistHeader adds while blacklistHeaders replaces, and that the default set is exactly Authorization, Proxy-Authorization and Cookie rather than a heuristic over values.

for a senior

Argue for turning it on suite-wide the same day failure logging goes on, and be able to say which log paths it actually covers so nobody builds a false sense of safety around response output.

for a principal

Treat it as one control in a layer, not the control. Decide what a build log is allowed to contain at all, and how a leak that already happened gets detected and the credential rotated.

## The problem redaction solves Failure-only logging is worth having because it puts the failing request in the CI job output. That is also its hazard: the failing request against the curling-ladder API carries `Authorization: Bearer <token>` and a `X-Club-Key`, and a CI job's output is read by more people, and kept for longer, than anyone thinks about when they add the log call. **The blacklist is the setting that lets you keep the log and lose the credential.** ## The three calls All of them live on `LogConfig` and all return a new `LogConfig`, because the config objects are immutable: - `blacklistHeader(String header, String... otherHeaders)` — adds one or more names to whatever is already blacklisted. This is the additive form. - `blacklistHeaders(Collection<String> headers)` — **replaces** the blacklist with exactly the names in the collection. Calling it after `blacklistHeader("X-Club-Key")` drops the club key again, which is the single most common surprise here. - `blacklistDefaultSensitiveHeaders()` — adds the three names REST Assured considers sensitive by default: `Authorization`, `Proxy-Authorization` and `Cookie`. Exactly three; it is not an open-ended heuristic and it does not look at your header values. Note what is *not* on that default list: `Set-Cookie`, `WWW-Authenticate`, and any bespoke key your own service invented. Anything beyond the three has to be named with `blacklistHeader`. ## What the printed line looks like The blacklist is consulted by the printer, header by header, at the moment it builds the log text. A blacklisted name is printed with its value swapped for the fixed string `[ BLACKLISTED ]`: ``` Headers: Accept=application/json Authorization=[ BLACKLISTED ] X-Club-Key=[ BLACKLISTED ] ``` Two consequences follow from keeping the name: 1. You can still tell from the log **that** the header was sent, which is exactly what you need when diagnosing a 401 on the ladder endpoint. 2. You cannot tell **what** it contained, so a redacted log can never on its own explain a wrong-credential failure. That is the intended trade. Name matching ignores case, so blacklisting `authorization` masks a header set as `Authorization`. ## Where it applies, and where it does not This is the boundary to get right, because the naive reading is too generous. | log path | blacklist applied? | |---|---| | the request log built by the request specification | yes — the configured blacklist is handed to the `RequestLoggingFilter` | | a response printed by `then().log(...)` on the validatable response | yes — the printer receives the configured blacklist | | a response printed through a `ResponseLoggingFilter` | no — that filter is constructed with an empty blacklist | The safe operating rule is the one the setting was designed for: **the blacklist protects the request log.** Since `Authorization`, `Proxy-Authorization` and `Cookie` are all request headers, that covers the case that matters. Do not architect a secret out of a response body or a `Set-Cookie` header by relying on this switch — it will not be masked on every path, and a body is never masked at all. The blacklist works on header names only; a token embedded in a query string or a JSON payload is printed verbatim. ## Applying it Because `LogConfig` is immutable, every setting has to be chained onto the same instance. Per request: ```java given().config(config().logConfig(logConfig() .blacklistDefaultSensitiveHeaders() .blacklistHeader("X-Club-Key"))) ``` Or once for the suite, by assigning `RestAssured.config`. The pitfall of the global form is that a *second* assignment built from a fresh `logConfig()` starts from defaults and silently discards the first — so put every log setting the suite needs in one chain rather than assigning `RestAssured.config` twice. ## What it does not change - The request on the wire is untouched. Redaction happens in the printer, not in the header list, so the real value still reaches the server. - It is not a secrets mechanism. It stops one specific leak — a value in a printed log — and says nothing about where the token came from or how long it is valid. - It is not retroactive. A log that was already printed unredacted is already in the job output; the fix is to configure the blacklist and rotate whatever leaked. - It does not remove the header from the log, only its value, so header-count assertions and eyeball checks still work. Configured once beside the failure-logging switch, this is a two-line change that makes a diagnosable CI log safe to leave in place.

  • Does blacklisting a header change what REST Assured actually sends?
    No. The blacklist is consulted by the printer at log time only; the header value is swapped for `[ BLACKLISTED ]` in the text being built. The real `Authorization` header goes on the wire exactly as you set it, which is why a redacted log can never by itself explain an authentication failure.
  • Why can a token still appear in a CI log after blacklistDefaultSensitiveHeaders() is configured?
    `LogConfig` is immutable and every `logConfig()` call starts from defaults, so a later `RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()` — which internally builds a fresh `LogConfig` — discards the blacklist set earlier. Chain both settings onto one `LogConfig` instead of assigning `RestAssured.config` twice.

saying these in an interview costs you the question

  • Thinks blacklisting stops the header from being sent
  • Assumes the header name disappears from the log too
  • Believes blacklistHeaders(Collection) adds to the existing set
  • Expects the blacklist to redact every response log as well
  • Thinks blacklist name matching is case-sensitive
  • Assumes a token in a query string or body is masked too