You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/10/18 18:23:28 UTC
(maven-mvnd) branch mvnd-1.x updated: Mask password-like values and user input in the daemon log
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a commit to branch mvnd-1.x
in repository https://gitbox.apache.org/repos/asf/maven-mvnd.git
The following commit(s) were added to refs/heads/mvnd-1.x by this push:
new 25443bc0 Mask password-like values and user input in the daemon log
25443bc0 is described below
commit 25443bc092bcdbf9e24509dd8aae087f00bffcd0
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Sun Oct 18 15:49:31 2026 +0200
Mask password-like values and user input in the daemon log
---
.../java/org/mvndaemon/mvnd/common/LogMasking.java | 84 ++++++++++++++++++++++
.../java/org/mvndaemon/mvnd/common/Message.java | 8 +--
.../org/mvndaemon/mvnd/common/LogMaskingTest.java | 77 ++++++++++++++++++++
.../java/org/mvndaemon/mvnd/cli/EnvHelper.java | 8 ++-
4 files changed, 170 insertions(+), 7 deletions(-)
diff --git a/common/src/main/java/org/mvndaemon/mvnd/common/LogMasking.java b/common/src/main/java/org/mvndaemon/mvnd/common/LogMasking.java
new file mode 100644
index 00000000..6216a8c1
--- /dev/null
+++ b/common/src/main/java/org/mvndaemon/mvnd/common/LogMasking.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.mvndaemon.mvnd.common;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Masks values that should not appear in the daemon log: property and environment values whose key names a
+ * password or similar, as they come in on the command line or in the client environment.
+ */
+public final class LogMasking {
+
+ private static final Pattern SENSITIVE_KEY =
+ Pattern.compile("(?i).*(password|passwd|passphrase|secret|token|credential).*");
+
+ private static final String MASK = "****";
+
+ private LogMasking() {}
+
+ public static boolean isSensitive(String key) {
+ return key != null && SENSITIVE_KEY.matcher(key).matches();
+ }
+
+ /**
+ * The value to log for {@code key}: the value itself, or a mask when the key looks sensitive.
+ */
+ public static String value(String key, String value) {
+ return value != null && isSensitive(key) ? MASK : value;
+ }
+
+ /**
+ * Command-line arguments with the values of sensitive {@code -Dkey=value} (also {@code -D key=value} and
+ * {@code --define key=value}) definitions masked.
+ */
+ public static List<String> args(List<String> args) {
+ if (args == null) {
+ return null;
+ }
+ List<String> result = new ArrayList<>(args.size());
+ boolean defineValue = false;
+ for (String arg : args) {
+ if (defineValue) {
+ result.add(definition(arg));
+ defineValue = false;
+ } else if (arg != null && arg.startsWith("-D") && arg.length() > 2) {
+ result.add("-D" + definition(arg.substring(2)));
+ } else {
+ result.add(arg);
+ defineValue = "-D".equals(arg) || "--define".equals(arg);
+ }
+ }
+ return result;
+ }
+
+ private static String definition(String keyValue) {
+ if (keyValue == null) {
+ return null;
+ }
+ int eq = keyValue.indexOf('=');
+ if (eq < 0) {
+ return keyValue;
+ }
+ String key = keyValue.substring(0, eq);
+ return isSensitive(key) ? key + '=' + MASK : keyValue;
+ }
+}
diff --git a/common/src/main/java/org/mvndaemon/mvnd/common/Message.java b/common/src/main/java/org/mvndaemon/mvnd/common/Message.java
index 8d826124..a679bf3d 100644
--- a/common/src/main/java/org/mvndaemon/mvnd/common/Message.java
+++ b/common/src/main/java/org/mvndaemon/mvnd/common/Message.java
@@ -384,7 +384,7 @@ public abstract class Message {
@Override
public String toString() {
return "BuildRequest{" + "args="
- + args + ", workingDir='"
+ + LogMasking.args(args) + ", workingDir='"
+ workingDir + '\'' + ", projectDir='"
+ projectDir + '\'' + '}';
}
@@ -1108,8 +1108,8 @@ public abstract class Message {
public String toString() {
return "PromptResponse{" + "projectId='"
+ projectId + '\'' + ", uid='"
- + uid + '\'' + ", message='"
- + message + '\'' + '}';
+ + uid + '\'' + ", message="
+ + (message == null ? "null" : "[" + message.length() + " chars]") + '}';
}
@Override
@@ -1362,7 +1362,7 @@ public abstract class Message {
@Override
public String toString() {
- return "InputResponse{" + "data='" + data + "\'" + '}';
+ return "InputResponse{" + (data == null ? "eof" : "data=[" + data.length() + " chars]") + '}';
}
@Override
diff --git a/common/src/test/java/org/mvndaemon/mvnd/common/LogMaskingTest.java b/common/src/test/java/org/mvndaemon/mvnd/common/LogMaskingTest.java
new file mode 100644
index 00000000..1e363718
--- /dev/null
+++ b/common/src/test/java/org/mvndaemon/mvnd/common/LogMaskingTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.mvndaemon.mvnd.common;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class LogMaskingTest {
+
+ @Test
+ void masksSensitiveDefinitions() {
+ assertEquals(
+ Arrays.asList(
+ "verify",
+ "-Dgpg.passphrase=****",
+ "-DskipTests=true",
+ "-D",
+ "repo.password=****",
+ "--define",
+ "api.TOKEN=****",
+ "-Dflag"),
+ LogMasking.args(Arrays.asList(
+ "verify",
+ "-Dgpg.passphrase=hunter2",
+ "-DskipTests=true",
+ "-D",
+ "repo.password=hunter2",
+ "--define",
+ "api.TOKEN=abc",
+ "-Dflag")));
+ }
+
+ @Test
+ void masksSensitiveEnvironmentValues() {
+ assertEquals("****", LogMasking.value("NPM_TOKEN", "abc"));
+ assertEquals("/usr/bin", LogMasking.value("PATH", "/usr/bin"));
+ assertEquals(null, LogMasking.value("DB_PASSWORD", null));
+ }
+
+ @Test
+ void messagesDoNotPrintUserInput() {
+ Message.Prompt prompt = new Message.Prompt("p", "u1", "Password:", true);
+ String response = prompt.response("hunter2").toString();
+ assertFalse(response.contains("hunter2"), response);
+ assertTrue(response.contains("[7 chars]"), response);
+
+ String input = Message.inputResponse("hunter2").toString();
+ assertFalse(input.contains("hunter2"), input);
+
+ String request = new Message.BuildRequest(
+ Arrays.asList("-Dgpg.passphrase=hunter2", "verify"), "/w", "/w", Collections.emptyMap())
+ .toString();
+ assertFalse(request.contains("hunter2"), request);
+ }
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java b/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java
index cfb83d81..e0e6aa24 100644
--- a/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java
@@ -35,6 +35,7 @@ import java.util.stream.Stream;
import org.jline.utils.ExecHelper;
import org.mvndaemon.mvnd.common.JavaVersion;
+import org.mvndaemon.mvnd.common.LogMasking;
import org.mvndaemon.mvnd.common.Os;
import org.mvndaemon.mvnd.nativ.CLibrary;
import org.slf4j.Logger;
@@ -65,7 +66,8 @@ public class EnvHelper {
String vr = requested.get(key);
int rc = CLibrary.setenv(key, vr);
if (Os.current() == Os.WINDOWS ^ rc != 0) {
- log.accept(String.format("Error setting environment value %s = %s", key, vr));
+ log.accept(
+ String.format("Error setting environment value %s = %s", key, LogMasking.value(key, vr)));
}
}
setEnv(requested);
@@ -83,8 +85,8 @@ public class EnvHelper {
if (!diffs.isEmpty()) {
log.accept("A few environment mismatches have been detected between the client and the daemon.");
diffs.forEach(key -> {
- String vr = requested.get(key);
- String va = nactual.get(key);
+ String vr = LogMasking.value(key, requested.get(key));
+ String va = LogMasking.value(key, nactual.get(key));
log.accept(String.format(
" %s -> %s instead of %s",
key, va != null ? "'" + va + "'" : "<null>", vr != null ? "'" + vr + "'" : "<null>"));