From 77c9cc6a6dab8910e7a97ebd8e3b8e836a26dc73 Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Wed, 24 Jun 2026 23:10:45 +0300 Subject: [PATCH 1/2] Mask secret query parameter when it is the last parameter handleDataWithSecret masks the secret value before logging, but the regex &secret=\w+& requires a trailing &. When secret is the last query parameter (e.g. appid=wx123&secret=abc123) there is no trailing &, so it is not masked and leaks in logs. Drop the trailing-& requirement so the value is masked regardless of position; the middle-parameter case is unchanged. Adds a regression test. --- .../java/me/chanjar/weixin/common/util/DataUtils.java | 2 +- .../me/chanjar/weixin/common/util/DataUtilsTest.java | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java index b8fb42e0e9..c6f441b525 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java @@ -18,7 +18,7 @@ public class DataUtils { public static E handleDataWithSecret(E data) { E dataForLog = data; if(data instanceof String && StringUtils.contains((String)data, "&secret=")){ - dataForLog = (E) RegExUtils.replaceAll((String)data,"&secret=\\w+&","&secret=******&"); + dataForLog = (E) RegExUtils.replaceAll((String)data,"&secret=\\w+","&secret=******"); } return dataForLog; } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java index f5732d9a0b..1794c3d4dd 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java @@ -19,4 +19,13 @@ public void testHandleDataWithSecret() { final String s = DataUtils.handleDataWithSecret(data); assertTrue(s.contains("&secret=******&")); } + + @Test + public void testHandleDataWithSecretAtEnd() { + // Secret is the last parameter in the query string, so there is no trailing & + String data = "appid=wx123&secret=abc123"; + final String s = DataUtils.handleDataWithSecret(data); + assertFalse(s.contains("abc123"), "Secret at the end of the string should be masked"); + assertTrue(s.contains("secret=******"), "Secret should be replaced with asterisks"); + } } From 331949d59e29a7d2a7ed72009d8ab423740e5c41 Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Fri, 26 Jun 2026 23:36:35 +0300 Subject: [PATCH 2/2] Mask the secret query parameter regardless of position and value handleDataWithSecret only masked secret when it followed an & and stopped at the first non-word character, so a leading secret= (first or only parameter) was not masked and a value containing URL-encoded or non-word characters (e.g. abc%2Fdef) leaked its remainder. Match the secret parameter at a query boundary and mask the whole value up to the next & or end of string. --- .../chanjar/weixin/common/util/DataUtils.java | 4 ++-- .../weixin/common/util/DataUtilsTest.java | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java index c6f441b525..095363cf8d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java @@ -17,8 +17,8 @@ public class DataUtils { */ public static E handleDataWithSecret(E data) { E dataForLog = data; - if(data instanceof String && StringUtils.contains((String)data, "&secret=")){ - dataForLog = (E) RegExUtils.replaceAll((String)data,"&secret=\\w+","&secret=******"); + if (data instanceof String && StringUtils.contains((String) data, "secret=")) { + dataForLog = (E) RegExUtils.replaceAll((String) data, "(^|[?&])secret=[^&]*", "$1secret=******"); } return dataForLog; } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java index 1794c3d4dd..66a336c268 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java @@ -28,4 +28,23 @@ public void testHandleDataWithSecretAtEnd() { assertFalse(s.contains("abc123"), "Secret at the end of the string should be masked"); assertTrue(s.contains("secret=******"), "Secret should be replaced with asterisks"); } + + @Test + public void testHandleDataWithSecretAsFirstParam() { + // Secret is the first/only parameter, so there is no leading & + String data = "secret=abc123&appid=wx123"; + final String s = DataUtils.handleDataWithSecret(data); + assertFalse(s.contains("abc123"), "Secret as the first parameter should be masked"); + assertTrue(s.contains("secret=******"), "Secret should be replaced with asterisks"); + } + + @Test + public void testHandleDataWithSecretEncodedValue() { + // The secret value contains URL-encoded and non-word characters; the whole value must be masked + String data = "appid=wx123&secret=abc%2Fdef-.+ghi&grant_type=client_credential"; + final String s = DataUtils.handleDataWithSecret(data); + assertFalse(s.contains("def"), "The full secret value must be masked, including after non-word characters"); + assertFalse(s.contains("%2F"), "Encoded characters in the secret must be masked too"); + assertTrue(s.contains("&secret=******&"), "Secret should be replaced with asterisks"); + } }