JDK Flight Recorder has always captured more than you’d think to ask
for. Command-line arguments, environment variables, system properties —
all useful for figuring out why a JVM is behaving strangely, all
written verbatim into the recording file. Which is fine until someone
attaches that .jfr file to a support ticket and it turns out
--dbpassword was one of the arguments.
JEP 536 fixes this in JDK 27 by redacting sensitive values inside the JVM, before they’re ever written to the recording — not by scrubbing the file afterward and hoping nobody looked at it first.
The problem, concretely #
This is what used to end up in a recording, untouched:
export ACCESS_TOKEN=SECRET_TOKEN
java -XX:StartFlightRecording:filename=dump.jfr \
-Xmx2G \
-Djavax.net.ssl.keyStorePassword=SECRET_PASSWORD \
-jar application.jar \
--dbpassword ANOTHER_SECRET_PASSWORDThree different secrets, three different places they leak from: an
environment variable, a system property, and a plain CLI argument.
Every one of them lands in jdk.InitialEnvironmentVariable,
jdk.InitialSystemProperty, or jdk.JVMInformation events inside the
recording, in plain text.
How it actually redacts #
JFR now recognizes sensitive key/value pairs and arguments as it generates the events,
and writes [REDACTED] in place of the value.
That distinction matters: a post-hoc scrubber can always miss a field someone adds later, or get skipped if someone reads the raw event stream instead of the final file. Redacting at the source means there’s nothing downstream to forget to scrub.
It’s on by default #
You don’t have to configure anything to get baseline protection — JFR ships with a default set of glob patterns it already treats as sensitive:
| Applies to | Default patterns |
|---|---|
redact-key (env vars, system properties) |
*api*key*, *auth*, *client*secret*, *credential*, *jaas*config*, *passphrase*, *passwd*, *password*, *private*key*, *pwd*, *secret*, *token* |
redact-argument (CLI arguments) |
Same list, minus *auth* |
redact-argument deliberately drops *auth* from its default list.
*auth* on redact-key catches things like AUTH_TOKEN just fine,
but on command-line arguments the same glob would also match
--author, --authority, and anything else that merely contains
“auth” — a false positive on ordinary, non-sensitive flags. Worth
remembering if you add your own custom patterns: broader globs are
more likely to swallow legitimate data the wider they get.
Configuring it yourself #
Both knobs hang off -XX:FlightRecorderOptions, as sub-options
separated by commas — each sub-option’s own filters are separated by
semicolons:
export CONFIDENTIAL=SOME_SECRET
java -XX:FlightRecorderOptions:'redact-key=confidential,redact-argument=https://*:*@*' \
-XX:StartFlightRecording:filename=dump.jfr \
-Dconfidential=ANOTHER_SECRET \
-jar application.jar https://john:[email protected]/login --verboseThat single redact-argument=https://*:*@* pattern is doing real work
— it catches credentials embedded in URLs, a shape the default list
doesn’t cover.
Matching more than one argument. A pattern can span the flag and its value(s) as separate space-separated tokens:
# matches `--password` followed by exactly one argument
java -XX:FlightRecorderOptions:'redact-argument=--password *' ...
# matches `--password` followed by exactly two arguments
java -XX:FlightRecorderOptions:'redact-argument=--password * *' ...Loading filters from a file — useful once the list gets long
enough that jamming it into a JVM flag stops being reasonable — with
an @ prefix, one filter per line:
java '-XX:FlightRecorderOptions:[email protected],[email protected]' ...Adding to the defaults instead of replacing them with a leading
+:
java -XX:FlightRecorderOptions:'redact-key=+confidential;secret;@keys.txt' ...Turning it off, if you need the old unredacted behavior back for some reason:
java -XX:FlightRecorderOptions:'redact-argument=none,redact-key=none' ...Checking it actually worked #
Two ways to verify redaction is doing what you expect, before you ship a recording anywhere:
# debug logging while the JVM runs
java -Xlog:jfr+redact=debug ...
# inspect the events most likely to carry secrets
jfr print --events InitialSystemProperty,JVMInformation,StringFlag,InitialEnvironmentVariable dump.jfrThe catch #
Recordings taken on JDK 27 will look different from recordings taken
on earlier versions, by design — anywhere a secret used to show up in
plain text, you now get [REDACTED]. If you have existing tooling
that parses jdk.InitialEnvironmentVariable or similar events and
expects real values, JEP 536 will break it silently rather than
loudly, since [REDACTED] is still a valid string in that field.
Worth auditing before the upgrade, not after.
For the record, the previous unredacted behavior is still one flag
away (redact-argument=none,redact-key=none from above) — nothing
here is a one-way door.