From ff98d2bca1406a43ce87c96ce527f8738f7583e2 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Wed, 14 Oct 2026 11:17:27 +0530 Subject: [PATCH 1/3] Fixes open-metadata/openmetadata-collate#7380: Keep JAVA_TOOL_OPTIONS secrets out of the startup logs The JVM prints JAVA_TOOL_OPTIONS to stderr at startup ("Picked up JAVA_TOOL_OPTIONS: ..."), so a truststore password passed that way ended up in the server and migration logs. bin/openmetadata-server-start.sh, bootstrap/openmetadata-ops.sh and bin/openmetadata.sh now pass the options to java as arguments, ahead of their own so precedence is unchanged, and unset the variable. A value with quotes is left to the JVM, which strips quotes that shell word splitting would keep. The docker smoke test starts the image with JAVA_TOOL_OPTIONS set and checks that the value stays out of the log while the options still reach the JVM. --- bin/openmetadata-server-start.sh | 17 +++++++++++++++-- bin/openmetadata.sh | 13 ++++++++++++- bootstrap/openmetadata-ops.sh | 13 ++++++++++++- scripts/docker_smoke_test.sh | 23 +++++++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/bin/openmetadata-server-start.sh b/bin/openmetadata-server-start.sh index ac337c864341..c53e52f97868 100644 --- a/bin/openmetadata-server-start.sh +++ b/bin/openmetadata-server-start.sh @@ -76,6 +76,19 @@ else JAVA="$JAVA_HOME/bin/java" fi +# The JVM echoes JAVA_TOOL_OPTIONS to stderr at startup ("Picked up JAVA_TOOL_OPTIONS: ..."), which +# puts any secret set there, such as a truststore password, into the container log. Pass the options +# to java as arguments instead, ahead of ours so ours still take precedence. A value with quotes stays +# in the environment: the JVM strips the quotes, but word splitting would keep them. +JAVA_TOOL_ARGS="" +case "$JAVA_TOOL_OPTIONS" in + *\"* | *\'*) ;; + ?*) + JAVA_TOOL_ARGS="$JAVA_TOOL_OPTIONS" + unset JAVA_TOOL_OPTIONS + ;; +esac + # Set Debug options if enabled if [ "x$OPENMETADATA_DEBUG" != "x" ]; then @@ -126,7 +139,7 @@ if [ "x$DAEMON_MODE" = "xtrue" ]; then # emits what logback already writes to the rotated logs/openmetadata.log. Silence the console # so this file only collects JVM and pre-logback output. CONSOLE_LOG_LEVEL=TRACE restores it. export CONSOLE_LOG_LEVEL="${CONSOLE_LOG_LEVEL:-OFF}" - nohup $JAVA $OPENMETADATA_HEAP_OPTS $OPENMETADATA_JVM_PERFORMANCE_OPTS $OPENMETADATA_GC_LOG_OPTS -cp $CLASSPATH $OPENMETADATA_OPTS "$APP_CLASS" "server" "$@" > "$CONSOLE_OUTPUT_FILE" 2>&1 < /dev/null & + nohup $JAVA $JAVA_TOOL_ARGS $OPENMETADATA_HEAP_OPTS $OPENMETADATA_JVM_PERFORMANCE_OPTS $OPENMETADATA_GC_LOG_OPTS -cp $CLASSPATH $OPENMETADATA_OPTS "$APP_CLASS" "server" "$@" > "$CONSOLE_OUTPUT_FILE" 2>&1 < /dev/null & else - exec $JAVA $OPENMETADATA_HEAP_OPTS $OPENMETADATA_JVM_PERFORMANCE_OPTS $OPENMETADATA_GC_LOG_OPTS -cp $CLASSPATH $OPENMETADATA_OPTS "$APP_CLASS" "server" "$@" + exec $JAVA $JAVA_TOOL_ARGS $OPENMETADATA_HEAP_OPTS $OPENMETADATA_JVM_PERFORMANCE_OPTS $OPENMETADATA_GC_LOG_OPTS -cp $CLASSPATH $OPENMETADATA_OPTS "$APP_CLASS" "server" "$@" fi diff --git a/bin/openmetadata.sh b/bin/openmetadata.sh index 4553fb10a73b..e92ae35aa7a7 100644 --- a/bin/openmetadata.sh +++ b/bin/openmetadata.sh @@ -36,7 +36,7 @@ function catalogStart { # Export CONSOLE_LOG_LEVEL=TRACE before starting to get the old behaviour back. export CONSOLE_LOG_LEVEL="${CONSOLE_LOG_LEVEL:-OFF}" cd ${CATALOG_HOME} - nohup ${JAVA} ${CATALOG_HEAP_OPTS} ${CATALOG_JVM_PERF_OPTS} ${CATALOG_DEBUG_OPTS} ${CATALOG_GC_LOG_OPTS} ${CATALOG_JMX_OPTS} -cp ${CLASSPATH} "${APP_CLASS}" "server" "$@" 2>>"${ERR_FILE}" 1>>"${OUT_FILE}" & + nohup ${JAVA} ${JAVA_TOOL_ARGS} ${CATALOG_HEAP_OPTS} ${CATALOG_JVM_PERF_OPTS} ${CATALOG_DEBUG_OPTS} ${CATALOG_GC_LOG_OPTS} ${CATALOG_JMX_OPTS} -cp ${CLASSPATH} "${APP_CLASS}" "server" "$@" 2>>"${ERR_FILE}" 1>>"${OUT_FILE}" & cd - &> /dev/null echo $! > ${PID_FILE} echo $(catalogStatus) @@ -151,6 +151,17 @@ else JAVA="${JAVA_HOME}/bin/java" fi +# As in bin/openmetadata-server-start.sh: the JVM would echo JAVA_TOOL_OPTIONS, and any secret in +# it, to stderr, so pass the options as arguments instead (a value with quotes stays in the env). +JAVA_TOOL_ARGS="" +case "$JAVA_TOOL_OPTIONS" in + *\"* | *\'*) ;; + ?*) + JAVA_TOOL_ARGS="$JAVA_TOOL_OPTIONS" + unset JAVA_TOOL_OPTIONS + ;; +esac + if [[ "x${CATALOG_HEAP_OPTS}" = "x" ]]; then export CATALOG_HEAP_OPTS="-Xmx2G -Xms2G" fi diff --git a/bootstrap/openmetadata-ops.sh b/bootstrap/openmetadata-ops.sh index b2d3e2db1f32..98d7f841ff62 100755 --- a/bootstrap/openmetadata-ops.sh +++ b/bootstrap/openmetadata-ops.sh @@ -32,6 +32,17 @@ else JAVA="${JAVA_HOME}/bin/java" fi +# As in bin/openmetadata-server-start.sh: the JVM would echo JAVA_TOOL_OPTIONS, and any secret in +# it, to stderr, so pass the options as arguments instead (a value with quotes stays in the env). +JAVA_TOOL_ARGS="" +case "$JAVA_TOOL_OPTIONS" in + *\"* | *\'*) ;; + ?*) + JAVA_TOOL_ARGS="$JAVA_TOOL_OPTIONS" + unset JAVA_TOOL_OPTIONS + ;; +esac + OPENMETADATA_SETUP_MAIN_CLASS=org.openmetadata.service.util.OpenMetadataOperations LIBS_DIR="${BOOTSTRAP_DIR}"/../libs/ if [ ${debug} ] ; then @@ -53,6 +64,6 @@ else CLASSPATH=`mvn -pl openmetadata-service -q exec:exec -Dexec.executable=echo -Dexec.args="%classpath"` fi -${JAVA} -Dbootstrap.dir=$BOOTSTRAP_DIR -cp ${CLASSPATH} ${OPENMETADATA_SETUP_MAIN_CLASS} -c $CONFIG_FILE_PATH "$@" +${JAVA} ${JAVA_TOOL_ARGS} -Dbootstrap.dir=$BOOTSTRAP_DIR -cp ${CLASSPATH} ${OPENMETADATA_SETUP_MAIN_CLASS} -c $CONFIG_FILE_PATH "$@" exit $? diff --git a/scripts/docker_smoke_test.sh b/scripts/docker_smoke_test.sh index f3230406627d..abc1102a28c6 100755 --- a/scripts/docker_smoke_test.sh +++ b/scripts/docker_smoke_test.sh @@ -207,6 +207,29 @@ case "$pid1" in esac docker rm -f "$CONTAINER" >/dev/null 2>&1 || true +# The JVM echoes JAVA_TOOL_OPTIONS to stderr before any logging starts ("Picked up +# JAVA_TOOL_OPTIONS: ..."), so a truststore password set there landed in the pod +# log. bin/openmetadata-server-start.sh passes the options to java as arguments +# instead. -XX:+PrintCommandLineFlags shows they still reach the JVM: it +# prints the flags once the arguments are parsed, after the echo would have been. +docker run -d --name "$CONTAINER" \ + -e JAVA_TOOL_OPTIONS="-XX:+PrintCommandLineFlags -Dsmoke.secret=not-for-the-log" \ + "$IMAGE" >/dev/null +for _ in 1 2 3 4 5 6 7 8 9 10; do + case "$(docker logs "$CONTAINER" 2>&1 || true)" in *PrintCommandLineFlags*) break ;; esac + sleep 1 +done +sleep 1 +startup="$(docker logs "$CONTAINER" 2>&1 || true)" +if echo "$startup" | grep -q "not-for-the-log"; then + fail "JAVA_TOOL_OPTIONS reached the container log: $(echo "$startup" | grep -m1 "not-for-the-log")" +elif ! echo "$startup" | grep -q -- "-XX:+PrintCommandLineFlags"; then + fail "JAVA_TOOL_OPTIONS never reached the JVM: $(echo "$startup" | tail -3 | tr '\n' ' ')" +else + pass "JAVA_TOOL_OPTIONS reaches the JVM without being echoed to the log" +fi +docker rm -f "$CONTAINER" >/dev/null 2>&1 || true + echo if [ "$failures" -ne 0 ]; then echo "==> $failures assertion(s) failed for $IMAGE" From 56edde72d828d823b9f21914148982892d6f9c18 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Wed, 14 Oct 2026 20:27:55 +0530 Subject: [PATCH 2/3] Hand JAVA_TOOL_OPTIONS to java as an argfile, not as arguments Passing the options as plain arguments traded the startup log for ps: /proc//cmdline is readable by every local user, while /proc//environ is readable only by the process owner. The launchers now hand the options to the java launcher as an @argfile on fd 3, fed by a heredoc, so the JVM's command line only shows @/dev/fd/3 and no file is left behind. musl JDKs re-exec the launcher with the options expanded, so on Alpine-based images ps still shows them. A value containing '#' now also stays in the environment, because argfile syntax reads '#' as the start of a comment. openmetadata-ops.sh now builds its classpath before taking the variable, so the dev-mode mvn call still sees JAVA_TOOL_OPTIONS. The docker smoke test also checks that the value is not on PID 1's command line. --- bin/openmetadata-server-start.sh | 15 ++++++++++----- bin/openmetadata.sh | 11 +++++++---- bootstrap/openmetadata-ops.sh | 25 ++++++++++++++----------- scripts/docker_smoke_test.sh | 14 ++++++++++---- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/bin/openmetadata-server-start.sh b/bin/openmetadata-server-start.sh index c53e52f97868..41dcac8ecd21 100644 --- a/bin/openmetadata-server-start.sh +++ b/bin/openmetadata-server-start.sh @@ -77,14 +77,19 @@ else fi # The JVM echoes JAVA_TOOL_OPTIONS to stderr at startup ("Picked up JAVA_TOOL_OPTIONS: ..."), which -# puts any secret set there, such as a truststore password, into the container log. Pass the options -# to java as arguments instead, ahead of ours so ours still take precedence. A value with quotes stays -# in the environment: the JVM strips the quotes, but word splitting would keep them. +# puts any secret set there, such as a truststore password, into the container log. Plain arguments +# would show in ps instead, so hand the options to the java launcher as an @argfile read from fd 3: +# argv only shows the file name (except on musl, where the launcher re-execs with them expanded), +# and the heredoc leaves no file behind. It goes ahead of our options so ours still take precedence. +# A value with quotes or '#' stays in the environment, because argfiles read those differently. JAVA_TOOL_ARGS="" case "$JAVA_TOOL_OPTIONS" in - *\"* | *\'*) ;; + *\"* | *\'* | *#*) ;; ?*) - JAVA_TOOL_ARGS="$JAVA_TOOL_OPTIONS" + exec 3</dev/null 2>&1 || true # The JVM echoes JAVA_TOOL_OPTIONS to stderr before any logging starts ("Picked up # JAVA_TOOL_OPTIONS: ..."), so a truststore password set there landed in the pod -# log. bin/openmetadata-server-start.sh passes the options to java as arguments -# instead. -XX:+PrintCommandLineFlags shows they still reach the JVM: it -# prints the flags once the arguments are parsed, after the echo would have been. +# log. bin/openmetadata-server-start.sh hands the options to java in an +# @argfile instead. The value must stay out of the log and off the JVM's command +# line, which every local user can read. -XX:+PrintCommandLineFlags shows the +# options still reach the JVM: it prints the flags once the arguments are parsed. docker run -d --name "$CONTAINER" \ -e JAVA_TOOL_OPTIONS="-XX:+PrintCommandLineFlags -Dsmoke.secret=not-for-the-log" \ "$IMAGE" >/dev/null @@ -221,12 +222,17 @@ for _ in 1 2 3 4 5 6 7 8 9 10; do done sleep 1 startup="$(docker logs "$CONTAINER" 2>&1 || true)" +cmdline="$(docker exec "$CONTAINER" /bin/sh -c 'tr "\0" " " < /proc/1/cmdline' 2>/dev/null || true)" if echo "$startup" | grep -q "not-for-the-log"; then fail "JAVA_TOOL_OPTIONS reached the container log: $(echo "$startup" | grep -m1 "not-for-the-log")" +elif echo "$cmdline" | grep -q "not-for-the-log"; then + fail "JAVA_TOOL_OPTIONS is on the JVM's command line, where ps shows it: $cmdline" elif ! echo "$startup" | grep -q -- "-XX:+PrintCommandLineFlags"; then fail "JAVA_TOOL_OPTIONS never reached the JVM: $(echo "$startup" | tail -3 | tr '\n' ' ')" +elif [ -z "$cmdline" ]; then + fail "could not read /proc/1 to check the JVM's command line before the container exited" else - pass "JAVA_TOOL_OPTIONS reaches the JVM without being echoed to the log" + pass "JAVA_TOOL_OPTIONS reaches the JVM without showing in its log or its command line" fi docker rm -f "$CONTAINER" >/dev/null 2>&1 || true From 2533b46a99016e2754d60cf2c299d781c13634df Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 15 Oct 2026 14:48:43 +0530 Subject: [PATCH 3/3] Pass JAVA_TOOL_OPTIONS in a VM options file so '#' and quotes work The argfile could not carry values containing '#' or quotes, so those stayed in the environment and the JVM still printed them, with no warning. Generated passwords often contain '#'. The launchers now write the options to a -XX:VMOptionsFile instead, which the JVM parses exactly like JAVA_TOOL_OPTIONS, quotes and '#' included, without echoing it. The file is created in /dev/shm (tmpfs, writable even with a read-only root filesystem) or $TMPDIR, opened on fd 3 and unlinked at once, so nothing stays on disk and ps only shows -XX:VMOptionsFile=/dev/fd/3. The JVM reads the file itself after the musl launcher re-exec, so this also holds on Alpine-based images. If neither directory is writable, the launchers print a one-line warning without the value and leave the variable to the JVM. The docker smoke test now puts a '#' in its value and checks that no options file is left behind. --- bin/openmetadata-server-start.sh | 34 +++++++++++++++++++------------- bin/openmetadata.sh | 28 +++++++++++++++----------- bootstrap/openmetadata-ops.sh | 28 +++++++++++++++----------- scripts/docker_smoke_test.sh | 15 +++++++++----- 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/bin/openmetadata-server-start.sh b/bin/openmetadata-server-start.sh index 41dcac8ecd21..fe62dde1c313 100644 --- a/bin/openmetadata-server-start.sh +++ b/bin/openmetadata-server-start.sh @@ -77,22 +77,28 @@ else fi # The JVM echoes JAVA_TOOL_OPTIONS to stderr at startup ("Picked up JAVA_TOOL_OPTIONS: ..."), which -# puts any secret set there, such as a truststore password, into the container log. Plain arguments -# would show in ps instead, so hand the options to the java launcher as an @argfile read from fd 3: -# argv only shows the file name (except on musl, where the launcher re-execs with them expanded), -# and the heredoc leaves no file behind. It goes ahead of our options so ours still take precedence. -# A value with quotes or '#' stays in the environment, because argfiles read those differently. +# puts any secret set there, such as a truststore password, into the container log, and plain +# arguments would show in ps instead. So pass the options in a -XX:VMOptionsFile: the JVM parses it +# exactly like JAVA_TOOL_OPTIONS, quotes and '#' included, and does not echo it. The file is opened +# on fd 3 and unlinked at once, tmpfs first, so ps only ever shows /dev/fd/3, even after musl's +# launcher re-exec. It goes ahead of our options so ours still take precedence. JAVA_TOOL_ARGS="" -case "$JAVA_TOOL_OPTIONS" in - *\"* | *\'* | *#*) ;; - ?*) - exec 3</dev/null) && break + done + if [ -n "$java_tool_options_file" ] && + printf '%s\n' "$JAVA_TOOL_OPTIONS" > "$java_tool_options_file"; then + exec 3< "$java_tool_options_file" + JAVA_TOOL_ARGS="-XX:VMOptionsFile=/dev/fd/3" unset JAVA_TOOL_OPTIONS - ;; -esac + else + echo "Warning: could not write JAVA_TOOL_OPTIONS to /dev/shm or ${TMPDIR:-/tmp}," \ + "so the JVM will print it at startup" >&2 + fi + [ -z "$java_tool_options_file" ] || rm -f "$java_tool_options_file" +fi # Set Debug options if enabled if [ "x$OPENMETADATA_DEBUG" != "x" ]; then diff --git a/bin/openmetadata.sh b/bin/openmetadata.sh index a45a2816dde9..8449a26c4827 100644 --- a/bin/openmetadata.sh +++ b/bin/openmetadata.sh @@ -151,19 +151,25 @@ else JAVA="${JAVA_HOME}/bin/java" fi -# As in bin/openmetadata-server-start.sh: hand JAVA_TOOL_OPTIONS to java as an @argfile on fd 3, so -# neither the JVM's startup echo nor ps shows it (a value with quotes or '#' stays in the env). +# As in bin/openmetadata-server-start.sh: pass JAVA_TOOL_OPTIONS in a -XX:VMOptionsFile on fd 3, +# so neither the JVM's startup echo nor ps shows it. JAVA_TOOL_ARGS="" -case "$JAVA_TOOL_OPTIONS" in - *\"* | *\'* | *#*) ;; - ?*) - exec 3</dev/null) && break + done + if [ -n "$java_tool_options_file" ] && + printf '%s\n' "$JAVA_TOOL_OPTIONS" > "$java_tool_options_file"; then + exec 3< "$java_tool_options_file" + JAVA_TOOL_ARGS="-XX:VMOptionsFile=/dev/fd/3" unset JAVA_TOOL_OPTIONS - ;; -esac + else + echo "Warning: could not write JAVA_TOOL_OPTIONS to /dev/shm or ${TMPDIR:-/tmp}," \ + "so the JVM will print it at startup" >&2 + fi + [ -z "$java_tool_options_file" ] || rm -f "$java_tool_options_file" +fi if [[ "x${CATALOG_HEAP_OPTS}" = "x" ]]; then export CATALOG_HEAP_OPTS="-Xmx2G -Xms2G" diff --git a/bootstrap/openmetadata-ops.sh b/bootstrap/openmetadata-ops.sh index b19615120923..a159c6ddc3a7 100755 --- a/bootstrap/openmetadata-ops.sh +++ b/bootstrap/openmetadata-ops.sh @@ -53,19 +53,25 @@ else CLASSPATH=`mvn -pl openmetadata-service -q exec:exec -Dexec.executable=echo -Dexec.args="%classpath"` fi -# As in bin/openmetadata-server-start.sh: hand JAVA_TOOL_OPTIONS to java as an @argfile on fd 3, so -# neither the JVM's startup echo nor ps shows it (a value with quotes or '#' stays in the env). +# As in bin/openmetadata-server-start.sh: pass JAVA_TOOL_OPTIONS in a -XX:VMOptionsFile on fd 3, +# so neither the JVM's startup echo nor ps shows it. JAVA_TOOL_ARGS="" -case "$JAVA_TOOL_OPTIONS" in - *\"* | *\'* | *#*) ;; - ?*) - exec 3</dev/null) && break + done + if [ -n "$java_tool_options_file" ] && + printf '%s\n' "$JAVA_TOOL_OPTIONS" > "$java_tool_options_file"; then + exec 3< "$java_tool_options_file" + JAVA_TOOL_ARGS="-XX:VMOptionsFile=/dev/fd/3" unset JAVA_TOOL_OPTIONS - ;; -esac + else + echo "Warning: could not write JAVA_TOOL_OPTIONS to /dev/shm or ${TMPDIR:-/tmp}," \ + "so the JVM will print it at startup" >&2 + fi + [ -z "$java_tool_options_file" ] || rm -f "$java_tool_options_file" +fi ${JAVA} ${JAVA_TOOL_ARGS} -Dbootstrap.dir=$BOOTSTRAP_DIR -cp ${CLASSPATH} ${OPENMETADATA_SETUP_MAIN_CLASS} -c $CONFIG_FILE_PATH "$@" exit $? diff --git a/scripts/docker_smoke_test.sh b/scripts/docker_smoke_test.sh index 3b442e704501..038c6e3c2754 100755 --- a/scripts/docker_smoke_test.sh +++ b/scripts/docker_smoke_test.sh @@ -209,12 +209,13 @@ docker rm -f "$CONTAINER" >/dev/null 2>&1 || true # The JVM echoes JAVA_TOOL_OPTIONS to stderr before any logging starts ("Picked up # JAVA_TOOL_OPTIONS: ..."), so a truststore password set there landed in the pod -# log. bin/openmetadata-server-start.sh hands the options to java in an -# @argfile instead. The value must stay out of the log and off the JVM's command -# line, which every local user can read. -XX:+PrintCommandLineFlags shows the -# options still reach the JVM: it prints the flags once the arguments are parsed. +# log. bin/openmetadata-server-start.sh hands the options to the JVM in a +# -XX:VMOptionsFile instead. The value, '#' included, must stay out of the log and +# off the JVM's command line, which every local user can read, and the options +# file must not be left behind. -XX:+PrintCommandLineFlags shows the options still +# reach the JVM: it prints the flags once the arguments are parsed. docker run -d --name "$CONTAINER" \ - -e JAVA_TOOL_OPTIONS="-XX:+PrintCommandLineFlags -Dsmoke.secret=not-for-the-log" \ + -e JAVA_TOOL_OPTIONS="-XX:+PrintCommandLineFlags -Dsmoke.secret=not-for-the-log#1" \ "$IMAGE" >/dev/null for _ in 1 2 3 4 5 6 7 8 9 10; do case "$(docker logs "$CONTAINER" 2>&1 || true)" in *PrintCommandLineFlags*) break ;; esac @@ -223,6 +224,8 @@ done sleep 1 startup="$(docker logs "$CONTAINER" 2>&1 || true)" cmdline="$(docker exec "$CONTAINER" /bin/sh -c 'tr "\0" " " < /proc/1/cmdline' 2>/dev/null || true)" +leftover="$(docker exec "$CONTAINER" /bin/sh -c 'ls /dev/shm /tmp 2>/dev/null' | + grep -c '^jvm-options\.' || true)" if echo "$startup" | grep -q "not-for-the-log"; then fail "JAVA_TOOL_OPTIONS reached the container log: $(echo "$startup" | grep -m1 "not-for-the-log")" elif echo "$cmdline" | grep -q "not-for-the-log"; then @@ -231,6 +234,8 @@ elif ! echo "$startup" | grep -q -- "-XX:+PrintCommandLineFlags"; then fail "JAVA_TOOL_OPTIONS never reached the JVM: $(echo "$startup" | tail -3 | tr '\n' ' ')" elif [ -z "$cmdline" ]; then fail "could not read /proc/1 to check the JVM's command line before the container exited" +elif [ "${leftover:-0}" -ne 0 ]; then + fail "the JAVA_TOOL_OPTIONS file was left behind in /dev/shm or /tmp" else pass "JAVA_TOOL_OPTIONS reaches the JVM without showing in its log or its command line" fi