Redact option headers from BEP command lines.

Ensures that options like --bes_header, --remote_header, and
--remote_{cache,exec,downloader}_header
are redacted from the unstructured command line reported
to the Build Event Protocol (BEP) for metadata handling.

Contributes to https://github.com/bazelbuild/bazel/discussions/20126.

PiperOrigin-RevId: 954562702
Change-Id: Iac335b38e7a75f63013ca743f8f59c42c71edf8d
diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java b/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java
index 7343ffa..2ed3605 100644
--- a/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java
@@ -72,6 +72,7 @@
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
       effectTags = {OptionEffectTag.UNKNOWN},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           "Specifies the file to get authentication credentials from. See "
               + "https://cloud.google.com/docs/authentication for details.")
@@ -107,6 +108,7 @@
       converter = EmptyToNullStringConverter.class,
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
       effectTags = {OptionEffectTag.UNKNOWN},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           "Specify the TLS client key to use; you also need to provide a client certificate to "
               + "enable client authentication. An empty value resets the flag to its default.")
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceOptions.java b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceOptions.java
index e4ac866..fc6ec33 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceOptions.java
@@ -20,6 +20,7 @@
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionMetadataTag;
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsClass;
 import java.time.Duration;
@@ -68,6 +69,7 @@
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.LOGGING,
       effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           """
           Specify a header in `NAME=VALUE` form that will be included in BES requests.
diff --git a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
index dd0eb6d..02af142 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
@@ -200,6 +200,7 @@
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.REMOTE,
       effectTags = {OptionEffectTag.UNKNOWN},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           "Specify a header that will be included in requests: --remote_header=Name=Value. "
               + "Multiple headers can be passed by specifying the flag multiple times. Multiple "
@@ -215,6 +216,7 @@
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.REMOTE,
       effectTags = {OptionEffectTag.UNKNOWN},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           "Specify a header that will be included in cache requests: "
               + "--remote_cache_header=Name=Value. "
@@ -231,6 +233,7 @@
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.REMOTE,
       effectTags = {OptionEffectTag.UNKNOWN},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           "Specify a header that will be included in execution requests: "
               + "--remote_exec_header=Name=Value. "
@@ -247,6 +250,7 @@
       defaultValue = "null",
       documentationCategory = OptionDocumentationCategory.REMOTE,
       effectTags = {OptionEffectTag.UNKNOWN},
+      metadataTags = {OptionMetadataTag.FULLY_REDACTED_IN_LOGS},
       help =
           "Specify a header that will be included in remote downloader requests: "
               + "--remote_downloader_header=Name=Value. "
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BUILD b/src/main/java/com/google/devtools/build/lib/runtime/BUILD
index 1744d44..cc651e3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BUILD
@@ -26,6 +26,7 @@
 java_library(
     name = "safe_request_logging",
     srcs = ["SafeRequestLogging.java"],
+    deps = ["//third_party:guava"],
 )
 
 java_library(
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index d0156aa..b2d06f6 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -786,7 +786,8 @@
         unstructuredServerCommandLineEvent =
             OriginalUnstructuredCommandLineEvent.REDACTED_UNSTRUCTURED_COMMAND_LINE_EVENT;
       } else {
-        unstructuredServerCommandLineEvent = new OriginalUnstructuredCommandLineEvent(args);
+        unstructuredServerCommandLineEvent =
+            new OriginalUnstructuredCommandLineEvent(SafeRequestLogging.redactArguments(args));
       }
       env.getEventBus().post(unstructuredServerCommandLineEvent);
       env.getEventBus().post(originalCommandLineEvent);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandLineEvent.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandLineEvent.java
index d692a21..92234a9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandLineEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandLineEvent.java
@@ -136,12 +136,25 @@
         List<ParsedOptionDescription> parsedOptionDescriptions) {
       List<Option> options = new ArrayList<>();
       for (ParsedOptionDescription parsedOption : parsedOptionDescriptions) {
-        options.add(
-            createOption(
-                parsedOption.getOptionDefinition(),
-                parsedOption.getSource(),
-                parsedOption.getCommandLineForm(),
-                parsedOption.getUnconvertedValue()));
+        if (parsedOption.isHidden()) {
+          continue;
+        }
+        if (parsedOption.isFullyRedactedInLogs()) {
+          String optionName = parsedOption.getOptionDefinition().getOptionName();
+          options.add(
+              createOption(
+                  parsedOption.getOptionDefinition(),
+                  parsedOption.getSource(),
+                  "--" + optionName + "=<REDACTED>",
+                  "<REDACTED>"));
+        } else {
+          options.add(
+              createOption(
+                  parsedOption.getOptionDefinition(),
+                  parsedOption.getSource(),
+                  parsedOption.getCommandLineForm(),
+                  parsedOption.getUnconvertedValue()));
+        }
       }
       return options;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/SafeRequestLogging.java b/src/main/java/com/google/devtools/build/lib/runtime/SafeRequestLogging.java
index 8819038..ad596ea 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/SafeRequestLogging.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/SafeRequestLogging.java
@@ -14,6 +14,10 @@
 
 package com.google.devtools.build.lib.runtime;
 
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -24,6 +28,46 @@
       Pattern.compile(
           "--client_env=([^=]*(?:auth|pass|cookie|token|api_key)[^=]*)=", Pattern.CASE_INSENSITIVE);
 
+  private static final ImmutableSet<String> CREDENTIAL_OPTION_NAMES =
+      ImmutableSet.of(
+          "--bes_header",
+          "--remote_header",
+          "--remote_cache_header",
+          "--remote_exec_header",
+          "--remote_downloader_header",
+          "--tls_client_key",
+          "--google_credentials");
+
+  private static final Pattern CREDENTIAL_OPTIONS =
+      Pattern.compile(
+          "--("
+              + CREDENTIAL_OPTION_NAMES.stream().map(s -> s.substring(2)).collect(joining("|"))
+              + ")=",
+          Pattern.CASE_INSENSITIVE);
+
+  public static ImmutableList<String> redactArguments(List<String> args) {
+    ImmutableList.Builder<String> redactedArgs = ImmutableList.builder();
+    Matcher credMatcher = CREDENTIAL_OPTIONS.matcher("");
+    boolean redactNext = false;
+    for (String arg : args) {
+      if (redactNext) {
+        redactedArgs.add("<REDACTED>");
+        redactNext = false;
+        continue;
+      }
+      credMatcher.reset(arg);
+      if (credMatcher.lookingAt()) {
+        redactedArgs.add(credMatcher.group() + "<REDACTED>");
+      } else if (CREDENTIAL_OPTION_NAMES.contains(arg)) {
+        redactedArgs.add(arg);
+        redactNext = true;
+      } else {
+        redactedArgs.add(arg);
+      }
+    }
+    return redactedArgs.build();
+  }
+
   private SafeRequestLogging() {}
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java b/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
index e17fabb..100ae96 100644
--- a/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
@@ -42,7 +42,14 @@
       if (result.length() != 0) {
         result.append(' ');
       }
-      result.append(option.getCanonicalFormWithValueEscaper(ShellEscaper::escapeString));
+      if (option.isFullyRedactedInLogs()) {
+        result
+            .append("--")
+            .append(option.getOptionDefinition().getOptionName())
+            .append("=<REDACTED>");
+      } else {
+        result.append(option.getCanonicalFormWithValueEscaper(ShellEscaper::escapeString));
+      }
     }
     return result.toString();
   }
@@ -65,7 +72,11 @@
       if (option.isHidden()) {
         continue;
       }
-      builder.add(option.getCanonicalForm());
+      if (option.isFullyRedactedInLogs()) {
+        builder.add("--" + option.getOptionDefinition().getOptionName() + "=<REDACTED>");
+      } else {
+        builder.add(option.getCanonicalForm());
+      }
     }
     return builder.build();
   }
diff --git a/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java b/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
index 626f71c..d5f1dea 100644
--- a/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
+++ b/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
@@ -189,7 +189,10 @@
             "This option isn't even a option, and should not be logged.")
         .put(
             OptionMetadataTag.NON_CONFIGURABLE,
-            "This option cannot be changed in a transition or be used in a select() statement.");
+            "This option cannot be changed in a transition or be used in a select() statement.")
+        .put(
+            OptionMetadataTag.FULLY_REDACTED_IN_LOGS,
+            "This option may contain sensitive credentials and should be redacted in logs.");
     return effectTagDescriptionBuilder.build();
   }
 }
diff --git a/src/main/java/com/google/devtools/common/options/OptionMetadataTag.java b/src/main/java/com/google/devtools/common/options/OptionMetadataTag.java
index 45f7dac..8f10395 100644
--- a/src/main/java/com/google/devtools/common/options/OptionMetadataTag.java
+++ b/src/main/java/com/google/devtools/common/options/OptionMetadataTag.java
@@ -68,7 +68,13 @@
    * Options which are NON_CONFIGURABLE cannot be changed in (non-exec) Starlark transitions and
    * cannot be used in select().
    */
-  NON_CONFIGURABLE(8);
+  NON_CONFIGURABLE(8),
+
+  /**
+   * Options which are FULLY_REDACTED_IN_LOGS contain sensitive credentials and should be redacted
+   * in logs.
+   */
+  FULLY_REDACTED_IN_LOGS(9);
 
   private final int value;
 
diff --git a/src/main/java/com/google/devtools/common/options/ParsedOptionDescription.java b/src/main/java/com/google/devtools/common/options/ParsedOptionDescription.java
index 5af7ac4..765f698 100644
--- a/src/main/java/com/google/devtools/common/options/ParsedOptionDescription.java
+++ b/src/main/java/com/google/devtools/common/options/ParsedOptionDescription.java
@@ -162,6 +162,10 @@
     return tags.contains(OptionMetadataTag.HIDDEN) || tags.contains(OptionMetadataTag.INTERNAL);
   }
 
+  public boolean isFullyRedactedInLogs() {
+    return metadataTags().contains(OptionMetadataTag.FULLY_REDACTED_IN_LOGS);
+  }
+
   @Nullable
   public String getUnconvertedValue() {
     return unconvertedValue;
diff --git a/src/main/protobuf/option_filters.proto b/src/main/protobuf/option_filters.proto
index bfbe840..ba00dfa 100644
--- a/src/main/protobuf/option_filters.proto
+++ b/src/main/protobuf/option_filters.proto
@@ -60,4 +60,5 @@
   reserved "IMMUTABLE";
   reserved 7;
   NON_CONFIGURABLE = 8;
+  FULLY_REDACTED_IN_LOGS = 9;
 }
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BUILD b/src/test/java/com/google/devtools/build/lib/runtime/BUILD
index cf9a211..da89842 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BUILD
@@ -89,6 +89,7 @@
         "//src/main/java/com/google/devtools/build/lib/query2/engine",
         "//src/main/java/com/google/devtools/build/lib/query2/query/output",
         "//src/main/java/com/google/devtools/build/lib/remote:store",
+        "//src/main/java/com/google/devtools/build/lib/remote/options",
         "//src/main/java/com/google/devtools/build/lib/runtime:after_command_event",
         "//src/main/java/com/google/devtools/build/lib/runtime:blaze_command_cluster",
         "//src/main/java/com/google/devtools/build/lib/runtime:blaze_command_result",
@@ -114,6 +115,7 @@
         "//src/main/java/com/google/devtools/build/lib/runtime:location_printer",
         "//src/main/java/com/google/devtools/build/lib/runtime:memory_pressure",
         "//src/main/java/com/google/devtools/build/lib/runtime:memory_pressure_listener",
+        "//src/main/java/com/google/devtools/build/lib/runtime:original_unstructured_command_line_event",
         "//src/main/java/com/google/devtools/build/lib/runtime:process_wrapper",
         "//src/main/java/com/google/devtools/build/lib/runtime:quiescing_executors_impl",
         "//src/main/java/com/google/devtools/build/lib/runtime:rc_chunk_of_args",
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BepCommandLineLeakTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BepCommandLineLeakTest.java
new file mode 100644
index 0000000..7a85691
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BepCommandLineLeakTest.java
@@ -0,0 +1,114 @@
+// Copyright 2025 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.devtools.build.lib.buildeventservice.BuildEventServiceOptions;
+import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent;
+import com.google.devtools.build.lib.remote.options.RemoteOptions;
+import com.google.devtools.build.lib.runtime.CommandLineEvent.CanonicalCommandLineEvent;
+import com.google.devtools.build.lib.runtime.CommandLineEvent.OriginalCommandLineEvent;
+import com.google.devtools.build.lib.runtime.proto.CommandLineOuterClass.CommandLine;
+import com.google.devtools.common.options.OptionPriority.PriorityCategory;
+import com.google.devtools.common.options.OptionsParser;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test that options with FULLY_REDACTED_IN_LOGS are redacted in BEP command line events. */
+@RunWith(JUnit4.class)
+public final class BepCommandLineLeakTest {
+
+  private static final String BES_BEARER = "ya29.BES_BEARER_TOKEN_xxxxxxxxxxxxxxxx";
+  private static final String BES_HEADER_ARG = "--bes_header=Authorization=Bearer " + BES_BEARER;
+
+  private static final String REMOTE_BEARER = "REMOTE_EXEC_TOKEN_zzzzzzzzzzzzzzzz";
+  private static final String REMOTE_HEADER_ARG =
+      "--remote_header=Authorization=Bearer " + REMOTE_BEARER;
+
+  private static OptionsParser newCommandParser() throws Exception {
+    OptionsParser parser =
+        OptionsParser.builder()
+            .optionsClasses(BuildEventServiceOptions.class, RemoteOptions.class)
+            .build();
+    parser.parse(
+        PriorityCategory.COMMAND_LINE,
+        /* source= */ "command line options",
+        ImmutableList.of(BES_HEADER_ARG, REMOTE_HEADER_ARG));
+    return parser;
+  }
+
+  @Test
+  public void structuredCommandLine_redactsHeaders() throws Exception {
+    OptionsParser cmd = newCommandParser();
+    OptionsParser startup =
+        OptionsParser.builder().optionsClasses(BlazeServerStartupOptions.class).build();
+
+    CommandLine original =
+        new OriginalCommandLineEvent(
+                "testblaze",
+                startup,
+                "build",
+                ImmutableList.of("//some:target"),
+                false,
+                cmd.asListOfExplicitOptions(),
+                ImmutableSortedMap.of(),
+                ImmutableSet.of(),
+                Optional.of(ImmutableList.of()))
+            .asStreamProto(null)
+            .getStructuredCommandLine();
+
+    CommandLine canonical =
+        new CanonicalCommandLineEvent(
+                "testblaze",
+                startup,
+                "build",
+                ImmutableList.of("//some:target"),
+                false,
+                ImmutableSortedMap.of(),
+                ImmutableSortedMap.of(),
+                ImmutableSet.of(),
+                cmd.asListOfCanonicalOptions(),
+                false)
+            .asStreamProto(null)
+            .getStructuredCommandLine();
+
+    for (CommandLine line : ImmutableList.of(original, canonical)) {
+      assertThat(line.toString()).doesNotContain(BES_BEARER);
+      assertThat(line.toString()).doesNotContain(REMOTE_BEARER);
+      assertThat(line.toString()).contains("--bes_header=<REDACTED>");
+      assertThat(line.toString()).contains("--remote_header=<REDACTED>");
+    }
+  }
+
+  @Test
+  public void unstructuredCommandLine_redactsHeaders() {
+    ImmutableList<String> rawArgs =
+        ImmutableList.of("build", BES_HEADER_ARG, REMOTE_HEADER_ARG, "//some:target");
+    BuildEvent proto =
+        new OriginalUnstructuredCommandLineEvent(SafeRequestLogging.redactArguments(rawArgs))
+            .asStreamProto(null);
+
+    String unstructured = proto.getUnstructuredCommandLine().toString();
+    assertThat(unstructured).doesNotContain(BES_BEARER);
+    assertThat(unstructured).doesNotContain(REMOTE_BEARER);
+    assertThat(unstructured).contains("--bes_header=<REDACTED>");
+    assertThat(unstructured).contains("--remote_header=<REDACTED>");
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/SafeRequestLoggingTest.java b/src/test/java/com/google/devtools/build/lib/runtime/SafeRequestLoggingTest.java
index 8417b92..6dfd321 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/SafeRequestLoggingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/SafeRequestLoggingTest.java
@@ -17,7 +17,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -97,17 +96,39 @@
 
   @Test
   public void testGetRequestLogForStandardCommandLine() {
-    List<String> complexCommandLine = ImmutableList.of(
-        "blaze",
-        "build",
-        "--client_env=FOO=BAR",
-        "--client_env=FOOPASS=mypassword",
-        "--package_path=./MY_PASSWORD/foo",
-        "--client_env=SOMEAuThCode=something");
+    ImmutableList<String> complexCommandLine =
+        ImmutableList.of(
+            "blaze",
+            "build",
+            "--client_env=FOO=BAR",
+            "--client_env=FOOPASS=mypassword",
+            "--package_path=./MY_PASSWORD/foo",
+            "--client_env=SOMEAuThCode=something");
     assertThat(SafeRequestLogging.getRequestLogString(complexCommandLine))
         .isEqualTo(
             "[blaze, build, --client_env=FOO=BAR, --client_env=FOOPASS=__private_value_removed__, "
                 + "--package_path=./MY_PASSWORD/foo, "
                 + "--client_env=SOMEAuThCode=__private_value_removed__]");
   }
+
+  @Test
+  public void testRedactArgumentsRedactsCredentials() {
+    ImmutableList<String> args =
+        ImmutableList.of(
+            "blaze",
+            "build",
+            "--bes_header=Auth=123",
+            "--remote_header",
+            "Auth=456",
+            "--some_other_flag");
+    assertThat(SafeRequestLogging.redactArguments(args))
+        .containsExactly(
+            "blaze",
+            "build",
+            "--bes_header=<REDACTED>",
+            "--remote_header",
+            "<REDACTED>",
+            "--some_other_flag")
+        .inOrder();
+  }
 }
diff --git a/src/test/shell/integration/build_event_stream_test.sh b/src/test/shell/integration/build_event_stream_test.sh
index b496f56..3093f17 100755
--- a/src/test/shell/integration/build_event_stream_test.sh
+++ b/src/test/shell/integration/build_event_stream_test.sh
@@ -1355,11 +1355,11 @@
   expect_log 'command_line_label: "canonical"'
   expect_log 'command_line_label: "tool"'
 
-  # Expect the actual tool command line flag to appear twice, because of the two
-  # bazel command lines that are reported
-  expect_log_n 'combined_form: "--experimental_tool_command_line=' 2
-  expect_log_n 'option_name: "experimental_tool_command_line"' 2
-  expect_log_n 'option_value: "foo bar"' 2
+  # Hidden options should not appear in the option lists of the original or
+  # canonical command line events
+  expect_not_log 'combined_form: "--experimental_tool_command_line='
+  expect_not_log 'option_name: "experimental_tool_command_line"'
+  expect_not_log 'option_value: "foo bar"'
 
   # Check the contents of the tool command line
   expect_log_once 'chunk: "foo bar"'