openjdk.ruOpenJDK на русском

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' ...