Add redaction module: JFR in-process data redaction (JEP 536) on JDK 27

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CifR2BE7PBvRy5hT8TS9oz
This commit is contained in:
2026-10-26 01:43:28 +00:00
co-authored by Claude Sonnet 5.5
parent c7eccd1db5
commit e84684e2c7
17 changed files with 682 additions and 0 deletions
+7
View File
@@ -11,6 +11,13 @@ Every number in the post comes from a file under `docs/output/`, produced by
either the JUnit test suite or a real `jcmd` + `curl` session against a either the JUnit test suite or a real `jcmd` + `curl` session against a
running instance of this app &mdash; see the index below. running instance of this app &mdash; see the index below.
## Modules
| Module | What it is | Post |
|---|---|---|
| (repo root) | Spring Boot app: profiling a live app with JFR and Mission Control | [Java Flight Recorder and Mission Control](https://ankurm.com/java-flight-recorder-mission-control-spring-boot-profiling/) |
| [`redaction/`](redaction/) | JDK 27 JEP 536: keep secrets out of recordings, with a leak scanner | [JFR In-Process Data Redaction](https://ankurm.com/jfr-data-redaction-jep-536-safe-flight-recordings-in-production/) |
## Versions this was built and verified against ## Versions this was built and verified against
| Component | Version | Verified against | | Component | Version | Verified against |
+1
View File
@@ -0,0 +1 @@
build/
+44
View File
@@ -0,0 +1,44 @@
# redaction — JFR In-Process Data Redaction (JEP 536)
Companion module for the ankurm.com post
**"JFR In-Process Data Redaction (JEP 536): Safe Flight Recordings in Production"**
(https://ankurm.com/jfr-data-redaction-jep-536-safe-flight-recordings-in-production/).
It extends the JFR / Mission Control post's workflow with the JDK 27 feature that
removes secrets from recordings before they are written.
No Maven and no dependencies: two small Java files and shell scripts.
| Component | Version | Verified against |
|---|---|---|
| JDK (redaction runs) | Temurin 27.0.1.0.2+1 | `java -version`; JEP 536 status "Closed / Delivered", release 27 |
| JDK (leak baseline) | Temurin 25.0.5+8 | `java -version`; JDK 25 has no redaction |
## Layout
| Path | What it is |
|---|---|
| `src/Workload.java` | Trivial program; the secrets are on its command line, `-D` flags and environment |
| `src/Scan.java` | Checks a `.jfr` file for planted secrets, by raw bytes and by decoded events |
| `filters/keys.txt`, `filters/args.txt` | Filter files for `redact-key=@...` / `redact-argument=@...` |
| `scripts/common.sh` | Plants seven fake secrets under `env -i` (your real environment is never passed in) |
| `scripts/run-all.sh` | Regenerates every file in `output/` |
| `output/*.txt` | Captured real output, quoted verbatim in the post |
## Run it
```bash
JDK27=/path/to/jdk-27 JDK25=/path/to/jdk-25 redaction/scripts/run-all.sh
```
## Output index
| File | What it shows |
|---|---|
| `01-jdk25-leak.txt` | JDK 25: all seven secrets sit in the recording |
| `02-jdk27-defaults.txt` | JDK 27, no flags: three secrets redacted, four still leak |
| `03-jdk27-opt-out.txt` | `redact-argument=none,redact-key=none` brings every leak back |
| `04-jdk27-custom-inline.txt` | `conn*` fails to match `DB_CONN` (globs are anchored); `*conn*` fixes it |
| `05-jdk27-filter-files-and-debug-log.txt` | Filter files with `@`, and `-Xlog:jfr+redact=debug` |
| `06-jdk27-missing-filter-file.txt` | A missing filter file stops the JVM at startup |
| `07-jdk27-no-plus-replaces-defaults.txt` | Without `+`, your list replaces the defaults |
| `08-jdk27-jcmd-late-recording.txt` | A recording started later with `jcmd JFR.start` is redacted the same way |
+3
View File
@@ -0,0 +1,3 @@
--db-pass *
--connection-string *
https://*:*@*
+2
View File
@@ -0,0 +1,2 @@
*db-pass*
*conn*
+163
View File
@@ -0,0 +1,163 @@
### JDK 25: no redaction exists. What is in the recording?
openjdk version "25.0.5" 2026-10-20 LTS
OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (build 25.0.5+8-LTS, mixed mode, sharing)
LEAK-ENV-TOKEN-1 RAW=LEAK EVENTS=1
LEAK-PROP-PASSWORD-2 RAW=LEAK EVENTS=3
LEAK-ARG-DBPASSWORD-3 RAW=LEAK EVENTS=3
LEAK-URL-PW-4 RAW=LEAK EVENTS=3
LEAK-ENV-CONN-5 RAW=LEAK EVENTS=1
LEAK-PROP-DBPASS-6 RAW=LEAK EVENTS=3
LEAK-ARG-DBPASS-7 RAW=LEAK EVENTS=3
exit=2
### jfr print
jdk.JVMInformation {
startTime = 01:42:36.460 (2026-10-26)
jvmName = "OpenJDK 64-Bit Server VM"
jvmVersion = "OpenJDK 64-Bit Server VM (25.0.5+8-LTS) for linux-amd64 JRE (25.0.5+8-LTS), built on 2026-10-20T00:00:00Z with gcc 14.2.0"
jvmArguments = "-XX:StartFlightRecording:filename=/tmp/jfr-redaction/jdk25.jfr -Xmx256m -Djavax.net.ssl.keyStorePassword=LEAK-PROP-PASSWORD-2 -Ddb-pass=LEAK-PROP-DBPASS-6 -Dapp.region=mumbai"
jvmFlags = N/A
javaArguments = "Workload --dbpassword LEAK-ARG-DBPASSWORD-3 --db-pass LEAK-ARG-DBPASS-7 https://john:[email protected]/login --verbose"
jvmStartTime = 01:42:35.982 (2026-10-26)
pid = 6378
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.specification.name"
value = "Java Virtual Machine Specification"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.name"
value = "OpenJDK 64-Bit Server VM"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.home"
value = "/tmp/claude-0/post2/j25/jdk-25.0.5+8"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "app.region"
value = "mumbai"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "sun.java.command"
value = "Workload --dbpassword LEAK-ARG-DBPASSWORD-3 --db-pass LEAK-ARG-DBPASS-7 https://john:[email protected]/login --verbose"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.class.path"
value = "/tmp/claude-0/post2/repo/redaction/build"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "sun.java.launcher"
value = "SUN_STANDARD"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.compressedOopsMode"
value = "32-bit"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "jdk.debug"
value = "release"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "db-pass"
value = "LEAK-PROP-DBPASS-6"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "javax.net.ssl.keyStorePassword"
value = "LEAK-PROP-PASSWORD-2"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.vendor"
value = "Eclipse Adoptium"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.specification.version"
value = "25"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.info"
value = "mixed mode, sharing"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.specification.vendor"
value = "Oracle Corporation"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.vm.version"
value = "25.0.5+8-LTS"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "sun.boot.library.path"
value = "/tmp/claude-0/post2/j25/jdk-25.0.5+8/lib"
}
jdk.InitialSystemProperty {
startTime = 01:42:36.460 (2026-10-26)
key = "java.library.path"
value = "/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:36.460 (2026-10-26)
key = "PATH"
value = "/usr/bin:/bin"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:36.460 (2026-10-26)
key = "DEPLOY_REGION"
value = "eu-west-1"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:36.460 (2026-10-26)
key = "HOME"
value = "/nonexistent"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:36.460 (2026-10-26)
key = "ACCESS_TOKEN"
value = "LEAK-ENV-TOKEN-1"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:36.460 (2026-10-26)
key = "DB_CONN"
value = "LEAK-ENV-CONN-5"
}
+163
View File
@@ -0,0 +1,163 @@
### JDK 27 with NO redaction flags at all
openjdk version "27.0.1.0.2" 2026-10-21
OpenJDK Runtime Environment Temurin-27.0.1.0.2+1 (build 27.0.1.0.2+1)
OpenJDK 64-Bit Server VM Temurin-27.0.1.0.2+1 (build 27.0.1.0.2+1, mixed mode, sharing)
LEAK-ENV-TOKEN-1 RAW=clean EVENTS=0
LEAK-PROP-PASSWORD-2 RAW=clean EVENTS=0
LEAK-ARG-DBPASSWORD-3 RAW=clean EVENTS=0
LEAK-URL-PW-4 RAW=LEAK EVENTS=2
LEAK-ENV-CONN-5 RAW=LEAK EVENTS=1
LEAK-PROP-DBPASS-6 RAW=LEAK EVENTS=2
LEAK-ARG-DBPASS-7 RAW=LEAK EVENTS=2
exit=2
### jfr print
jdk.JVMInformation {
startTime = 01:42:38.133 (2026-10-26)
jvmName = "OpenJDK 64-Bit Server VM"
jvmVersion = "OpenJDK 64-Bit Server VM (27.0.1.0.2+1) for linux-amd64 JRE (27.0.1.0.2+1), built on 2026-10-21T00:00:00Z with gcc 14.2.0"
jvmArguments = "-XX:StartFlightRecording:filename=/tmp/jfr-redaction/default.jfr -Xmx256m [REDACTED] -Ddb-pass=LEAK-PROP-DBPASS-6 -Dapp.region=mumbai"
jvmFlags = N/A
javaArguments = "Workload [REDACTED] [REDACTED] --db-pass LEAK-ARG-DBPASS-7 https://john:[email protected]/login --verbose"
jvmStartTime = 01:42:37.505 (2026-10-26)
pid = 6463
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.specification.name"
value = "Java Virtual Machine Specification"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.name"
value = "OpenJDK 64-Bit Server VM"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.home"
value = "/tmp/claude-0/post2/jdk-27.0.1.0.2+1"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "app.region"
value = "mumbai"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "sun.java.command"
value = "Workload [REDACTED] [REDACTED] --db-pass LEAK-ARG-DBPASS-7 https://john:[email protected]/login --verbose"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.class.path"
value = "/tmp/claude-0/post2/repo/redaction/build"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "sun.java.launcher"
value = "SUN_STANDARD"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.compressedOopsMode"
value = "32-bit"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "jdk.debug"
value = "release"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "db-pass"
value = "LEAK-PROP-DBPASS-6"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "javax.net.ssl.keyStorePassword"
value = "[REDACTED]"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.vendor"
value = "Eclipse Adoptium"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.specification.version"
value = "27"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.info"
value = "mixed mode, sharing"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.specification.vendor"
value = "Oracle Corporation"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.vm.version"
value = "27.0.1.0.2+1"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "sun.boot.library.path"
value = "/tmp/claude-0/post2/jdk-27.0.1.0.2+1/lib"
}
jdk.InitialSystemProperty {
startTime = 01:42:38.133 (2026-10-26)
key = "java.library.path"
value = "/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:38.133 (2026-10-26)
key = "PATH"
value = "/usr/bin:/bin"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:38.133 (2026-10-26)
key = "DEPLOY_REGION"
value = "eu-west-1"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:38.133 (2026-10-26)
key = "HOME"
value = "/nonexistent"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:38.133 (2026-10-26)
key = "ACCESS_TOKEN"
value = "[REDACTED]"
}
jdk.InitialEnvironmentVariable {
startTime = 01:42:38.133 (2026-10-26)
key = "DB_CONN"
value = "LEAK-ENV-CONN-5"
}
+9
View File
@@ -0,0 +1,9 @@
### JDK 27 with redact-argument=none,redact-key=none (the opt-out)
LEAK-ENV-TOKEN-1 RAW=LEAK EVENTS=1
LEAK-PROP-PASSWORD-2 RAW=LEAK EVENTS=2
LEAK-ARG-DBPASSWORD-3 RAW=LEAK EVENTS=2
LEAK-URL-PW-4 RAW=LEAK EVENTS=2
LEAK-ENV-CONN-5 RAW=LEAK EVENTS=1
LEAK-PROP-DBPASS-6 RAW=LEAK EVENTS=2
LEAK-ARG-DBPASS-7 RAW=LEAK EVENTS=2
exit=2
@@ -0,0 +1,46 @@
### JDK 27, first attempt: conn* and a +prefix
flags: -XX:FlightRecorderOptions:redact-key=+db-pass;conn*,redact-argument=+--db-pass *;https://*:*@*
LEAK-ENV-TOKEN-1 RAW=clean EVENTS=0
LEAK-PROP-PASSWORD-2 RAW=clean EVENTS=0
LEAK-ARG-DBPASSWORD-3 RAW=clean EVENTS=0
LEAK-URL-PW-4 RAW=clean EVENTS=0
LEAK-ENV-CONN-5 RAW=LEAK EVENTS=1
LEAK-PROP-DBPASS-6 RAW=clean EVENTS=0
LEAK-ARG-DBPASS-7 RAW=clean EVENTS=0
exit=2
### jfr print (JVMInformation and the DB_CONN variable)
jdk.JVMInformation {
startTime = 01:42:44.988 (2026-10-26)
jvmName = "OpenJDK 64-Bit Server VM"
jvmVersion = "OpenJDK 64-Bit Server VM (27.0.1.0.2+1) for linux-amd64 JRE (27.0.1.0.2+1), built on 2026-10-21T00:00:00Z with gcc 14.2.0"
jvmArguments = "-XX:StartFlightRecording:filename=/tmp/jfr-redaction/wrongglob.jfr -XX:FlightRecorderOptions:redact-key=+db-pass;conn*,redact-argument=[REDACTED] -Xmx256m [REDACTED] -Ddb-pass=[REDACTED] -Dapp.region=mumbai"
jvmFlags = N/A
javaArguments = "Workload [REDACTED] [REDACTED] [REDACTED] [REDACTED] [REDACTED] --verbose"
jvmStartTime = 01:42:43.787 (2026-10-26)
pid = 6681
}
key = "DB_CONN"
value = "LEAK-ENV-CONN-5"
### fixed: *conn* instead of conn*
flags: -XX:FlightRecorderOptions:redact-key=+db-pass;*conn*,redact-argument=+--db-pass *;https://*:*@*
LEAK-ENV-TOKEN-1 RAW=clean EVENTS=0
LEAK-PROP-PASSWORD-2 RAW=clean EVENTS=0
LEAK-ARG-DBPASSWORD-3 RAW=clean EVENTS=0
LEAK-URL-PW-4 RAW=clean EVENTS=0
LEAK-ENV-CONN-5 RAW=clean EVENTS=0
LEAK-PROP-DBPASS-6 RAW=clean EVENTS=0
LEAK-ARG-DBPASS-7 RAW=clean EVENTS=0
exit=0
### jfr print (JVMInformation)
jdk.JVMInformation {
startTime = 01:42:49.421 (2026-10-26)
jvmName = "OpenJDK 64-Bit Server VM"
jvmVersion = "OpenJDK 64-Bit Server VM (27.0.1.0.2+1) for linux-amd64 JRE (27.0.1.0.2+1), built on 2026-10-21T00:00:00Z with gcc 14.2.0"
jvmArguments = "-XX:StartFlightRecording:filename=/tmp/jfr-redaction/fixed.jfr -XX:FlightRecorderOptions:redact-key=+db-pass;*conn*,redact-argument=[REDACTED] -Xmx256m [REDACTED] -Ddb-pass=[REDACTED] -Dapp.region=mumbai"
jvmFlags = N/A
javaArguments = "Workload [REDACTED] [REDACTED] [REDACTED] [REDACTED] [REDACTED] --verbose"
jvmStartTime = 01:42:47.483 (2026-10-26)
pid = 6823
}
@@ -0,0 +1,28 @@
### JDK 27, filters loaded from files with @, plus -Xlog:jfr+redact=debug
keys.txt:
*db-pass*
*conn*
args.txt:
--db-pass *
--connection-string *
https://*:*@*
[0.153s][debug][jfr,redact] Redaction file <module>/filters/args.txt read successfully
[0.156s][debug][jfr,redact] Redaction file <module>/filters/keys.txt read successfully
[0.157s][debug][jfr,redact] Redacted initial environment variable named 'ACCESS_TOKEN'
[0.157s][debug][jfr,redact] Redacted initial environment variable named 'DB_CONN'
[0.157s][debug][jfr,redact] Redacted initial system property named 'javax.net.ssl.keyStorePassword'
[0.157s][debug][jfr,redact] Redacted initial system property named 'db-pass'
[0.157s][debug][jfr,redact] Redacted initial system property named 'sun.java.command'
[0.157s][debug][jfr,redact] Redacted Java Arguments 'Workload [REDACTED] [REDACTED] [REDACTED] [REDACTED] [REDACTED] --verbose'
[0.157s][debug][jfr,redact] Redacted JVM Arguments '-XX:StartFlightRecording:filename=/tmp/jfr-redaction/files.jfr -XX:FlightRecorderOptions:redact-key=+@<module>/filters/keys.txt,redact-argument=[REDACTED] -Xlog:jfr+redact=debug -Xmx256m [REDACTED] -Ddb-pass=[REDACTED] -Dapp.region=mumbai'
[0.157s][debug][jfr,redact] Redacted string flag 'FlightRecorderOptions'
LEAK-ENV-TOKEN-1 RAW=clean EVENTS=0
LEAK-PROP-PASSWORD-2 RAW=clean EVENTS=0
LEAK-ARG-DBPASSWORD-3 RAW=clean EVENTS=0
LEAK-URL-PW-4 RAW=clean EVENTS=0
LEAK-ENV-CONN-5 RAW=clean EVENTS=0
LEAK-PROP-DBPASS-6 RAW=clean EVENTS=0
LEAK-ARG-DBPASS-7 RAW=clean EVENTS=0
exit=0
@@ -0,0 +1,8 @@
### JDK 27, a filter file that does not exist
[0.105s][warning][jfr,redact] Default redaction filters are replaced. Prepend with '+' to add filters to the
[0.105s][warning][jfr,redact] defaults, or specify -XX:FlightRecorderOptions:'redact-key=none;<filters>'
[0.105s][warning][jfr,redact] to replace without a warning.
[0.105s][error ][jfr,redact] Failed to access redaction file /no/such/keys.txt
Error occurred during initialization of VM
Failure when starting JFR on_create_vm_2
exit=1
@@ -0,0 +1,12 @@
### JDK 27, redact-key=db-pass WITHOUT the + prefix replaces the defaults
[0.127s][warning][jfr,redact] Default redaction filters are replaced. Prepend with '+' to add filters to the
[0.127s][warning][jfr,redact] defaults, or specify -XX:FlightRecorderOptions:'redact-key=none;<filters>'
[0.127s][warning][jfr,redact] to replace without a warning.
LEAK-ENV-TOKEN-1 RAW=LEAK EVENTS=1
LEAK-PROP-PASSWORD-2 RAW=LEAK EVENTS=1
LEAK-ARG-DBPASSWORD-3 RAW=clean EVENTS=0
LEAK-URL-PW-4 RAW=LEAK EVENTS=2
LEAK-ENV-CONN-5 RAW=LEAK EVENTS=1
LEAK-PROP-DBPASS-6 RAW=clean EVENTS=0
LEAK-ARG-DBPASS-7 RAW=LEAK EVENTS=2
exit=2
@@ -0,0 +1,17 @@
### JDK 27, recording started later with jcmd JFR.start (JVM launched without any JFR flag)
7047:
Started recording 1. No limit specified, using maxsize=250MB as default.
Use jcmd 7047 JFR.dump name=late to copy recording data to file.
7047:
Stopped recording "late", 206.8 kB written to:
<tmp>/late.jfr
LEAK-ENV-TOKEN-1 RAW=clean EVENTS=0
LEAK-PROP-PASSWORD-2 RAW=clean EVENTS=0
LEAK-ARG-DBPASSWORD-3 RAW=clean EVENTS=0
LEAK-URL-PW-4 RAW=LEAK EVENTS=2
LEAK-ENV-CONN-5 RAW=LEAK EVENTS=1
LEAK-PROP-DBPASS-6 RAW=LEAK EVENTS=2
LEAK-ARG-DBPASS-7 RAW=LEAK EVENTS=2
exit=2
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Shared helpers. Source this; do not run it.
#
# run_case <jdk-home> <recording.jfr> <extra JVM flags...>
# Runs the Workload with the SAME planted secrets every time, under a clean
# environment (env -i), so only the fake secrets below can ever end up in a
# recording. Your real shell environment is never passed through.
HERE="$(cd "$(dirname "$0")/.." && pwd)"
BUILD="$HERE/build"
# The planted secrets. Each one has a name that tells you where it hides.
SECRETS=(LEAK-ENV-TOKEN-1 LEAK-PROP-PASSWORD-2 LEAK-ARG-DBPASSWORD-3 LEAK-URL-PW-4
LEAK-ENV-CONN-5 LEAK-PROP-DBPASS-6 LEAK-ARG-DBPASS-7)
compile() { # <jdk-home>
mkdir -p "$BUILD"
"$1/bin/javac" --release 21 -d "$BUILD" "$HERE/src/Workload.java" "$HERE/src/Scan.java" 2>&1 | grep -v '^Picked up' || true
}
run_jvm() { # <jdk-home> [flags...] (no recording flag: caller decides)
local jdk="$1"; shift
env -i PATH=/usr/bin:/bin HOME=/nonexistent \
ACCESS_TOKEN=LEAK-ENV-TOKEN-1 DB_CONN=LEAK-ENV-CONN-5 DEPLOY_REGION=eu-west-1 \
"$jdk/bin/java" "$@" \
-Xmx256m \
-Djavax.net.ssl.keyStorePassword=LEAK-PROP-PASSWORD-2 \
-Ddb-pass=LEAK-PROP-DBPASS-6 \
-Dapp.region=mumbai \
-cp "$BUILD" Workload \
--dbpassword LEAK-ARG-DBPASSWORD-3 \
--db-pass LEAK-ARG-DBPASS-7 \
https://john:[email protected]/login \
--verbose
}
run_case() { # <jdk-home> <jfr-out> [flags...] (recording from JVM start)
local jdk="$1" out="$2"; shift 2
run_jvm "$jdk" -XX:StartFlightRecording:filename="$out" "$@"
}
scan() { "$1/bin/java" -cp "$BUILD" Scan "$2" "${SECRETS[@]}" 2>&1 | grep -v '^Picked up'; }
print_events() { # <jdk-home> <jfr>
"$1/bin/jfr" print --events InitialSystemProperty,JVMInformation,InitialEnvironmentVariable "$2" 2>&1 | grep -v '^Picked up'
}
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Regenerates every file under redaction/output/.
# Usage: JDK27=/path/to/jdk-27 JDK25=/path/to/jdk-25 redaction/scripts/run-all.sh
# Each case plants the same seven fake secrets (see common.sh), records, then
# greps the recording for them. Recordings go to a scratch dir, not the repo.
set -uo pipefail
source "$(dirname "$0")/common.sh"
: "${JDK27:?set JDK27 to a JDK 27 home}" "${JDK25:?set JDK25 to a JDK 25 home}"
OUT="$HERE/output"; TMP=/tmp/jfr-redaction; rm -rf "$TMP"; mkdir -p "$OUT" "$TMP"
cd "$HERE"
unset JAVA_TOOL_OPTIONS # keep proxy banners out of committed output
compile "$JDK27"
vers() { "$1/bin/java" -version 2>&1 | grep -v '^Picked up'; }
hdr() { echo "### $*"; }
{ hdr "JDK 25: no redaction exists. What is in the recording?"
vers "$JDK25"
run_case "$JDK25" "$TMP/jdk25.jfr" >/dev/null
scan "$JDK25" "$TMP/jdk25.jfr"; echo "exit=${PIPESTATUS[0]}"
echo; hdr "jfr print"; print_events "$JDK25" "$TMP/jdk25.jfr"
} > "$OUT/01-jdk25-leak.txt" 2>&1
{ hdr "JDK 27 with NO redaction flags at all"
vers "$JDK27"
run_case "$JDK27" "$TMP/default.jfr" >/dev/null
scan "$JDK27" "$TMP/default.jfr"; echo "exit=${PIPESTATUS[0]}"
echo; hdr "jfr print"; print_events "$JDK27" "$TMP/default.jfr"
} > "$OUT/02-jdk27-defaults.txt" 2>&1
{ hdr "JDK 27 with redact-argument=none,redact-key=none (the opt-out)"
run_case "$JDK27" "$TMP/none.jfr" -XX:FlightRecorderOptions:redact-argument=none,redact-key=none >/dev/null
scan "$JDK27" "$TMP/none.jfr"; echo "exit=${PIPESTATUS[0]}"
} > "$OUT/03-jdk27-opt-out.txt" 2>&1
{ hdr "JDK 27, first attempt: conn* and a +prefix"
F="-XX:FlightRecorderOptions:redact-key=+db-pass;conn*,redact-argument=+--db-pass *;https://*:*@*"
echo "flags: $F"
run_case "$JDK27" "$TMP/wrongglob.jfr" "$F" >/dev/null
scan "$JDK27" "$TMP/wrongglob.jfr"; echo "exit=${PIPESTATUS[0]}"
echo; hdr "jfr print (JVMInformation and the DB_CONN variable)"
print_events "$JDK27" "$TMP/wrongglob.jfr" | awk '/jdk.JVMInformation/,/^}/ {print} /key = "DB_CONN"/ {print; getline; print}'
hdr "fixed: *conn* instead of conn*"
F="-XX:FlightRecorderOptions:redact-key=+db-pass;*conn*,redact-argument=+--db-pass *;https://*:*@*"
echo "flags: $F"
run_case "$JDK27" "$TMP/fixed.jfr" "$F" >/dev/null
scan "$JDK27" "$TMP/fixed.jfr"; echo "exit=${PIPESTATUS[0]}"
echo; hdr "jfr print (JVMInformation)"
print_events "$JDK27" "$TMP/fixed.jfr" | awk '/jdk.JVMInformation/,/^}/ {print}'
} > "$OUT/04-jdk27-custom-inline.txt" 2>&1
{ hdr "JDK 27, filters loaded from files with @, plus -Xlog:jfr+redact=debug"
echo "keys.txt:"; sed 's/^/ /' "$HERE/filters/keys.txt"
echo "args.txt:"; sed 's/^/ /' "$HERE/filters/args.txt"
echo
F="-XX:FlightRecorderOptions:redact-key=+@$HERE/filters/keys.txt,redact-argument=+@$HERE/filters/args.txt"
run_case "$JDK27" "$TMP/files.jfr" "$F" -Xlog:jfr+redact=debug 2>&1 \
| grep -E 'jfr,redact' | sed "s#$HERE#<module>#g"
echo; scan "$JDK27" "$TMP/files.jfr"; echo "exit=${PIPESTATUS[0]}"
} > "$OUT/05-jdk27-filter-files-and-debug-log.txt" 2>&1
{ hdr "JDK 27, a filter file that does not exist"
env -i PATH=/usr/bin:/bin "$JDK27/bin/java" -XX:FlightRecorderOptions:redact-key=@/no/such/keys.txt -version 2>&1 | grep -v '^Picked up'
echo "exit=${PIPESTATUS[0]}"
} > "$OUT/06-jdk27-missing-filter-file.txt" 2>&1
{ hdr "JDK 27, redact-key=db-pass WITHOUT the + prefix replaces the defaults"
run_case "$JDK27" "$TMP/noplus.jfr" -XX:FlightRecorderOptions:redact-key=db-pass 2>&1 | grep -E 'jfr,redact'
scan "$JDK27" "$TMP/noplus.jfr"; echo "exit=${PIPESTATUS[0]}"
} > "$OUT/07-jdk27-no-plus-replaces-defaults.txt" 2>&1
{ hdr "JDK 27, recording started later with jcmd JFR.start (JVM launched without any JFR flag)"
run_jvm "$JDK27" -Dworkload.sleep.ms=8000 >/dev/null &
sleep 2
PID=$(pgrep -f 'Workload' | head -1)
"$JDK27/bin/jcmd" "$PID" JFR.start name=late filename="$TMP/late.jfr" 2>&1 | grep -v '^Picked up' | sed "s#$TMP#<tmp>#g"
sleep 1
"$JDK27/bin/jcmd" "$PID" JFR.stop name=late 2>&1 | grep -v '^Picked up' | sed "s#$TMP#<tmp>#g"
wait
scan "$JDK27" "$TMP/late.jfr"; echo "exit=${PIPESTATUS[0]}"
} > "$OUT/08-jdk27-jcmd-late-recording.txt" 2>&1
echo "regenerated:"; ls -1 "$OUT"
+35
View File
@@ -0,0 +1,35 @@
import java.nio.file.*;
import java.util.*;
import jdk.jfr.consumer.*;
/**
* Answers one question about a .jfr file: do the planted secrets appear in it?
*
* Two independent checks, because they fail differently:
* RAW - searches the raw bytes of the file for each secret (what an attacker with
* `strings` or a hex editor would do).
* EVENT - searches every field of every event after decoding (what `jfr print` shows).
*
* Usage: java Scan.java recording.jfr secret1 secret2 ...
* Prints one line per secret and exits 0 if none leaked, 2 if any leaked.
*/
public class Scan {
public static void main(String[] a) throws Exception {
Path file = Path.of(a[0]);
byte[] raw = Files.readAllBytes(file);
String rawLatin = new String(raw, java.nio.charset.StandardCharsets.ISO_8859_1);
List<String> events = new ArrayList<>();
try (RecordingFile rf = new RecordingFile(file)) {
while (rf.hasMoreEvents()) events.add(rf.readEvent().toString());
}
boolean leaked = false;
for (int i = 1; i < a.length; i++) {
String s = a[i];
boolean inRaw = rawLatin.contains(s);
long inEvents = events.stream().filter(e -> e.contains(s)).count();
leaked |= inRaw || inEvents > 0;
System.out.printf("%-26s RAW=%-5s EVENTS=%d%n", s, inRaw ? "LEAK" : "clean", inEvents);
}
System.exit(leaked ? 2 : 0);
}
}
+17
View File
@@ -0,0 +1,17 @@
/**
* A deliberately boring program. It does a little work so the JVM has something
* to record, then exits; the recording is written by -XX:StartFlightRecording.
* All the interesting part is on the command line, in -D flags and in the
* environment, which is where the secrets live.
*
* Run with: java Workload.java <args> (single-file source launcher)
* Post: https://ankurm.com/jfr-data-redaction-jep-536-safe-flight-recordings-in-production/
*/
public class Workload {
public static void main(String[] args) throws Exception {
long sum = 0;
for (int i = 0; i < 5_000_000; i++) sum += Integer.hashCode(i);
Thread.sleep(Long.getLong("workload.sleep.ms", 300));
System.out.println("workload done, args=" + args.length + ", checksum=" + sum);
}
}