From 5be0f0b1377ecb4ce602c2c0d34c4adee9271196 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Thu, 4 Jun 2026 19:26:23 +0200 Subject: [PATCH 1/3] Fix password leak from JDBC URL userinfo into db.user span tag --- .../jdbc/JDBCConnectionUrlParser.java | 8 +++-- ...BCConnectionUrlParserPasswordLeakTest.java | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java index 2eaccd81400..80a41bc17bd 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java @@ -36,9 +36,11 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { populateStandardProperties(builder, splitQuery(uri.getQuery(), '&')); - final String user = uri.getUserInfo(); - if (user != null) { - builder.user(user); + final String userInfo = uri.getUserInfo(); + if (userInfo != null) { + // getUserInfo() returns "user:password" — strip the password + final int colonLoc = userInfo.indexOf(':'); + builder.user(colonLoc >= 0 ? userInfo.substring(0, colonLoc) : userInfo); } String path = uri.getPath(); diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java new file mode 100644 index 00000000000..4a978706cbd --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java @@ -0,0 +1,29 @@ +package datadog.trace.bootstrap.instrumentation.jdbc; + +import static datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser.extractDBInfo; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.tabletest.junit.TableTest; + +/** + * Tests that passwords embedded in JDBC URL userinfo (user:password@host) do not leak into the + * {@code db.user} span tag. + * + *

{@link java.net.URI#getUserInfo()} returns the full userinfo component, including the + * password. Without sanitization, {@code builder.user("myuser:secret")} stores the password in + * {@code DBInfo.user}, which is then set as the {@code db.user} span tag. + */ +class JDBCConnectionUrlParserPasswordLeakTest { + + @TableTest({ + "scenario | url | type | host | user ", + "PostgreSQL userinfo with password | jdbc:postgresql://myuser:secret123@pg.host/mydb | postgresql | pg.host | myuser", + "PostgreSQL userinfo without password | jdbc:postgresql://myuser@pg.host/mydb | postgresql | pg.host | myuser" + }) + void passwordShouldNotLeakIntoUserTag(String url, String type, String host, String user) { + DBInfo info = extractDBInfo(url, null); + assertEquals(type, info.getType()); + assertEquals(host, info.getHost()); + assertEquals(user, info.getUser()); + } +} From 13f882e3f624de473d4ff1f7bdf629493b6a1192 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Thu, 4 Jun 2026 20:00:08 +0200 Subject: [PATCH 2/3] Use getRawUserInfo() to avoid splitting on percent-encoded colon in username --- .../jdbc/JDBCConnectionUrlParser.java | 16 +++++++++++----- .../JDBCConnectionUrlParserPasswordLeakTest.java | 7 ++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java index 80a41bc17bd..479cf4393e4 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java @@ -36,11 +36,17 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { populateStandardProperties(builder, splitQuery(uri.getQuery(), '&')); - final String userInfo = uri.getUserInfo(); - if (userInfo != null) { - // getUserInfo() returns "user:password" — strip the password - final int colonLoc = userInfo.indexOf(':'); - builder.user(colonLoc >= 0 ? userInfo.substring(0, colonLoc) : userInfo); + final String rawUserInfo = uri.getRawUserInfo(); + if (rawUserInfo != null) { + // getRawUserInfo() avoids decoding %3A in the username before splitting on the unescaped + // ':' + final int colonLoc = rawUserInfo.indexOf(':'); + final String rawUser = colonLoc >= 0 ? rawUserInfo.substring(0, colonLoc) : rawUserInfo; + try { + builder.user(URLDecoder.decode(rawUser, "UTF-8")); + } catch (final UnsupportedEncodingException e) { + builder.user(rawUser); + } } String path = uri.getPath(); diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java index 4a978706cbd..668d962a9fc 100644 --- a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java @@ -16,9 +16,10 @@ class JDBCConnectionUrlParserPasswordLeakTest { @TableTest({ - "scenario | url | type | host | user ", - "PostgreSQL userinfo with password | jdbc:postgresql://myuser:secret123@pg.host/mydb | postgresql | pg.host | myuser", - "PostgreSQL userinfo without password | jdbc:postgresql://myuser@pg.host/mydb | postgresql | pg.host | myuser" + "scenario | url | type | host | user ", + "PostgreSQL userinfo with password | jdbc:postgresql://myuser:secret123@pg.host/mydb | postgresql | pg.host | myuser ", + "PostgreSQL userinfo without password | jdbc:postgresql://myuser@pg.host/mydb | postgresql | pg.host | myuser ", + "PostgreSQL userinfo with percent-encoded colon | jdbc:postgresql://tenant%3Aalice@pg.host/mydb | postgresql | pg.host | tenant:alice" }) void passwordShouldNotLeakIntoUserTag(String url, String type, String host, String user) { DBInfo info = extractDBInfo(url, null); From 00fda7ba6dc6dcdefe86e4292b21ba800cabda48 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Thu, 4 Jun 2026 22:17:48 +0200 Subject: [PATCH 3/3] Address review comments: shorten comment, add SAP test case, remove Javadoc --- .../jdbc/JDBCConnectionUrlParser.java | 3 +-- ...JDBCConnectionUrlParserPasswordLeakTest.java | 17 +++++------------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java index 479cf4393e4..0f08e5019b7 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java @@ -38,8 +38,7 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { final String rawUserInfo = uri.getRawUserInfo(); if (rawUserInfo != null) { - // getRawUserInfo() avoids decoding %3A in the username before splitting on the unescaped - // ':' + // rawUserInfo keeps %3A encoded, so indexOf(':') only matches the password separator final int colonLoc = rawUserInfo.indexOf(':'); final String rawUser = colonLoc >= 0 ? rawUserInfo.substring(0, colonLoc) : rawUserInfo; try { diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java index 668d962a9fc..2da05fd7846 100644 --- a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserPasswordLeakTest.java @@ -5,21 +5,14 @@ import org.tabletest.junit.TableTest; -/** - * Tests that passwords embedded in JDBC URL userinfo (user:password@host) do not leak into the - * {@code db.user} span tag. - * - *

{@link java.net.URI#getUserInfo()} returns the full userinfo component, including the - * password. Without sanitization, {@code builder.user("myuser:secret")} stores the password in - * {@code DBInfo.user}, which is then set as the {@code db.user} span tag. - */ class JDBCConnectionUrlParserPasswordLeakTest { @TableTest({ - "scenario | url | type | host | user ", - "PostgreSQL userinfo with password | jdbc:postgresql://myuser:secret123@pg.host/mydb | postgresql | pg.host | myuser ", - "PostgreSQL userinfo without password | jdbc:postgresql://myuser@pg.host/mydb | postgresql | pg.host | myuser ", - "PostgreSQL userinfo with percent-encoded colon | jdbc:postgresql://tenant%3Aalice@pg.host/mydb | postgresql | pg.host | tenant:alice" + "scenario | url | type | host | user ", + "PostgreSQL userinfo with password | jdbc:postgresql://myuser:secret123@pg.host/mydb | postgresql | pg.host | myuser ", + "PostgreSQL userinfo without password | jdbc:postgresql://myuser@pg.host/mydb | postgresql | pg.host | myuser ", + "PostgreSQL userinfo with percent-encoded colon | jdbc:postgresql://tenant%3Aalice@pg.host/mydb | postgresql | pg.host | tenant:alice", + "SAP userinfo with password | jdbc:sap://myuser:secret@sap.host/sapdb | sap | sap.host | myuser " }) void passwordShouldNotLeakIntoUserTag(String url, String type, String host, String user) { DBInfo info = extractDBInfo(url, null);