8367584: Implement JEP 536: JFR In-Process Data Redaction

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2026-06-03 06:33:32 +00:00
parent 8ba600d6f3
commit 39de79eae2
17 changed files with 1621 additions and 78 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,7 @@
#include "jfr/dcmd/jfrDcmds.hpp"
#include "jfr/jfr.hpp"
#include "jfr/jni/jfrJavaSupport.hpp"
#include "jfr/periodic/jfrRedactedEvents.hpp"
#include "jfr/recorder/jfrRecorder.hpp"
#include "jfr/recorder/service/jfrOptionSet.hpp"
#include "jfr/support/jfrThreadLocal.hpp"
@ -401,11 +402,25 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out
};
void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
outputStream* out = output();
print_help(output(), false);
}
static void print_filters(outputStream* out, JfrRedactedEvents::StringArray* filters) {
for (int i = 0; i < filters->length(); i++) {
out->print_cr(" %s", filters->at(i)->text());
}
}
void JfrConfigureFlightRecorderDCmd::print_help(outputStream* out, bool startup) {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
if (startup) {
out->print_cr("Syntax : -XX:FlightRecorderOptions:[options]");
out->print_cr("");
}
out->print_cr("Options:");
out->print_cr("");
out->print_cr(" globalbuffercount (Optional) Number of global buffers. This option is a legacy");
out->print_cr( " %-19s (Optional) Number of global buffers. This option is a legacy",
startup ? "numglobalbuffers": "globalbuffercount");
out->print_cr(" option: change the memorysize parameter to alter the number of");
out->print_cr(" global buffers. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (STRING, default determined by the value for");
@ -427,7 +442,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
out->print_cr(" gigabytes. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (STRING, 10M)");
out->print_cr("");
out->print_cr(" repositorypath (Optional) Path to the location where recordings are stored until");
out->print_cr( " %-19s (Optional) Path to the location where recordings are stored until",
startup ? "repository" : "repositorypath");
out->print_cr(" they are written to a permanent file. (STRING, The default");
out->print_cr(" location is the temporary directory for the operating system. On");
out->print_cr(" Linux operating systems, the temporary directory is /tmp. On");
@ -443,7 +459,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
out->print_cr(" degradation. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (LONG, 64)");
out->print_cr("");
out->print_cr(" thread_buffer_size (Optional) Local buffer size for each thread in bytes if one of");
out->print_cr( " %-19s (Optional) Local buffer size for each thread in bytes if one of",
startup ? "threadbuffersize" : "thread_buffer_size");
out->print_cr(" the following suffixes is not used: 'k' or 'K' for kilobytes or");
out->print_cr(" 'm' or 'M' for megabytes. Overriding this parameter could reduce");
out->print_cr(" performance and is not recommended. This value cannot be changed");
@ -452,14 +469,77 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
out->print_cr(" preserve-repository (Optional) Preserve files stored in the disk repository after the");
out->print_cr(" Java Virtual Machine has exited. (BOOLEAN, false)");
out->print_cr("");
out->print_cr("Options must be specified using the <key> or <key>=<value> syntax.");
out->print_cr("");
out->print_cr("Example usage:");
out->print_cr("");
out->print_cr(" $ jcmd <pid> JFR.configure");
out->print_cr(" $ jcmd <pid> JFR.configure repositorypath=/temporary");
out->print_cr(" $ jcmd <pid> JFR.configure stackdepth=256");
out->print_cr(" $ jcmd <pid> JFR.configure memorysize=100M");
if (startup) {
out->print_cr(" old-object-queue-size (Optional) Maximum number of old objects to track. By default,");
out->print_cr(" the number of objects is set to 256. (LONG, 256)");
out->print_cr("");
out->print_cr(" redact-argument (Optional) Replace command-line arguments that match a");
out->print_cr(" semicolon-separated list of glob patterns, for example,");
out->print_cr(" *secret*;password*. Matching is case-insensitive, and the");
out->print_cr(" supported wildcards are '*' and '?'. To redact multiple arguments,");
out->print_cr(" use a literal space (' ') as a separator. For example, to match");
out->print_cr(" the two arguments --auth username:token, use the filter");
out->print_cr(" --auth *:*. Filters containing spaces must be quoted as a single");
out->print_cr(" command-line argument, for example,");
out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=--auth *:*'.");
out->print_cr(" Arguments containing spaces might not be matched as expected.");
out->print_cr(" The option redact-argument is best-effort and applies only to");
out->print_cr(" command-line arguments in the jdk.JVMInformation event and to");
out->print_cr(" the java.command system property in the jdk.InitialSystemProperty");
out->print_cr(" event. Other events, such as jdk.ProcessStart (child processes),");
out->print_cr(" are not redacted.");
out->print_cr("");
out->print_cr(" If the redact-argument option is not specified, the following");
out->print_cr(" filters are used by default:");
out->print_cr("");
print_filters(out, JfrRedactedEvents::argument_filters());
out->print_cr("");
out->print_cr(" To load patterns from a file (one per line), use @<filename>.");
out->print_cr(" To add to the default patterns instead of replacing them, prefix");
out->print_cr(" the whole list with '+', for example, +*foo*;@redact.txt.");
out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for");
out->print_cr(" command-line arguments. Redacted arguments will be replaced");
out->print_cr(" with '[REDACTED]'. (STRING, default filters)");
out->print_cr("");
out->print_cr(" redact-key (Optional) Replace the value of environment variables and system");
out->print_cr(" properties whose key matches a semicolon-separated list of glob");
out->print_cr(" patterns, for example, *password*;*token*. Matching is");
out->print_cr(" case-insensitive, and the supported wildcards are '*' and '?'.");
out->print_cr(" The option redact-key is best-effort and applies only to the");
out->print_cr(" jdk.InitialSystemProperty, jdk.InitialEnvironmentVariable and");
out->print_cr(" jdk.JVMInformation (-Dkey...) events. Other events, such as");
out->print_cr(" jdk.InitialSecurityProperty, are not redacted.");
out->print_cr("");
out->print_cr(" If the redact-key option is not specified, the");
out->print_cr(" following filters are used by default:");
out->print_cr("");
print_filters(out, JfrRedactedEvents::key_filters());
out->print_cr("");
out->print_cr(" To load patterns from a file (one per line), use @<filename>.");
out->print_cr(" To add to the default patterns instead of replacing them, prefix");
out->print_cr(" the whole list with '+', for example, +*cred*;@keys.txt.");
out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for key");
out->print_cr(" matching. Redacted values will be replaced with '[REDACTED]'.");
out->print_cr(" (STRING, default filters)");
out->print_cr("");
out->print_cr("Options must be specified using the <key>=<value> syntax. Multiple options are separated");
out->print_cr("with a comma.");
out->print_cr("");
out->print_cr("Example usage:");
out->print_cr("");
out->print_cr(" -XX:FlightRecorderOptions:repository=/temporary,stackdepth=256");
out->print_cr(" -XX:FlightRecorderOptions:'redact-key=+*confidential*;*private*,redact-argument=+https://*:*@*'");
out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=+-*private *'");
} else {
out->print_cr("Options must be specified using the <key> or <key>=<value> syntax.");
out->print_cr("");
out->print_cr("Example usage:");
out->print_cr("");
out->print_cr(" $ jcmd <pid> JFR.configure");
out->print_cr(" $ jcmd <pid> JFR.configure repositorypath=/temporary");
out->print_cr(" $ jcmd <pid> JFR.configure stackdepth=256");
out->print_cr(" $ jcmd <pid> JFR.configure memorysize=100M");
}
out->print_cr("");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -202,6 +202,7 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser {
return "Low";
}
static int num_arguments() { return 10; }
static void print_help(outputStream* out, bool startup);
virtual void execute(DCmdSource source, TRAPS);
virtual void print_help(const char* name) const;
};

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -268,37 +268,6 @@ const char* JfrOSInterface::virtualization_name() {
return "No virtualization detected";
}
int JfrOSInterface::generate_initial_environment_variable_events() {
if (os::get_environ() == nullptr) {
return OS_ERR;
}
if (EventInitialEnvironmentVariable::is_enabled()) {
// One time stamp for all events, so they can be grouped together
JfrTicks time_stamp = JfrTicks::now();
for (char** p = os::get_environ(); *p != nullptr; p++) {
char* variable = *p;
char* equal_sign = strchr(variable, '=');
if (equal_sign != nullptr) {
// Extract key/value
ResourceMark rm;
ptrdiff_t key_length = equal_sign - variable;
char* key = NEW_RESOURCE_ARRAY(char, key_length + 1);
char* value = equal_sign + 1;
strncpy(key, variable, key_length);
key[key_length] = '\0';
EventInitialEnvironmentVariable event(UNTIMED);
event.set_starttime(time_stamp);
event.set_endtime(time_stamp);
event.set_key(key);
event.set_value(value);
event.commit();
}
}
}
return OS_OK;
}
int JfrOSInterface::system_processes(SystemProcess** sys_processes, int* no_of_sys_processes) {
return instance()._impl->system_processes(sys_processes, no_of_sys_processes);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -53,7 +53,6 @@ class JfrOSInterface: public JfrCHeapObj {
static int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad);
static int os_version(char** os_version);
static const char* virtualization_name();
static int generate_initial_environment_variable_events();
static int system_processes(SystemProcess** system_processes, int* no_of_sys_processes);
static int network_utilization(NetworkInterface** network_interfaces);
};

