This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit f10512972fca9e4a8d1a29b349a9f58d21e8aa15 Author: opencode <[email protected]> AuthorDate: Thu Nov 5 13:46:29 2026 +0100 Mask inline credentials on the DataSource bean surface DataSourceProxy doubles as the data source's bean/JMX surface, but getUrl(), getConnectionProperties() and getJdbcInterceptors() returned the configured strings verbatim while the pool MBean masked the same credentials. A client reading the DataSource MBean received a password typed into the URL, connectionProperties or an interceptor definition in cleartext. Apply the same masking to the three getters, leaving raw access through getPoolProperties() and the setters. Add a regression test for all three bean getters and for the raw programmatic accessor. --- .../apache/tomcat/jdbc/pool/DataSourceProxy.java | 22 +++++++++++++-- .../jdbc/pool/jmx/TestJmxCredentialAttributes.java | 33 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java index 96df1c2bf4..9d4875c1b2 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java @@ -1140,9 +1140,15 @@ public class DataSourceProxy implements PoolConfiguration { } + /** + * Returns the connection properties with any credential they carry masked out, so the data source's own bean/JMX + * surface does not expose a password in cleartext. The write is not masked: {@link #setConnectionProperties(String)} + * takes the string as it is meant to reach the driver. Use {@link #getPoolProperties()} for the raw value. + * {@inheritDoc} + */ @Override public String getConnectionProperties() { - return getPoolProperties().getConnectionProperties(); + return PoolProperties.maskCredentials(getPoolProperties().getConnectionProperties()); } @@ -1195,9 +1201,14 @@ public class DataSourceProxy implements PoolConfiguration { } + /** + * Returns the interceptor configuration with any credential its definitions carry masked out, so the data source's + * own bean/JMX surface does not expose a password in cleartext. Use {@link #getPoolProperties()} for the raw value. + * {@inheritDoc} + */ @Override public String getJdbcInterceptors() { - return getPoolProperties().getJdbcInterceptors(); + return PoolProperties.maskInterceptorCredentials(getPoolProperties().getJdbcInterceptors()); } @@ -1269,9 +1280,14 @@ public class DataSourceProxy implements PoolConfiguration { } + /** + * Returns the JDBC URL with any credential it carries masked out, so the data source's own bean/JMX surface does + * not expose a password in cleartext. The write is not masked: {@link #setUrl(String)} takes the URL as it is meant + * to reach the driver. Use {@link #getPoolProperties()} for the raw value. {@inheritDoc} + */ @Override public String getUrl() { - return getPoolProperties().getUrl(); + return PoolProperties.maskCredentials(getPoolProperties().getUrl()); } diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxCredentialAttributes.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxCredentialAttributes.java index d75b177c5d..0c88f1c5bc 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxCredentialAttributes.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxCredentialAttributes.java @@ -201,6 +201,39 @@ class TestJmxCredentialAttributes extends JdbcPoolBaseTest { } } + @Test + void theDataSourceBeanGettersNeverCarryTheInlineCredentials() throws Exception { + DataSource ds = poolWithInlineCredentials(); + ds.setJdbcInterceptors("ResetAbandonedTimer(password=" + INTERCEPTOR_SECRET + ")"); + try { + Assertions.assertFalse(ds.getUrl().contains(URL_SECRET), + "the data source bean getter must mask a credential typed into the URL"); + Assertions.assertEquals(getUrl() + ";password=" + MASK, ds.getUrl(), + "the bean getter must show the same mask as the pool MBean"); + Assertions.assertFalse(ds.getConnectionProperties().contains(PROPERTIES_SECRET), + "the data source bean getter must mask a credential in the connection properties"); + Assertions.assertEquals("password=" + MASK + ";a=b", ds.getConnectionProperties(), + "the bean getter must keep the non-credential pairs readable"); + Assertions.assertFalse(ds.getJdbcInterceptors().contains(INTERCEPTOR_SECRET), + "the data source bean getter must mask a credential in an interceptor definition"); + Assertions.assertEquals("ResetAbandonedTimer(password=" + MASK + ")", + ds.getJdbcInterceptors(), + "the bean getter must keep the interceptor name and property readable"); + + Assertions.assertEquals(getUrl() + ";password=" + URL_SECRET, + ds.getPoolProperties().getUrl(), + "the programmatic configuration accessor must still return the raw URL"); + Assertions.assertEquals("password=" + PROPERTIES_SECRET + ";a=b", + ds.getPoolProperties().getConnectionProperties(), + "the programmatic configuration accessor must still return the raw connection properties"); + Assertions.assertEquals("ResetAbandonedTimer(password=" + INTERCEPTOR_SECRET + ")", + ds.getPoolProperties().getJdbcInterceptors(), + "the programmatic configuration accessor must still return the raw interceptor string"); + } finally { + ds.close(); + } + } + @Test void theConnectionPropertiesAttributeOfThePoolMBeanNeverCarriesTheCredentialTypedIntoThem() throws Exception { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
