From cad0d8e6ef40ed5d1b5deef0cc874bddb68517e9 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 23 Jul 2026 10:14:50 +0000 Subject: [PATCH] fix: redact invalid configuration values Signed-off-by: Gregor Zeitlinger --- .../config/ExporterPushgatewayProperties.java | 12 +++++------- .../config/PrometheusPropertiesLoader.java | 3 +-- .../java/io/prometheus/metrics/config/Util.java | 14 +++++++++----- .../metrics/config/ExporterPropertiesTest.java | 9 +++------ .../ExporterPushgatewayPropertiesTest.java | 4 ++-- .../config/OpenMetrics2PropertiesTest.java | 17 +++++------------ .../config/PrometheusPropertiesLoaderTest.java | 5 ++--- .../io/prometheus/metrics/config/UtilTest.java | 14 +++++++++++++- .../exporter/pushgateway/PushGateway.java | 4 ++-- .../exporter/pushgateway/PushGatewayTest.java | 10 ++++++++++ 10 files changed, 52 insertions(+), 40 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 10e8c0f33..e97ade191 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -94,9 +94,8 @@ static ExporterPushgatewayProperties load(PropertySource propertySource) if (scheme != null) { if (!scheme.equals("http") && !scheme.equals("https")) { throw new PrometheusPropertiesException( - String.format( - "%s.%s: Illegal value. Expecting 'http' or 'https'. Found: %s", - PREFIX, SCHEME, scheme)); + Util.invalidValueMessage( + PREFIX + "." + SCHEME, "Illegal value. Expecting 'http' or 'https'.")); } } @@ -119,10 +118,9 @@ static ExporterPushgatewayProperties load(PropertySource propertySource) return EscapingScheme.DOTS_ESCAPING; default: throw new PrometheusPropertiesException( - String.format( - "%s.%s: Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', " - + "or 'dots'. Found: %s", - PREFIX, ESCAPING_SCHEME, scheme)); + Util.invalidValueMessage( + PREFIX + "." + ESCAPING_SCHEME, + "Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', or 'dots'.")); } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java index 6d52b71ef..04bbfe4a5 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java @@ -169,8 +169,7 @@ private static Properties loadPropertiesFromFile() throws PrometheusPropertiesEx try (InputStream stream = Files.newInputStream(Paths.get(path))) { properties.load(stream); } catch (IOException e) { - throw new PrometheusPropertiesException( - "Failed to read Prometheus properties from " + path + ": " + e.getMessage(), e); + throw new PrometheusPropertiesException("Failed to read Prometheus properties file.", e); } } return properties; diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java index 20bd75699..d90810faf 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java @@ -24,7 +24,7 @@ static Boolean loadBoolean(String prefix, String propertyName, PropertySource pr String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; if (!"true".equalsIgnoreCase(property) && !"false".equalsIgnoreCase(property)) { throw new PrometheusPropertiesException( - String.format("%s: Expecting 'true' or 'false'. Found: %s", fullKey, property)); + invalidValueMessage(fullKey, "Expecting 'true' or 'false'.")); } return Boolean.parseBoolean(property); } @@ -88,7 +88,7 @@ static List loadDoubleList( } } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting comma separated list of double values"); + invalidValueMessage(fullKey, "Expecting comma separated list of double values")); } } return Arrays.asList(result); @@ -130,7 +130,7 @@ static Integer loadInteger(String prefix, String propertyName, PropertySource pr return Integer.parseInt(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting integer value"); + invalidValueMessage(fullKey, "Expecting integer value")); } } return null; @@ -146,7 +146,7 @@ static Double loadDouble(String prefix, String propertyName, PropertySource prop return Double.parseDouble(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting double value"); + invalidValueMessage(fullKey, "Expecting double value")); } } return null; @@ -162,7 +162,7 @@ static Long loadLong(String prefix, String propertyName, PropertySource property return Long.parseLong(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting long value"); + invalidValueMessage(fullKey, "Expecting long value")); } } return null; @@ -197,4 +197,8 @@ static void assertValue( throw new PrometheusPropertiesException(fullMessage); } } + + static String invalidValueMessage(String fullKey, String message) { + return fullKey + ": " + message; + } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java index 514c5ff52..2f912ff77 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java @@ -27,8 +27,7 @@ void load() { new HashMap<>( Map.of("io.prometheus.exporter.include_created_timestamps", "invalid")))) .withMessage( - "io.prometheus.exporter.include_created_timestamps: Expecting 'true' or 'false'. Found:" - + " invalid"); + "io.prometheus.exporter.include_created_timestamps: Expecting 'true' or 'false'."); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -36,8 +35,7 @@ void load() { new HashMap<>( Map.of("io.prometheus.exporter.exemplars_on_all_metric_types", "invalid")))) .withMessage( - "io.prometheus.exporter.exemplars_on_all_metric_types: Expecting 'true' or 'false'." - + " Found: invalid"); + "io.prometheus.exporter.exemplars_on_all_metric_types: Expecting 'true' or 'false'."); } private static ExporterProperties load(Map map) { @@ -84,7 +82,6 @@ void prometheusTimestampsInMs() { new HashMap<>( Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "invalid")))) .withMessage( - "io.prometheus.exporter.prometheus_timestamps_in_ms: Expecting 'true' or 'false'." - + " Found: invalid"); + "io.prometheus.exporter.prometheus_timestamps_in_ms: Expecting 'true' or 'false'."); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java index c92e6f2f9..4715662f3 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java @@ -27,7 +27,7 @@ void load() { .isThrownBy(() -> load(Map.of("io.prometheus.exporter.pushgateway.scheme", "foo"))) .withMessage( "io.prometheus.exporter.pushgateway.scheme: Illegal value. Expecting 'http' or 'https'." - + " Found: foo"); + + ""); } @Test @@ -60,7 +60,7 @@ void loadWithInvalidEscapingScheme() { () -> load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "invalid"))) .withMessage( "io.prometheus.exporter.pushgateway.escaping_scheme: Illegal value. Expecting" - + " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: invalid"); + + " 'allow-utf-8', 'values', 'underscores', or 'dots'."); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java index e7a273464..0546a138f 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java @@ -37,8 +37,7 @@ void loadInvalidValue() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> load(new HashMap<>(Map.of("io.prometheus.openmetrics2.enabled", "invalid")))) - .withMessage( - "io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'. Found: invalid"); + .withMessage("io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'."); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -46,17 +45,14 @@ void loadInvalidValue() { new HashMap<>( Map.of("io.prometheus.openmetrics2.content_negotiation", "invalid")))) .withMessage( - "io.prometheus.openmetrics2.content_negotiation: Expecting 'true' or 'false'. Found:" - + " invalid"); + "io.prometheus.openmetrics2.content_negotiation: Expecting 'true' or 'false'."); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> load( new HashMap<>( Map.of("io.prometheus.openmetrics2.composite_values", "invalid")))) - .withMessage( - "io.prometheus.openmetrics2.composite_values: Expecting 'true' or 'false'. Found:" - + " invalid"); + .withMessage("io.prometheus.openmetrics2.composite_values: Expecting 'true' or 'false'."); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -64,17 +60,14 @@ void loadInvalidValue() { new HashMap<>( Map.of("io.prometheus.openmetrics2.exemplar_compliance", "invalid")))) .withMessage( - "io.prometheus.openmetrics2.exemplar_compliance: Expecting 'true' or 'false'. Found:" - + " invalid"); + "io.prometheus.openmetrics2.exemplar_compliance: Expecting 'true' or 'false'."); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> load( new HashMap<>( Map.of("io.prometheus.openmetrics2.native_histograms", "invalid")))) - .withMessage( - "io.prometheus.openmetrics2.native_histograms: Expecting 'true' or 'false'. Found:" - + " invalid"); + .withMessage("io.prometheus.openmetrics2.native_histograms: Expecting 'true' or 'false'."); } private static OpenMetrics2Properties load(Map map) { diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java index 532b00295..8f910cceb 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java @@ -31,9 +31,8 @@ void propertiesShouldBeLoadedFromPropertiesFile() { void cantLoadPropertiesFile() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> PrometheusPropertiesLoader.load(new Properties())) - .withMessage( - "Failed to read Prometheus properties from nonexistent.properties:" - + " nonexistent.properties"); + .withMessage("Failed to read Prometheus properties file.") + .withMessageNotContaining("nonexistent.properties"); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java index e4d7fa829..c3ed65ec7 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java @@ -51,6 +51,18 @@ void loadOptionalDuration_invalidNumber_throws() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) - .withMessage("foo=abc: Expecting long value"); + .withMessage("foo: Expecting long value"); + } + + @Test + void invalidValueMessageRedactsRawValue() { + String secret = "bad\n\"secret-value"; + Map regularProperties = new HashMap<>(Map.of("foo", secret)); + PropertySource propertySource = new PropertySource(regularProperties); + + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy(() -> Util.loadBoolean("", "foo", propertySource)) + .withMessage("foo: Expecting 'true' or 'false'.") + .withMessageNotContaining(secret); } } diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 5bf26b6c1..b7dd844ec 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -554,9 +554,9 @@ public PushGateway build() { getEscapingScheme(properties), getConnectionTimeout(properties), getReadTimeout(properties)); - } catch (MalformedURLException e) { + } catch (MalformedURLException | IllegalArgumentException e) { throw new PrometheusPropertiesException( - address + ": Invalid address. Expecting :"); + "Invalid Pushgateway address. Expecting :"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); // cannot happen, UTF-8 is always supported } diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index bae8fdd91..8c33543f9 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -6,6 +6,7 @@ import static org.mockserver.model.HttpResponse.response; import io.prometheus.metrics.config.EscapingScheme; +import io.prometheus.metrics.config.PrometheusPropertiesException; import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.io.IOException; @@ -48,6 +49,15 @@ void testInvalidURLThrowsRuntimeException() { }); } + @Test + void testInvalidURLDoesNotExposeCredentials() { + String secretAddress = "user:secret@[::"; + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy(() -> PushGateway.builder().address(secretAddress).build()) + .withMessage("Invalid Pushgateway address. Expecting :") + .withMessageNotContaining("secret"); + } + @Test void testMultipleSlashesAreStrippedFromURL() throws NoSuchFieldException, IllegalAccessException { final PushGateway pushGateway =