This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 38ad6e58a44d27198d6960a5afb71d32256d40a5 Author: opencode <[email protected]> AuthorDate: Mon Oct 26 15:47:59 2026 +0100 Mask the password in DataSourceProxy.getDbProperties() setPassword() stores the password into the shared database properties, and getDbProperties() handed out that live object unsanitized. DataSource is designed to be registered as its own MBean/bean, so the plaintext password reached the DbProperties bean/JMX view and any reflective exporter or direct caller, bypassing the module's deliberate masking: the same class masks getPassword() with a fixed placeholder, and the pool's own MBean has always exposed this view as a copy without the password. The proxy getter now returns that same password-free copy. The connect path is unaffected - it clones the properties from the PoolConfiguration directly - but callers that mutated the returned object expect that it no longer feeds back into the configuration. --- .../apache/tomcat/jdbc/pool/DataSourceProxy.java | 13 ++++++++- .../tomcat/jdbc/pool/jmx/TestJmxMonitor.java | 33 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) 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 7954be2b99..d51cd1438d 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 @@ -863,9 +863,20 @@ public class DataSourceProxy implements PoolConfiguration { } + /** + * Returns a copy of the database properties with the password removed. + * This object doubles as its own JMX/bean surface and + * {@link #setPassword(String)} stores the password into the database + * properties, so returning them as they are would expose the plaintext + * password through a getter while {@link #getPassword()} is explicitly + * masked - the same protection the pool's own MBean + * ({@link org.apache.tomcat.jdbc.pool.jmx.ConnectionPool#getDbProperties()}) + * already applies. + * {@inheritDoc} + */ @Override public Properties getDbProperties() { - return getPoolProperties().getDbProperties(); + return PoolUtilities.cloneWithoutPassword(getPoolProperties().getDbProperties()); } diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java index 85419e32d0..877de0b96d 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java @@ -240,6 +240,39 @@ class TestJmxMonitor extends JdbcPoolBaseTest { } } + @Test + void theDataSourceBeanSurfaceNeverExposesThePassword() throws Exception { + final String secret = "s3cr3t-pool-password"; + DataSource ds = newDataSource(); + ds.createPool(); // connect with the fixture credentials first + // the pool is already connected; this only records what a real + // configuration holds + ds.setPassword(secret); + ds.setConnectionProperties("appconfigured=yes"); + + // The DataSource object doubles as its own MBean/bean surface: + // the modeler and reflective exporters read these public getters. + // The deliberately masked getPassword() must not be bypassed by a + // sibling getter that still hands out the live db-properties. + Properties dbProperties = ds.getDbProperties(); + Assertions.assertNotNull(dbProperties); + for (String name : dbProperties.stringPropertyNames()) { + Assertions.assertFalse(secret.equals(dbProperties.getProperty(name)), + "the bean view of the driver properties must not carry the" + + " password, property " + name); + } + Assertions.assertEquals("yes", dbProperties.getProperty("appconfigured"), + "the sanitization must still show every other driver property"); + // The returned view is a copy: writing through it cannot smuggle + // values back into the pool configuration. + dbProperties.setProperty("injected", "through-the-view"); + Assertions.assertNull(ds.getPoolProperties().getDbProperties().getProperty("injected"), + "the exposed db-properties must be a view, not the live object"); + // and the pool must still authenticate - the copy is display-side + // only, the connect path keeps its own credentials + Assertions.assertDoesNotThrow(() -> ds.getConnection().close()); + } + @Test void closingThePoolUnregistersItsMBean() throws Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
