JEP 536: JFR In-Process Data Redaction
Маскирование данных JFR внутри процесса
| Ответственный | Erik Gahlin |
| Тип | Feature |
| Область | JDK |
| Статус | Closed / Delivered |
| Выпуск | 27 |
| Компонент | hotspot / jfr |
| Обсуждение | hotspot dash jfr dash dev at openjdk dot org |
| Трудоёмкость | M |
| Длительность | M |
| Рецензенты | Markus Grönlund, Vladimir Kozlov |
| Одобрен | Mikael Vidstedt |
| Создан | 2025/11/30 02:51 |
| Обновлён | 2026/08/18 21:08 |
| Задача | 8372760 |
Аннотация
Доработать JDK Flight Recorder (JFR), чтобы в записях маскировались аргументы командной строки и начальные значения переменных окружения и системных свойств. Маскировать эти данные до того, как они покинут процесс, чтобы конфиденциальная информация не утекала.
Мотивация
JFR — встроенный в HotSpot JVM фреймворк диагностики с низкими накладными расходами. Он записывает информацию о среде выполнения и приложении в события с отметками времени в файле записи. Позже их анализируют инструменты, например команда jfr и JDK Mission Control.
Файлы записей JFR обычно содержат события с аргументами командной строки и значениями переменных окружения и системных свойств, чтобы было видно, как процесс был запущен и настроен. Поэтому в этих событиях могут оказаться конфиденциальные данные, например секреты в аргументах командной строки, токены доступа в переменных окружения и пароли в системных свойствах. Сейчас эти данные попадают в файлы записей без изменений, и конфиденциальная информация может утечь, когда записи передают другим, архивируют или прикладывают к обращениям в поддержку.
Например, вы можете запустить приложение с включённым JFR так:
$ export ACCESS_TOKEN=SECRET_TOKEN
$ java -XX:StartFlightRecording:filename=dump.jfr \
-Xmx2G \
-Djavax.net.ssl.keyStorePassword=SECRET_PASSWORD \
-jar application.jar \
--dbpassword ANOTHER_SECRET_PASSWORD
Полученный файл записи dump.jfr содержит события, в которых видны все три секретных значения:
- событие
jdk.InitialEnvironmentVariableзаписывает значение переменной окруженияACCESS_TOKEN, - событие
jdk.InitialSystemPropertyзаписывает значение системного свойстваjavax.net.ssl.keyStorePassword, а - событие
jdk.JVMInformationзаписывает аргумент, переданный параметру командной строки приложения--dbpassword.
Маскирование данных внутри процесса предотвратило бы такую утечку и сохранило бы неконфиденциальную информацию, нужную для диагностики проблем, например максимальный размер кучи (-Xmx2G).
Описание
Теперь JFR по умолчанию маскирует многие виды конфиденциальной информации без какой-либо дополнительной настройки.
Вы можете явно выбрать информацию, которую JFR должен маскировать, с помощью новых подпараметров существующего параметра командной строки -XX:FlightRecorderOptions. Каждый подпараметр задаёт один или несколько фильтров, которые выбирают маскируемые аргументы командной строки, переменные окружения и системные свойства.
-
Подпараметр
redact-argumentзадаёт список фильтров для аргументов командной строки. Если один из фильтров совпадает с аргументом, аргумент маскируется. -
Подпараметр
redact-keyзадаёт список фильтров для пар «ключ — значение», то есть переменных окружения и системных свойств. Если один из фильтров совпадает с ключом, связанное с ним значение маскируется.
Сопоставление выполняется без учёта регистра. Фильтры используют glob-шаблоны, где * и ? — подстановочные символы. Если фильтр совпадает с аргументом или ключом, аргумент или значение ключа записывается в событие как [REDACTED]. Несколько фильтров разделяются точкой с запятой (;) и проверяются в заданном порядке. Когда фильтр совпадает с аргументом, этот аргумент маскируется и проверка продолжается со следующего аргумента. Несколько подпараметров -XX:FlightRecorderOptions разделяются запятыми.
Например, чтобы маскировать любую переменную окружения или системное свойство с именем confidential или CONFIDENTIAL, а также любой аргумент командной строки, похожий на URL с именем пользователя и паролем (т. е. username:password@host), используйте:
$ 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:YET_ANOTHER_SECRET@example.com/login --verbose
Чтобы убедиться, что конфиденциальная информация замаскирована, используйте jfr print:
$ jfr print \
--events InitialSystemProperty,JVMInformation,StringFlag,InitialEnvironmentVariable \
dump.jfr
jdk.JVMInformation {
startTime = 17:39:02.196 (2026-02-15)
jvmVersion = "Java HotSpot(TM) 64-Bit Server VM"
jvmArguments = "-Dconfidential=[REDACTED]
-XX:FlightRecorderOptions:redact-key=confidential,redact-argument=[REDACTED]
-XX:StartFlightRecording:filename=dump.jfr"
jvmFlags = "N/A"
javaArguments = "-jar application.jar [REDACTED] --verbose"
jvmStartTime = 17:39:02.050 (2026-02-15)
pid = 43671
}
jdk.InitialSystemProperty {
startTime = 17:39:02.196 (2026-02-15)
key = "confidential"
value = "[REDACTED]"
}
jdk.InitialSystemProperty {
startTime = 17:39:02.196 (2026-02-15)
key = "sun.java.command"
value = "-jar application.jar [REDACTED] --verbose"
}
jdk.StringFlag {
startTime = 17:39:02.196 (2026-02-15)
name = "FlightRecorderOptions"
value = "redact-key=confidential,redact-argument=[REDACTED]"
origin = "Command line"
}
jdk.InitialEnvironmentVariable {
startTime = 17:39:02.244 (2026-02-15)
key = "CONFIDENTIAL"
value = "[REDACTED]"
}
Чтобы отладить сопоставление фильтров redact-argument и redact-key, запустите JVM с параметром командной строки -Xlog:jfr+redact=debug: так видно, какие аргументы командной строки, переменные окружения и системные свойства маскируются.
Сопоставление нескольких аргументов командной строки
Иногда аргумент командной строки задаёт имя параметра, значение которого передаётся в следующем аргументе, например --password SENSITIVE. Маскировать только --password было бы недостаточно, а сопоставление с самим SENSITIVE потребовало бы раскрыть в фильтре что-то о SENSITIVE и тем самым ослабило бы его конфиденциальность.
Для этого фильтр может совпадать с последовательностью аргументов командной строки, разделённых пробелами. Например, этот фильтр маскирует любой аргумент с именем --password вместе со следующим аргументом:
$ java -XX:FlightRecorderOptions:'redact-argument=--password *' ...
Чтобы сопоставить три аргумента, добавьте ещё один пробел и ещё одну звёздочку:
$ java -XX:FlightRecorderOptions:'redact-argument=--password * *' ...
Загрузка фильтров из файла
Чтобы командные строки не становились слишком длинными, JFR может загружать фильтры маскирования из файла. Так списки фильтров можно повторно использовать в разных развёртываниях и обновлять фильтры без изменения параметров запуска JVM. Файлы фильтров содержат по одному фильтру в строке; пробелы в конце строки игнорируются.
Чтобы указать файл фильтров в командной строке, добавьте перед именем файла префикс @:
$ java '-XX:FlightRecorderOptions:redact-argument=@args.txt,redact-key=@keys.txt' ...
JFR читает файлы фильтров при запуске JVM. Если файл не удаётся прочитать, JVM выводит сообщение об ошибке с указанием файла и завершает работу. Чтобы проверить, что файл фильтров успешно загружен, используйте -Xlog:jfr+redact=debug.
При необходимости один и тот же файл фильтров может использоваться и для подпараметра redact-argument, и для подпараметра redact-key.
Фильтры по умолчанию
Если подпараметр redact-key не указан, JFR использует для пар «ключ — значение» следующий список фильтров по умолчанию:
*api*key*
*auth*
*client*secret*
*credential*
*jaas*config*
*passphrase*
*passwd*
*password*
*private*key*
*pwd*
*secret*
*token*
Если подпараметр redact-argument не указан, JFR использует для аргументов командной строки следующий список фильтров по умолчанию:
-*api*key *
-*client*secret *
-*credential *
-*jaas*config *
-*passphrase *
-*passwd *
-*password *
-*private*key *
-*pwd *
-*secret *
-*token *
*api*key*
*client*secret*
*credential*
*jaas*config*
*passphrase*
*passwd*
*password*
*private*key*
*pwd*
*secret*
*token*
Список фильтров по умолчанию для redact-argument похож на список для redact-key, за исключением того, что
-
он включает фильтры, совпадающие с несколькими аргументами; например,
-*password *совпадает и с-password SECRET, и с--password SECRET; и -
в нём нет фильтра
*auth*, чтобы не совпадали слова вродеauthor.
Вернёмся к исходному примеру:
$ export ACCESS_TOKEN=SECRET_TOKEN
$ java -XX:StartFlightRecording:filename=dump.jfr \
-Xmx2G \
-Djavax.net.ssl.keyStorePassword=SECRET_PASSWORD \
-jar application.jar \
--dbpassword ANOTHER_SECRET_PASSWORD
JFR маскирует:
- значение переменной окружения
ACCESS_TOKENс помощью фильтра по умолчанию*token*, - значение системного свойства
javax.net.ssl.keyStorePasswordс помощью фильтра по умолчанию*password*, а также - параметр командной строки
--dbpasswordи его аргументANOTHER_SECRET_PASSWORDс помощью фильтра по умолчанию-*password *.
Добавление к фильтрам по умолчанию
Чтобы при указании собственных фильтров сохранить и фильтры по умолчанию, добавьте перед первым фильтром префикс +:
$ java -XX:FlightRecorderOptions:'redact-key=+confidential;secret;@keys.txt' ...
Отключение маскирования
Чтобы полностью отключить маскирование, укажите none:
$ java -XX:FlightRecorderOptions:'redact-argument=none' ...
Синтаксис
Синтаксис списков фильтров:
filter-list ::= 'none' | filters
filters ::= ['+'] filter (';' filter)*
filter ::= expression | '@' filename
glob-pattern ::= characters (including '*' and '?' wildcards), matched case-insensitively
Для redact-key:
expression ::= glob-pattern
Для redact-argument:
expression ::= glob-pattern (' ' glob-pattern)*
Альтернативы
-
Использовать существующую команду jfr scrub, чтобы вручную удалять из файлов записей события с конфиденциальными данными.
Это однообразная работа, в которой легко ошибиться. Кроме того, при обычной работе данные событий записываются во временный каталог, прежде чем попасть в файл записи. Если JVM аварийно завершится, в этом каталоге могут остаться немаскированные данные событий. Если данные записи передаются потоком через FlightRecorderMXBean или RemoteRecordingStream, немаскированные данные событий могут покинуть хост.
-
Отключать отдельные события, чтобы конфиденциальные данные не записывались. Например,
$ java -XX:StartFlightRecording:'jdk.InitialEnvironmentVariable#enabled=false' ...отключило бы запись значений переменных окружения.
Такую настройку неудобно выполнять, и она может убрать значения неконфиденциальных переменных окружения, которые нужны для диагностики проблем.
Кроме того, человек, настраивающий параметры командной строки JVM, может не знать, что конфиденциальные данные могут попасть в запись, и поэтому не будет знать, что нужно отключить отдельные события. Фильтры маскирования по умолчанию убирают многие виды конфиденциальных данных без предварительной настройки.
-
Фильтровать данные с помощью регулярных выражений.
Фильтрацию на основе регулярных выражений можно было бы реализовать в JVM с помощью библиотеки регулярных выражений C++, но эта библиотека может выбрасывать исключения, а они запрещены в HotSpot.
Другой вариант — реализовать фильтрацию на основе регулярных выражений в коде на Java с помощью API
java.util.regex, но это увеличило бы время запуска. Кроме того, это затруднило бы распространение фильтров маскирования на новые события в ситуациях, когда вызов из JVM в код на Java невозможен.Независимо от способа реализации, опыт показывает, что выразительная мощь регулярных выражений нужна редко.
-
Отключить маскирование по умолчанию, чтобы поля с конфиденциальной информацией не заменялись на
[REDACTED], пока маскирование не включено в командной строке.Конфиденциальные данные, переданные из командной оболочки, редко нужны для диагностики проблем. Если они всё же нужны, заполнитель
[REDACTED]показывает, что действует маскирование; как его отключить, можно узнать из документации. Непреднамеренная утечка конфиденциальных данных вызывает большее беспокойство, поскольку ущерб от неё может быть необратимым.Кроме того, как отмечалось выше, человек, настраивающий параметры командной строки JVM, может не знать, что конфиденциальные данные могут попасть в запись. Если бы маскирование было отключено по умолчанию, конфиденциальные данные могли бы утечь, если кто-то другой позже подключится к приложению через JDK Mission Control или запустит запись с помощью инструмента
jcmd.
Риски и допущения
В режиме маскирования по умолчанию записи JFR могут отличаться от записей, созданных в предыдущих выпусках, поскольку в затронутых полях будет [REDACTED]. Чтобы вернуть прежнее поведение, укажите:
$ java -XX:FlightRecorderOptions:'redact-argument=none,redact-key=none' ...