View File

@ -41,6 +41,7 @@
#include "jfr/periodic/jfrNativeMemoryEvent.hpp"
#include "jfr/periodic/jfrNetworkUtilization.hpp"
#include "jfr/periodic/jfrOSInterface.hpp"
#include "jfr/periodic/jfrRedactedEvents.hpp"
#include "jfr/periodic/jfrThreadCPULoadEvent.hpp"
#include "jfr/periodic/jfrThreadDumpEvent.hpp"
#include "jfr/recorder/jfrRecorder.hpp"
@ -100,17 +101,8 @@ TRACE_REQUEST_FUNC(ResidentSetSize) {
}
TRACE_REQUEST_FUNC(JVMInformation) {
ResourceMark rm;
EventJVMInformation event;
event.set_jvmName(VM_Version::vm_name());
event.set_jvmVersion(VM_Version::internal_vm_info_string());
event.set_javaArguments(Arguments::java_command());
event.set_jvmArguments(Arguments::jvm_args());
event.set_jvmFlags(Arguments::jvm_flags());
event.set_jvmStartTime(Management::vm_init_done_time());
event.set_pid(os::current_process_id());
event.commit();
}
JfrRedactedEvents::emit_jvm_information();
}
TRACE_REQUEST_FUNC(OSInformation) {
ResourceMark rm;
@ -169,7 +161,9 @@ TRACE_REQUEST_FUNC(NativeLibrary) {
}
TRACE_REQUEST_FUNC(InitialEnvironmentVariable) {
JfrOSInterface::generate_initial_environment_variable_events();
if (!JfrRedactedEvents::emit_initial_environment_variables()) {
log_debug(jfr, system)( "Unable to generate periodic event InitialEnvironmentVariable");
}
}
TRACE_REQUEST_FUNC(CPUInformation) {
@ -393,7 +387,7 @@ TRACE_REQUEST_FUNC(BooleanFlag) {
}
TRACE_REQUEST_FUNC(StringFlag) {
SEND_FLAGS_OF_TYPE(StringFlag, ccstr);
JfrRedactedEvents::emit_string_flags();
}
class VM_GC_SendObjectCountEvent : public VM_GC_HeapInspection {
@ -476,19 +470,7 @@ TRACE_REQUEST_FUNC(YoungGenerationConfiguration) {
}
TRACE_REQUEST_FUNC(InitialSystemProperty) {
SystemProperty* p = Arguments::system_properties();
JfrTicks time_stamp = JfrTicks::now();
while (p != nullptr) {
if (!p->internal()) {
EventInitialSystemProperty event(UNTIMED);
event.set_key(p->key());
event.set_value(p->value());
event.set_starttime(time_stamp);
event.set_endtime(time_stamp);
event.commit();
}
p = p->next();
}
JfrRedactedEvents::emit_initial_system_properties();
}
TRACE_REQUEST_FUNC(ThreadAllocationStatistics) {

View File

@ -0,0 +1,663 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "jfr/jfrEvents.hpp"
#include "jfr/periodic/jfrRedactedEvents.hpp"
#include "jfr/utilities/jfrTime.hpp"
#include "logging/log.hpp"
#include "logging/logMessage.hpp"
#include "runtime/arguments.hpp"
#include "runtime/flags/jvmFlag.hpp"
#include "runtime/os.hpp"
#include "runtime/vm_version.hpp"
#include "services/diagnosticArgument.hpp"
#include "services/diagnosticFramework.hpp"
#include "services/management.hpp"
#include "utilities/ostream.hpp"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
using StringArray = JfrRedactedEvents::StringArray;
using String = JfrRedactedEvents::String;
using StringFlag = JfrRedactedEvents::StringFlag;
using StringKeyValueArray = GrowableArray<JfrRedactedEvents::StringKeyValue*>*;
static const char REDACTED[] = "[REDACTED]";
static const char DELIMITER[] = " ";
static const char REDACT_ARGUMENT_EQUAL[] = "redact-argument=";
static const size_t REDACTED_LENGTH = sizeof(REDACTED) -1;
static const size_t DELIMITER_LENGTH = sizeof(DELIMITER) -1;
static const size_t REDACT_ARGUMENT_EQUAL_LENGTH = sizeof(REDACT_ARGUMENT_EQUAL) -1;
String* JfrRedactedEvents::_redacted_java_command_line = nullptr;
String* JfrRedactedEvents::_redacted_jvm_command_line = nullptr;
String* JfrRedactedEvents::_redacted_flags_command_line = nullptr;
String* JfrRedactedEvents::_redacted_flight_recorder_options = nullptr;
StringKeyValueArray JfrRedactedEvents::_initial_environment_variables = nullptr;
StringKeyValueArray JfrRedactedEvents::_initial_system_properties = nullptr;
GrowableArray<StringFlag*>* JfrRedactedEvents::_string_flags = nullptr;
StringArray* JfrRedactedEvents::_redacted_arguments = nullptr;
StringArray* JfrRedactedEvents::_argument_filters = nullptr;
StringArray* JfrRedactedEvents::_key_filters = nullptr;
bool JfrRedactedEvents::_initialized = false;
bool JfrRedactedEvents::set_argument_filter(const char* filters) {
assert (_argument_filters == nullptr, "invariant");
assert (filters != nullptr, "invariant");
_argument_filters = new StringArray();
return append_filters(_argument_filters, true, filters);
}
bool JfrRedactedEvents::set_key_filter(const char* filters) {
assert (_key_filters == nullptr, "invariant");
assert (filters != nullptr, "invariant");
_key_filters = new StringArray();
return append_filters(_key_filters, false, filters);
}
void JfrRedactedEvents::log_redaction() {
//At this point JFR is not enabled so events will not be emitted
ensure_initialized();
emit_initial_environment_variables(true);
emit_initial_system_properties(true);
emit_jvm_information(true);
emit_string_flags(true);
}
StringArray* JfrRedactedEvents::key_filters() {
ensure_initialized();
return _key_filters;
}
StringArray* JfrRedactedEvents::argument_filters() {
ensure_initialized();
return _argument_filters;
}
void JfrRedactedEvents::add_default_filters(StringArray* target, bool argument) {
if (argument) {
target->add("-*api*key *");
target->add("-*client*secret *");
target->add("-*credential *");
target->add("-*jaas*config *");
target->add("-*passphrase *");
target->add("-*passwd *");
target->add("-*password *");
target->add("-*private*key *");
target->add("-*pwd *");
target->add("-*secret *");
target->add("-*token *");
} else {
target->add("*auth*");
}
target->add("*api*key*");
target->add("*client*secret*");
target->add("*credential*");
target->add("*jaas*config*");
target->add("*passphrase*");
target->add("*passwd*");
target->add("*password*");
target->add("*private*key*");
target->add("*pwd*");
target->add("*secret*");
target->add("*token*");
}
bool JfrRedactedEvents::append_filters(StringArray* target, bool argument, const char* filters) {
const char* option_name = argument ? "redact-argument": "redact-key";
if (filters == nullptr) {
log_warning(jfr, redact)("Missing value for option -XX:FlightRecorderOptions:%s", option_name);
return false;
}
if (filters[0] == '\0') {
LogMessage(jfr, redact) msg;
msg.warning("Default redaction filters are replaced. Specify:");
msg.warning("-XX:FlightRecorderOptions:%s=none to disable filters without a warning.", option_name);
return true;
}
if (strcmp(filters, "none") == 0) {
return true;
}
if (filters[0] == '+') {
filters++;
add_default_filters(target, argument);
} else {
if (strncmp(filters, "none;", 5) != 0) {
LogMessage(jfr, redact) msg;
msg.warning("Default redaction filters are replaced. Prepend with '+' to add filters to the");
msg.warning("defaults, or specify -XX:FlightRecorderOptions:'%s=none;<filters>'", option_name);
msg.warning("to replace without a warning.");
} else {
filters += 5;
}
}
StringArray* filter_array = split(filters, ';');
bool success = true;
for (int i = 0; i < filter_array->length(); i++) {
const String* filter = filter_array->at(i);
if (strcmp(filter->text(), "none") == 0) {
log_error(jfr, redact)("'none' can only be used as the first filter.");
success = false;
break;
}
if (filter->at(0) == '@') {
const char* filename = filter->text() + 1;
if (!read_file(target, filename)) {
success = false;
}
} else {
target->add_contents(filter);
}
}
delete filter_array;
return success;
}
char* JfrRedactedEvents::new_redacted_text() {
char* result = NEW_C_HEAP_ARRAY(char, REDACTED_LENGTH + 1, mtTracing);
strncpy(result, REDACTED, REDACTED_LENGTH + 1);
return result;
}
bool JfrRedactedEvents::emit_initial_environment_variables(bool log) {
if (_initial_environment_variables == nullptr) {
ensure_initialized();
char** envp = os::get_environ();
if (envp == nullptr) {
return false;
}
_initial_environment_variables = make_array<StringKeyValue*>(0);
for (char** p = envp; *p != nullptr; ++p) {
char* variable = *p;
char* equal_sign = strchr(variable, '=');
if (equal_sign != nullptr) {
ptrdiff_t length = equal_sign - variable;
String* key = new String(variable, (size_t)length);
const char* value = equal_sign + 1;
if (is_redacted_key(key->text())) {
value = REDACTED;
if (log) {
log_debug(jfr, redact)("Redacted initial environment variable named '%s'", key->text());
}
}
_initial_environment_variables->append(new StringKeyValue(key, value));
}
}
}
JfrTicks time_stamp = JfrTicks::now();
for (int i = 0; i < _initial_environment_variables->length(); i++) {
StringKeyValue* pair = _initial_environment_variables->at(i);
EventInitialEnvironmentVariable event(UNTIMED);
event.set_starttime(time_stamp);
event.set_endtime(time_stamp);
event.set_key(pair->key());
event.set_value(pair->value());
event.commit();
}
return true;
}
void JfrRedactedEvents::emit_initial_system_properties(bool log) {
if (_initial_system_properties == nullptr) {
ensure_initialized();
_initial_system_properties = make_array<StringKeyValue*>(0);
for (SystemProperty* p = Arguments::system_properties(); p != nullptr; p = p->next()) {
if (!p->internal()) {
const char* value = p->value();
if (strcmp(p->key(), "sun.java.command") == 0) {
value = String::c_str(_redacted_java_command_line);
}
if (is_redacted_key(p->key())) {
value = REDACTED;
}
if (log && value != nullptr && strstr(value, REDACTED) != nullptr) {
log_debug(jfr, redact)("Redacted initial system property named '%s'", p->key());
}
_initial_system_properties->append(new StringKeyValue(p->key(), value));
}
}
}
JfrTicks time_stamp = JfrTicks::now();
for (int i = 0; i < _initial_system_properties->length(); i++) {
StringKeyValue* pair = _initial_system_properties->at(i);
EventInitialSystemProperty event(UNTIMED);
event.set_key(pair->key());
event.set_value(pair->value());
event.set_starttime(time_stamp);
event.set_endtime(time_stamp);
event.commit();
}
}
bool JfrRedactedEvents::match_flag(const char* flag_name, const char* arg) {
if (flag_name == nullptr || arg == nullptr) {
return false;
}
while (*flag_name) {
if (*arg != *flag_name) {
return false;
}
flag_name++;
arg++;
}
return *arg == '\0' || *arg == '=';
}
void JfrRedactedEvents::emit_string_flags(bool log) {
if (_string_flags == nullptr) {
ensure_initialized();
_string_flags = make_array<StringFlag*>(0);
JVMFlag *flag = JVMFlag::flags;
while (flag->name() != nullptr) {
if (flag->is_ccstr() && flag->is_unlocked()) {
const char* value = nullptr;
bool redacted = false;
for (int i = 0; i < _redacted_arguments->length(); i++) {
if (match_flag(flag->name(), _redacted_arguments->at(i)->text())) {
value = REDACTED;
redacted = true;
break;
}
}
if (strcmp("FlightRecorderOptions", flag->name()) == 0) {
if (_redacted_flight_recorder_options != nullptr) {
value = _redacted_flight_recorder_options->text();
redacted = true;
}
}
if (log && redacted) {
log_debug(jfr, redact)("Redacted string flag '%s'", flag->name());
}
_string_flags->append(new StringFlag(flag, value));
}
++flag;
}
}
for (int i = 0; i < _string_flags->length(); i++) {
StringFlag* sf = _string_flags->at(i);
const JVMFlag* flag = sf->jvm_flag();
EventStringFlag event;
event.set_name(flag->name());
if (sf->redacted_value() != nullptr) {
// If a flag is redacted and later changed at runtime,
// the event will still show the redacted text.
event.set_value(sf->redacted_value());
} else {
// If a flag is not redacted the first time,
// it will not be redacted later if it's changed at runtime.
event.set_value(flag->get_ccstr());
}
event.set_origin(static_cast<u8>(flag->get_origin()));
event.commit();
}
}
void JfrRedactedEvents::log_jvm_information_redaction(const char* type, const String* redacted) {
const char* text = String::c_str(redacted);
if (text != nullptr && strstr(text, REDACTED) != nullptr) {
log_debug(jfr, redact)("Redacted %s '%s'", type, text);
}
}
void JfrRedactedEvents::emit_jvm_information(bool log) {
ensure_initialized();
EventJVMInformation event;
event.set_jvmName(VM_Version::vm_name());
event.set_jvmVersion(VM_Version::internal_vm_info_string());
event.set_javaArguments(String::c_str(_redacted_java_command_line));
event.set_jvmArguments(String::c_str(_redacted_jvm_command_line));
event.set_jvmFlags(String::c_str(_redacted_flags_command_line));
event.set_jvmStartTime(Management::vm_init_done_time());
event.set_pid(os::current_process_id());
event.commit();
if (log) {
log_jvm_information_redaction("Java Arguments", _redacted_java_command_line);
log_jvm_information_redaction("JVM Arguments", _redacted_jvm_command_line);
log_jvm_information_redaction("JVM Flags", _redacted_flags_command_line);
}
}
void JfrRedactedEvents::ensure_initialized() {
if (_initialized) {
return;
}
if (_key_filters == nullptr) {
_key_filters = new StringArray();
add_default_filters(_key_filters, false);
}
if (_argument_filters == nullptr) {
_argument_filters = new StringArray();
add_default_filters(_argument_filters, true);
}
if (FlightRecorderOptions != nullptr) {
if (strstr(FlightRecorderOptions, REDACT_ARGUMENT_EQUAL) != nullptr) {
DCmdIter iterator(FlightRecorderOptions, ',');
stringStream result;
size_t pos = 0;
while(iterator.has_next()) {
CmdLine line = iterator.next();
const char* start = line.cmd_addr();
if (strncmp(start, REDACT_ARGUMENT_EQUAL, REDACT_ARGUMENT_EQUAL_LENGTH) == 0) {
result.print(REDACT_ARGUMENT_EQUAL);
result.print(REDACTED);
// Preserve ',' if there are more tokens
pos = iterator.has_next() ? iterator.cursor() - 1 : iterator.cursor();
}
while (pos < iterator.cursor()) {
result.write(FlightRecorderOptions + pos, 1);
pos++;
}
}
_redacted_flight_recorder_options = new String(result.base());
} else {
_redacted_flight_recorder_options = new String(FlightRecorderOptions);
}
}
_redacted_arguments = new StringArray();
StringArray* java_args = make_java_args_array();
_redacted_java_command_line = redact_command_line(java_args);
delete java_args;
StringArray* jvm_args = make_jvm_args_array(Arguments::jvm_args_array(), Arguments::num_jvm_args());
_redacted_jvm_command_line = redact_command_line(jvm_args);
delete jvm_args;
StringArray* flags_args = make_jvm_args_array(Arguments::jvm_flags_array(), Arguments::num_jvm_flags());
_redacted_flags_command_line = redact_command_line(flags_args);
delete flags_args;
_initialized = true;
}
String* JfrRedactedEvents::redact_command_line(StringArray* arguments) {
if (arguments == nullptr) {
return nullptr;
}
GrowableArray<StringArray*>* filters = make_array<StringArray*>(_argument_filters->length());
for (int i = 0 ; i < _argument_filters->length(); i++) {
filters->append(make_filter_array(_argument_filters->at(i)->text()));
}
StringArray* result = new StringArray();
int arg_index = 0;
while (arg_index < arguments->length()) {
int next_index = arg_index;
for (int i = 0; i < filters->length(); i++) {
next_index = match_arguments(filters->at(i), arguments, arg_index);
if (next_index > arg_index) {
break;
}
}
if (next_index > arg_index) {
for (int j = arg_index; j < next_index; j++) {
result->add(REDACTED);
const char* arg = arguments->at(j)->text();
if (arg != nullptr && strncmp(arg, "-XX:", 4) == 0) {
_redacted_arguments->add(arg + 4);
}
}
arg_index = next_index;
} else {
result->add_contents(arguments->at(arg_index));
arg_index++;
}
}
destroy_array(filters);
String* ret = concatenate(result);
delete result;
return ret;
}
String* JfrRedactedEvents::concatenate(StringArray* strings) {
size_t length = 0;
for (int i = 0; i < strings->length(); i++) {
length += strings->at(i)->length();
if (i != 0) {
length += 1;
}
}
String* text = new String(length);
size_t index = 0;
for (int i = 0; i < strings->length(); i++) {
if (i != 0) {
text->set(index++, ' ');
}
const String* s = strings->at(i);
size_t len = s->length();
for (size_t j = 0; j < len; j++) {
text->set(index++, s->at(j));
}
}
return text;
}
bool JfrRedactedEvents::equals_case_insensitive(char a, char b) {
return tolower((unsigned char)a) == tolower((unsigned char)b);
}
bool JfrRedactedEvents::is_redacted_key(const char* key) {
return match_key(_key_filters, key);
}
bool JfrRedactedEvents::is_separator(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
bool JfrRedactedEvents::is_whitespace(char c) {
return c == ' ';
}
StringArray* JfrRedactedEvents::make_filter_array(const char* filter) {
StringArray* result = new StringArray();
while (*filter) {
const char* q = strstr(filter, DELIMITER);
if (q == nullptr) {
size_t len = strlen(filter);
if (len > 0) {
result->add(filter, len);
}
break;
}
size_t len = (size_t)(q - filter);
if (len > 0) {
result->add(filter, len);
}
filter = q + DELIMITER_LENGTH;
}
return result;
}
StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int array_length) {
if (jvm_args_array == nullptr) {
return nullptr;
}
StringArray* result = new StringArray(array_length);
for(int i = 0; i < array_length; i++) {
char* argument = jvm_args_array[i];
if (_redacted_flight_recorder_options != nullptr &&
strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) {
const char* text = _redacted_flight_recorder_options->text();
size_t length = _redacted_flight_recorder_options->length();
// Length must be at least 26 or the JVM will not start.
result->add(new String(argument, 26, text, length));
continue;
}
if (strncmp(argument, "-D", 2) == 0) {
const char* key_start = argument + 2;
const char* eq = strchr(key_start, '=');
if (eq != nullptr) {
size_t key_length = (size_t)(eq - key_start);
String* key_tmp = new String(key_start, key_length);
bool redact = match_key(_key_filters, key_tmp->text());
delete key_tmp;
if (redact) {
size_t unsensitive_length = (size_t)(eq - argument) + 1;
result->add(new String(argument, unsensitive_length, REDACTED, REDACTED_LENGTH));
continue;
}
}
}
result->add(argument);
}
return result;
}
StringArray* JfrRedactedEvents::make_java_args_array() {
StringArray* array = new StringArray();
const char* p = Arguments::java_command();
if (p == nullptr) {
return array;
}
const char* end = p + strlen(p);
while (p < end) {
while (p < end && is_whitespace(*p)) { // Launcher separates arguments with ' '.
p++;
}
if (p >= end) {
break;
}
const char* start = p;
while (p < end && !is_whitespace(*p)) { // Launcher separates arguments with ' '.
p++;
}
size_t length = (size_t)(p - start);
if (length > 0) {
array->add(start, length);
}
}
return array;
}
bool JfrRedactedEvents::match(const char* filter, const char* text) {
const char* filter_last = nullptr;
const char* text_last = nullptr;
while (*text != '\0') {
if (*filter == '*') {
filter_last = filter;
text_last = text;
filter++;
} else if (*filter == '?' || equals_case_insensitive(*filter, *text)) {
filter++;
text++;
} else if (filter_last != nullptr) {
text_last++;
text = text_last;
filter = filter_last + 1;
} else {
return false;
}
}
while (*filter == '*') {
filter++;
}
return *filter == '\0';
}
int JfrRedactedEvents::match_arguments(StringArray* filter, StringArray* arguments, int arg_index) {
int index = arg_index;
if (arg_index + filter->length() <= arguments->length()) {
for (int i = 0; i < filter->length(); i++) {
if (!match(filter->at(i)->text(), arguments->at(index)->text())) {
return arg_index;
}
index++;
}
}
return index;
}
bool JfrRedactedEvents::match_key(StringArray* filters, const char* text) {
for (int i = 0; i < filters->length(); i++) {
const char* filter = filters->at(i)->text();
if (match(filter, text)) {
return true;
}
}
return false;
}
bool JfrRedactedEvents::read_file(StringArray* target, const char* filename) {
FILE* file = os::fopen(filename, "r");
if (file == nullptr) {
log_error(jfr, redact)("Failed to open redaction file: %s", filename);
return false;
}
stringStream ss;
while (true) {
const int c = fgetc(file);
if (c == EOF && ferror(file) != 0) {
log_error(jfr, redact)("Error reading redaction file: %s", filename);
fclose(file);
return false;
}
if (c == EOF || c == '\n') {
const char* line = ss.base();
size_t end = ss.size();
while (end > 0 && is_separator(line[end - 1])) {
end--;
}
if (end > 0) {
target->add(line, end);
}
ss.reset();
if (c == EOF) {
break;
}
} else {
ss.put((char)c);
}
}
fclose(file);
log_debug(jfr, redact)("Redaction file %s read successfully", filename);
return true;
}
StringArray* JfrRedactedEvents::split(const char* text, char separator) {
const char* last = text;
StringArray* result = new StringArray();
for (const char* position = text; *position != '\0'; position++) {
if (*position == separator) {
if (position > last) {
result->add(last, (size_t)(position - last));
}
last = position + 1;
}
}
if (*last != '\0') {
result->add(last, strlen(last));
}
return result;
}

View File

@ -0,0 +1,223 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP
#define SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/growableArray.hpp"
#include <string.h>
class JVMFlag;
class JfrRedactedEvents: public AllStatic {
public:
class StringArray;
// Called at startup
static void log_redaction();
static char* new_redacted_text();
static bool set_argument_filter(const char* filters);
static bool set_key_filter(const char* filters);
static StringArray* argument_filters();
static StringArray* key_filters();
// Synchronized in Java
static bool emit_initial_environment_variables(bool log = false);
static void emit_initial_system_properties(bool log = false);
static void emit_jvm_information(bool log = false);
static void emit_string_flags(bool log = false);
template<typename T> static GrowableArray<T>* make_array(int size) {
return new (mtTracing) GrowableArray<T>(size, mtTracing);
}
template<typename T> static void destroy_array(GrowableArray<T*>* array) {
if (array == nullptr) {
return;
}
for (int i = 0; i < array->length(); ++i) {
delete array->at(i);
}
delete array;
}
class String: public CHeapObj<mtTracing> {
public:
String(size_t length) : _length(length) {
_text = NEW_C_HEAP_ARRAY(char, length + 1, mtTracing);
memset(_text, 0, length + 1);
}
String(const char* source, size_t length) : String(length) {
memcpy(_text, source, length);
}
String(const char* source) : String(source, strlen(source)) {
}
String(const char* prefix, size_t prefix_length, const char* postfix, size_t postfix_length)
: String(prefix_length + postfix_length) {
memcpy(_text, prefix, prefix_length);
memcpy(_text + prefix_length, postfix, postfix_length);
}
~String() {
FREE_C_HEAP_ARRAY(_text);
}
const char* text() const {
return _text;
}
char at(size_t index) const {
assert(index < _length, "out of bounds");
return _text[index];
}
void set(size_t index, char c) {
assert(index < _length, "out of bounds");
_text[index] = c;
}
size_t length() const {
return _length;
}
static String* from_cstr(const char* s) {
return s == nullptr ? nullptr : new String(s);
}
static const char* c_str(const String* s) {
return s == nullptr ? nullptr : s->text();
}
private:
const size_t _length;
char* _text;
};
class StringArray: public CHeapObj<mtTracing> {
public:
StringArray() : _array(make_array<String*>(0)) {
}
StringArray(int capacity) : _array(make_array<String*>(capacity)) {
}
StringArray(const char* const array[], int count) : _array(make_array<String*>(count)) {
for (int i = 0; i < count; ++i) {
_array->append(new String(array[i]));
}
}
~StringArray() {
destroy_array(_array);
}
int length() const {
return _array->length();
}
const String* at(int i) const {
return _array->at(i);
}
void add(String* s) {
_array->append(s);
}
void add_contents(const String* s) {
add(s->text());
}
void add(const char* s) {
_array->append(new String(s));
}
void add(const char* s, size_t length) {
_array->append(new String(s, length));
}
private:
GrowableArray<String*>* const _array;
};
class StringKeyValue: public CHeapObj<mtTracing> {
public:
StringKeyValue(String* key, const char* value): _key(key), _value(String::from_cstr(value)) {
}
StringKeyValue(const char* key, const char* value) : StringKeyValue(new String(key), value) {
}
~StringKeyValue() {
delete _key;
delete _value;
}
const char* key() const {
return _key->text();
}
const char* value() const {
return String::c_str(_value);
}
private:
String* const _key;
String* const _value;
};
class StringFlag: public CHeapObj<mtTracing> {
public:
StringFlag(const JVMFlag* flag, const char* redacted_value)
: _flag(flag),
_redacted_value(String::from_cstr(redacted_value)) {
}
~StringFlag() {
delete _redacted_value;
}
const JVMFlag* jvm_flag() const {
return _flag;
}
const char* redacted_value() const {
return _redacted_value == nullptr ? nullptr : _redacted_value->text();
}
private:
const JVMFlag* const _flag;
String* _redacted_value;
};
private:
static bool _initialized;
static StringArray* _argument_filters;
static StringArray* _key_filters;
static StringArray* _redacted_arguments;
static String* _redacted_java_command_line;
static String* _redacted_jvm_command_line;
static String* _redacted_flags_command_line;
static String* _redacted_flight_recorder_options;
static GrowableArray<StringKeyValue*>* _initial_system_properties;
static GrowableArray<StringKeyValue*>* _initial_environment_variables;
static GrowableArray<StringFlag*>* _string_flags;
static bool append_filters(StringArray* target, bool argument, const char* filters);
static void add_default_filters(StringArray* target, bool argument);
static String* concatenate(StringArray* strings);
static bool equals_case_insensitive(char a, char b);
static bool is_redacted_key(const char* key);
static bool is_separator(char c);
static bool is_whitespace(char c);
static void ensure_initialized();
static StringArray* make_java_args_array();
static StringArray* make_jvm_args_array(char** jvm_args_array, int array_length);
static StringArray* make_filter_array(const char* filter);
static void log_jvm_information_redaction(const char* type, const String* redacted);
static bool match(const char* pattern, const char* text);
static bool match_flag(const char* flag_name, const char* argument);
static int match_arguments(StringArray* filter_array, StringArray* arguments, int arg_index);
static bool match_key(StringArray* array, const char* text);
static bool read_file(StringArray* target, const char* filename);
static String* redact_command_line(StringArray* arguments);
static StringArray* split(const char* text, char separator);
};
#endif // SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP

View File

@ -29,6 +29,7 @@
#include "jfr/jni/jfrJavaSupport.hpp"
#include "jfr/leakprofiler/sampling/objectSampler.hpp"
#include "jfr/periodic/jfrOSInterface.hpp"
#include "jfr/periodic/jfrRedactedEvents.hpp"
#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp"
#include "jfr/periodic/sampling/jfrThreadSampler.hpp"
#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
@ -243,6 +244,9 @@ bool JfrRecorder::on_create_vm_2() {
}
bool JfrRecorder::on_create_vm_3() {
if (log_is_enabled(Debug, jfr, redact)) {
JfrRedactedEvents::log_redaction();
}
JVMTI_ONLY( assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_LIVE, "invalid init sequence, phase is %d", (int)JvmtiEnvBase::get_phase()); )
return CDSConfig::is_dumping_archive() || launch_command_line_recordings(JavaThread::current());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,7 @@
#include "cds/cdsConfig.hpp"
#include "classfile/javaClasses.hpp"
#include "jfr/dcmd/jfrDcmds.hpp"
#include "jfr/periodic/jfrRedactedEvents.hpp"
#include "jfr/recorder/service/jfrMemorySizer.hpp"
#include "jfr/recorder/service/jfrOptionSet.hpp"
#include "jfr/utilities/jfrAllocation.hpp"
@ -262,6 +263,20 @@ static DCmdArgument<bool> _dcmd_preserve_repository(
false,
default_preserve_repository);
static DCmdArgument<char*> _dcmd_redact_argument(
"redact-argument",
"Redact command line arguments",
"STRING",
false,
nullptr);
static DCmdArgument<char*> _dcmd_redact_key(
"redact-key",
"Redact environment variables and system properties",
"STRING",
false,
nullptr);
static DCmdParser _parser;
static void register_parser_options() {
@ -277,6 +292,8 @@ static void register_parser_options() {
_parser.add_dcmd_option(&_dcmd_retransform);
_parser.add_dcmd_option(&_dcmd_old_object_queue_size);
_parser.add_dcmd_option(&_dcmd_preserve_repository);
_parser.add_dcmd_option(&_dcmd_redact_argument);
_parser.add_dcmd_option(&_dcmd_redact_key);
DEBUG_ONLY(_parser.add_dcmd_option(&_dcmd_sample_protection);)
}
@ -285,6 +302,11 @@ static bool parse_flight_recorder_options_internal(TRAPS) {
return true;
}
const size_t length = strlen((const char*)FlightRecorderOptions);
if (strcmp(FlightRecorderOptions, "help") == 0) {
JfrConfigureFlightRecorderDCmd::print_help(tty, true);
vm_exit(0);
}
CmdLine cmdline((const char*)FlightRecorderOptions, length, true);
_parser.parse(&cmdline, ',', THREAD);
if (HAS_PENDING_EXCEPTION) {
@ -332,6 +354,20 @@ bool JfrOptionSet::initialize(JavaThread* thread) {
set_retransform(_dcmd_retransform.value());
}
set_old_object_queue_size(_dcmd_old_object_queue_size.value());
if (_dcmd_redact_argument.is_set()) {
if (!JfrRedactedEvents::set_argument_filter(_dcmd_redact_argument.value())) {
return false;
}
_dcmd_redact_argument.destroy_value();
_dcmd_redact_argument.set_value(JfrRedactedEvents::new_redacted_text());
}
if (_dcmd_redact_key.is_set()) {
if (!JfrRedactedEvents::set_key_filter(_dcmd_redact_key.value())) {
return false;
}
_dcmd_redact_key.destroy_value();
_dcmd_redact_key.set_value(JfrRedactedEvents::new_redacted_text());
}
return adjust_memory_options();
}

View File

@ -169,6 +169,7 @@ class outputStream;
LOG_TAG(ptrqueue) \
LOG_TAG(purge) \
LOG_TAG(record) \
LOG_TAG(redact) \
LOG_TAG(redefine) \
LOG_TAG(ref) \
LOG_TAG(refine) \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -85,6 +85,9 @@ public:
// instance to the caller.
return line;
}
size_t cursor() const {
return _cursor;
}
};
// Iterator class to iterate over diagnostic command arguments

View File

@ -1148,8 +1148,10 @@ These `java` options control the runtime behavior of the Java HotSpot VM.
option is disabled.
[`-XX:FlightRecorderOptions=`]{#-XX_FlightRecorderOptions}*parameter*`=`*value* (or) `-XX:FlightRecorderOptions:`*parameter*`=`*value*
: Sets the parameters that control the behavior of JFR. Multiple parameters can be specified
by separating them with a comma.
: Sets the parameters that control the behavior of JFR.
`-XX:FlightRecorderOptions:help` prints the available options, default
redaction filters, and example command lines. Multiple parameters can be
specified by separating them with a comma.
The following list contains the available JFR *parameter*`=`*value*
entries:
@ -1196,6 +1198,43 @@ These `java` options control the runtime behavior of the Java HotSpot VM.
false, instrumentation is added when event classes are loaded. By
default, this parameter is enabled.
`redact-argument=`argument-filter
: Replace command-line arguments that match a semicolon-separated list
of glob patterns, for example, `*secret*;password*`. Matching is
case-insensitive, and the supported wildcards are `*` and `?`. To redact
multiple arguments, use a literal space (`' '`) as a separator.
For example, to match the two arguments `--auth username:token`, use the
filter `--auth *:*`. Filters containing spaces must be quoted as a single
command-line argument, for example,
`-XX:FlightRecorderOptions='redact-argument=--auth *:*'`.
Arguments containing spaces might not be matched as expected. To load
patterns from a file (one per line) use `@<filename>`. To add to the
default patterns instead of replacing them, prefix the whole list with
`+`, for example, `+*foo*;@redact.txt`. Use `none` (lowercase) to disable
all redaction filters for command-line arguments. Redacted arguments will
be replaced with `[REDACTED]`. The option `redact-argument` is best-effort
and applies only to command-line arguments in the `jdk.JVMInformation`
event and to the `java.command` system property in the
`jdk.InitialSystemProperty` event. Other events, such as `jdk.ProcessStart`
(child processes), are not redacted. Use `-XX:FlightRecorderOptions:help`
to see the default filters used by the `redact-argument` option.
`redact-key=`key-filter
: Replace the value of environment variables and system properties
whose key matches a semicolon-separated list of glob patterns,
for example, `*password*;*token*`. Matching is case-insensitive, and
the supported wildcards are `*` and `?`. To load patterns from a file
(one per line), use `@<filename>`. To add to the default patterns
instead of replacing them, prefix the whole list with `+`,
for example, `+*cred*;@keys.txt`. Use `none` (lowercase) to
disable all redaction filters for key matching. Redacted values
will be replaced with `[REDACTED]`. The option `redact-key` is
best-effort and applies only to the `jdk.InitialSystemProperty`,
`jdk.InitialEnvironmentVariable` and `jdk.JVMInformation` (-Dkey=...)
events. Other events, such as `jdk.InitialSecurityProperty`, are not
redacted. Use `-XX:FlightRecorderOptions:help` to see the default filters
used by the `redact-key` option.
`stackdepth=`*depth*
: Stack depth for stack traces. By default, the depth is set to 64 method
calls. The maximum is 2048. Values greater than 64 could create

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.startupargs;
// Purpose of this class is to allow tests capture environment variables,
// system properties and command line arguments. Using 'java -version' will
// not work as Java command line arguments are not captured.
public class Application {
public static void main(String... args) {
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.startupargs;
import jdk.test.lib.Asserts;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
/**
* @test
* @requires vm.flagless
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @run main jdk.jfr.startupargs.TestOptionsHelp
*/
public class TestOptionsHelp {
public static void main(String... args) throws Exception {
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder("-XX:FlightRecorderOptions:help");
OutputAnalyzer out = ProcessTools.executeProcess(pb);
out.shouldContain("Syntax : -XX:FlightRecorderOptions:[options]");
out.shouldContain("numglobalbuffers");
out.shouldContain("Multiple options are separated");
out.shouldHaveExitValue(0);
}
}

View File

@ -0,0 +1,456 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.startupargs;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.consumer.RecordingFile;
import jdk.test.lib.Asserts;
import jdk.test.lib.jfr.CommonHelper;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
/**
* @test
* @summary Test that redaction of sensitive data works
* @requires vm.flagless
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @build jdk.jfr.startupargs.Application
* @run main/timeout=900 jdk.jfr.startupargs.TestRedact
*/
public class TestRedact {
private static Path TEST_DIRECTORY = Path.of(System.getProperty("test.src", ".")).toAbsolutePath();
private static Path FILE_REDACTED_ARGUMENTS = TEST_DIRECTORY.resolve("redacted-arguments.txt");
private static Path FILE_REDACTED_KEYS = TEST_DIRECTORY.resolve("redacted-keys.txt");
private record Execution(
Path file,
String jvmArgs,
String jvmFlags,
String javaArgs,
Map<String, String> environment,
Map<String, String> systemProperties,
Map<String, String> stringFlags,
OutputAnalyzer output
) {
void assertUnredacted(String text) throws Exception {
if (jvmArgs != null && jvmArgs.contains(text)) {
return;
}
if (javaArgs != null && javaArgs.contains(text)) {
return;
}
if (systemProperties.containsValue(text) || environment.containsValue(text)) {
return;
}
printSystemProperties();
printEnvironment();
printCommandLine();
throw new Exception("Could not find text '" + text + "'. Likely it's been incorrectly redacted.");
}
private void printCommandLine() {
System.out.println("JVM Args: " + jvmArgs);
System.out.println("JVM Flags: " + jvmFlags);
System.out.println("Java Args: " + javaArgs);
System.out.println();
}
void assertRedactedKey(String key) throws Exception {
String v = systemProperties.get(key);
if (v != null) {
printSystemProperties();
if (!v.equals("[REDACTED]")) {
throw new Exception("Expected system property value of key '" + key + "' to be [REDACTED]");
}
return;
}
v = environment.get(key);
if (v == null) {
printEnvironment();
printSystemProperties();
throw new Exception("Expected key '" + key + "' in environment variable or system property");
}
if (!v.equals("[REDACTED]")) {
printEnvironment();
throw new Exception("Expected environment value of key '" + key + "' to be [REDACTED]");
}
}
void printEnvironment() {
printProperties("Environment Variables", environment);
}
void printStringFlags() {
printProperties("String Flags", stringFlags);
}
void printSystemProperties() {
printProperties("System Properties", systemProperties);
}
private void printProperties(String title, Map<String, String> map) {
System.out.println(title);
System.out.println("=".repeat(title.length()));
for (var entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
System.out.println();
}
boolean hasCapitalizedLetter(String text) {
return text.chars().anyMatch(Character::isUpperCase);
}
void assertRedactedArgument(String argument) throws Exception {
if (!hasCapitalizedLetter(argument)) {
throw new IllegalArgumentException("Redacted arguments in test must have at least one capitalized letter to avoid clash with random UUID in temporary filenames.");
}
checkArgument("JVM arguments", jvmArgs, argument);
checkArgument("Java arguments", javaArgs, argument);
checkArgument("Flags", jvmFlags, argument);
checkArgument("sun.java.command", systemProperties.get("sun.java.command"), argument);
}
private void checkArgument(String kind, String arguments, String argument) throws Exception {
if (arguments != null && arguments.contains(argument)) {
System.out.println("ARGS: " + arguments);
throw new Exception("Found '" + argument + "' in " + kind + ". Should have been [REDACTED]");
}
}
public void print() {
printCommandLine();
printEnvironment();
printSystemProperties();
printStringFlags();
}
}
public static void main(String... args) throws Exception {
testRedactionOfRedacted();
testRedactKey();
testRedactArgument();
testRedactMultiple();
testWildcards();
testDefaults();
testRedactFile();
testAppendable();
testEmpty();
testBinary();
}
private static void testRedactionOfRedacted() throws Exception {
Execution e = run(
"-XX:FlightRecorderOptions:" +
"maxchunksize=1MB,redact-argument=Zebra," +
"old-object-queue-size=256," +
"redact-key=tiger",
"Zebra",
"Tiger"
);
e.assertRedactedArgument("Zebra");
e.assertRedactedArgument("redact-argument=Zebra");
e.assertUnredacted("Tiger");
}
private static void testAppendable() throws Exception {
Execution e = run(
Map.of(
"Secret", "Thing", // For default filter
"Cat", "Bird" // For user-defined filter
),
Map.of(
"Secret", "Stuff", // For default filter
"Cat", "Dog" // For user-defined filter
),
"-XX:FlightRecorderOptions:redact-argument=+Foo;Bar,redact-key=+Cat",
"Foo", // For user-defined
"Bar", // For user-defined
"Secret"); // For default filter
e.assertRedactedArgument("Foo"); // Matched by user-defined filter
e.assertRedactedArgument("Bar"); // Matched by user-defined filter
e.assertRedactedKey("Cat"); // Matched by user-defined filter
e.assertRedactedArgument("Secret"); // Matched by default filter *secret*
e.assertRedactedKey("Secret"); // Matched by default filter *secret*
}
private static void testWildcards() throws Exception {
Execution e = run(
"-XX:FlightRecorderOptions:redact-argument=*PPLE;ORAN*;C*HE*RY;N?2?4?;A?C*?F",
"APPLE", "ORANGE", "CHERRY", "N12345", "ABCDEF", "N4711"
);
e.assertRedactedArgument("APPLE");
e.assertRedactedArgument("ORANGE");
e.assertRedactedArgument("CHERRY");
e.assertRedactedArgument("N12345");
e.assertRedactedArgument("ABCDEF");
e.assertUnredacted("N4711");
// Tests that only fully matched filters are redacted
Execution f = run(
"-XX:FlightRecorderOptions:redact-argument=-*TIGER* *",
"--TIGER"
);
f.assertUnredacted("--TIGER");
}
private static void testBinary() throws Exception {
Execution unredacted = run(
Map.of("stuff1", "hammock"),
Map.of("stuff2", "haberdashery"),
"-XX:FlightRecorderOptions:stackdepth=64",
"hammock"
);
if (!contains(unredacted.file, "hammock")) {
throw new Exception("Expected 'hammock' to be in recording file");
}
if (!contains(unredacted.file, "haberdashery")) {
throw new Exception("Expected 'haberdashery' to be in recording file");
}
Execution redacted = run(
Map.of("what", "zippers"),
Map.of("where", "haberdashery"),
"-XX:FlightRecorderOptions:redact-argument=zippers,redact-key=what;where",
"zippers"
);
if (contains(redacted.file, "zippers")) {
redacted.print();
throw new Exception("Expected all occurrences of 'zippers' to be redacted");
}
if (contains(redacted.file, "haberdashery")) {
redacted.print();
throw new Exception("Expected all occurrences of 'haberdashery' to be redacted");
}
}
private static boolean contains(Path file, String text) throws IOException {
byte[] bytes = Files.readAllBytes(file);
byte[] chars = text.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i <= bytes.length - chars.length; i++) {
if (matchesAt(bytes, chars, i)) {
System.out.print("Found '" + text + "' at position " + i);
System.out.println(" in file " + file.toAbsolutePath());
return true;
}
}
return false;
}
private static boolean matchesAt(byte[] bytes, byte[] chars, int position) {
for (int j = 0; j < chars.length; j++) {
if (bytes[position + j] != chars[j]) {
return false;
}
}
return true;
}
private static void testRedactFile() throws Exception {
Execution e1 = run(
Map.of("stuff1", "Snake"),
Map.of("stuff2", "Snake"),
"-XX:FlightRecorderOptions:redact-argument=@" + FILE_REDACTED_ARGUMENTS,
"https://John:Smith@www.example.com/myresource",
"-conf-Key=chicken",
"/Foo/Dog",
"-Header", "Authorization:Bearer", "Banana",
"Apple");
e1.assertRedactedArgument("https://John:Smith@www.example.com/myresource");
e1.assertRedactedArgument("conf-Key=chicken");
e1.assertRedactedArgument("/Foo/Dog");
e1.assertRedactedArgument("-Header");
e1.assertRedactedArgument("Authorization:Bearer");
e1.assertRedactedArgument("Banana");
e1.assertRedactedArgument("Apple");
e1.assertUnredacted("Snake");
Execution e2 = run(
Map.of("confidential", "apple"),
Map.of("very-sensitive", "banana"),
"-XX:FlightRecorderOptions:redact-key=@" + FILE_REDACTED_KEYS,
"Snake");
e2.assertRedactedKey("confidential");
e2.assertRedactedKey("very-sensitive");
e2.assertUnredacted("Snake");
}
private static void testEmpty() throws Exception {
var environment = Map.of("API_TOKEN", "Zebra1");
var properties = Map.of("API_KEY", "Zebra2");
Execution e1 = run(environment, properties,
"-XX:FlightRecorderOptions:redact-key=,redact-argument=", "Zebra3"
);
e1.output().shouldContain("Default redaction filters are replaced.");
e1.output().shouldContain("redact-key=none to disable filters without a warning");
e1.output().shouldContain("redact-argument=none to disable filters without a warning");
e1.assertUnredacted("Zebra1");
e1.assertUnredacted("Zebra2");
e1.assertUnredacted("Zebra3");
Execution e2 = run(environment, properties,
"-XX:FlightRecorderOptions:redact-argument=none,redact-key=none", "Zebra3"
);
e2.output().shouldNotContain("Default redaction filters are replaced.");
e2.output().shouldNotContain("redact-key=none to disable filters without a warning");
e2.output().shouldNotContain("redact-argument=none to disable filters without a warning");
e2.assertUnredacted("Zebra1");
e2.assertUnredacted("Zebra2");
e2.assertUnredacted("Zebra3");
}
private static void testDefaults() throws Exception {
Execution e = run(
Map.of("apiKey","thing"),
Map.of("apiKey", "stuff"),
"-DapiKey=stuff",
"Secret",
"-password=Foo"
);
e.assertRedactedArgument("Secret");
e.assertRedactedArgument("-password=Foo");
e.assertRedactedKey("apiKey");
}
private static void testRedactArgument() throws Exception {
Execution e = run(
"-XX:FlightRecorderOptions:redact-argument=" +
// FILTERS
"Plain;" +
"--option *;" +
"--pass-phrase *;" +
"--login * *;" +
"--colon-based:*;" +
"https://*:*@*",
// SENSITIVE INFORMATION
"Plain",
"--option", "Option-value",
"--pass-phrase", "Cant-be-whitespace-outage",
"--login", "John", "N4711",
"--colon-based:Hello",
"https://Smith:abc123@example.com/path",
// UNSENSITIVE INFORMATION
"Banana"
);
e.assertRedactedArgument("Plain");
e.assertRedactedArgument("Option-value");
e.assertRedactedArgument("Cant-be-whitespace-outage");
e.assertRedactedArgument("Hello");
e.assertRedactedArgument("John");
e.assertRedactedArgument("N4711");
e.assertRedactedArgument("Smith:abc123");
e.assertUnredacted("Banana");
}
private static void testRedactMultiple() throws Exception {
Execution e = run(
Map.of("foo", "bird", "bar, ", "orange"),
Map.of("foo", "tiger", "bar", "banana"),
"-XX:FlightRecorderOptions:redact-key=foo;bar,redact-argument=Baz;Quz", "Baz", "Quz");
e.assertRedactedKey("foo");
e.assertRedactedKey("bar");
e.assertRedactedArgument("Baz");
e.assertRedactedArgument("Quz");
}
private static void testRedactKey() throws Exception {
Execution e = run(
Map.of("cart", "wheel", "banana", "split", "rose", "bud"),
Map.of("banana", "split", "cat", "milk", "carpet","magic", "rose", "bud"),
"-XX:FlightRecorderOptions:redact-key=banana;ca*t",
"rose"
);
e.assertRedactedKey("banana");
e.assertRedactedKey("cat");
e.assertRedactedKey("carpet");
e.assertUnredacted("rose");
}
private static Execution run(String options, String... args) throws Exception {
return run(Map.of(), Map.of(), options, args);
}
private static Execution run(Map<String, String> environment, Map<String, String> properties, String options, String... args) throws Exception {
List<String> arguments = new ArrayList<>();
Path file = Path.of("file.jfr");
for (var entry : properties.entrySet()) {
arguments.add("-D" + entry.getKey() + "=" + entry.getValue());
}
arguments.add("-XX:StartFlightRecording:filename=" + file.toAbsolutePath().toString());
arguments.add(options);
arguments.add("jdk.jfr.startupargs.Application");
arguments.addAll(Arrays.asList(args));
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(arguments);
pb.environment().put("env.secret", "confidential");
// An environment variable redacted by default filters
pb.environment().putAll(environment);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
System.out.println(output.getOutput());
output.shouldHaveExitValue(0);
var environmentVariables = new HashMap<String, String>();
var systemProperties = new HashMap<String, String>();
var stringFlags = new HashMap<String, String>();
var jvmArgs = new AtomicReference<String>();
var jvmFlags= new AtomicReference<String>();
var javaArgs = new AtomicReference<String>();
try (var es = EventStream.openFile(file)) {
es.onEvent("jdk.InitialSystemProperty", e -> {
systemProperties.put(e.getString("key"), e.getString("value"));
});
es.onEvent("jdk.InitialEnvironmentVariable", e -> {
environmentVariables.put(e.getString("key"), e.getString("value"));
});
es.onEvent("jdk.JVMInformation", e -> {
jvmArgs.set(e.getString("jvmArguments"));
javaArgs.set(e.getString("javaArguments"));
jvmFlags.set(e.getString("jvmFlags"));
});
es.onEvent("jdk.StringFlag", e -> {
stringFlags.put(e.getString("name"), e.getString("value"));
});
es.start();
}
return new Execution(file,
jvmArgs.get(), jvmFlags.get(), javaArgs.get(),
environmentVariables, systemProperties, stringFlags, output);
}
}

View File

@ -0,0 +1,6 @@
https://*:*@*
apple
-conf-key=*
/foo/*
--passphrase *
-header Authorization:Bearer *

View File

@ -0,0 +1,2 @@
*sensitive
confidential