Security Report: Sensitive Configuration Values Leaked via PrometheusPropertiesException to HTTP Clients
1. Vulnerability Summary
| Field |
Value |
| Product |
prometheus/client_java |
| Affected Version |
1.8.0 (main branch); all prior 1.x releases using prometheus-metrics-config are likely affected |
| Component |
prometheus-metrics-config (root cause), prometheus-metrics-exporter-httpserver (disclosure vector) |
| Vulnerable File |
prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java |
| Vulnerable Lines |
26-27, 90-91, 132-133, 148-149, 164-165, 196-197 |
| CWE |
CWE-209: Generation of Error Message Containing Sensitive Information |
| CVSS 3.1 Score |
5.3 (Medium) |
| CVSS 3.1 Vector |
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N |
| Discoverer |
Security Research |
2. Vulnerability Description
The Util class in the prometheus-metrics-config module embeds raw, unredacted configuration property values directly into PrometheusPropertiesException error messages. When a property value cannot be parsed as the expected type (integer, double, long, boolean, or double list), the exception message includes the full property key and its verbatim value using the pattern fullKey + "=" + property + ": Expecting <type> value".
PrometheusPropertiesException extends RuntimeException (unchecked), which means it propagates freely through the call stack without requiring explicit handling. When this exception reaches the HttpExchangeAdapter in the prometheus-metrics-exporter-httpserver module, the sendErrorResponseWithStackTrace() method serializes the complete exception -- including the exception message containing the raw property value and the full Java stack trace -- into an HTTP 500 response body and sends it directly to the requesting client.
This creates an information disclosure vulnerability: if a configuration property inadvertently contains sensitive data (API keys, database passwords, tokens) and that value fails type parsing, the sensitive value is leaked verbatim to any unauthenticated HTTP client that triggers the error path. The /metrics endpoint served by the built-in HTTPServer is accessible without authentication by default.
3. Affected Component Architecture
The vulnerability involves a two-stage pipeline: (1) raw values are embedded in exception messages at the configuration layer, and (2) those messages are forwarded unredacted to HTTP clients at the exporter layer.
Environment Variable / Properties File
|
| raw property value read
v
Util.loadInteger() / loadDouble() / loadLong() / loadBoolean() / loadDoubleList()
|
| parse fails -> PrometheusPropertiesException("key=RAW_VALUE: Expecting ...")
v
PrometheusPropertiesException (RuntimeException, unchecked)
|
| propagates unchecked through call stack
v
PrometheusScrapeHandler.handleRequest()
|
| catch (RuntimeException e) -> exchange.handleException(e)
v
HttpExchangeAdapter.sendErrorResponseWithStackTrace()
|
| exception.printStackTrace() -> HTTP 500 response body
v
HTTP Client receives full exception message + stack trace
(including the raw property value)
Key Design Flaws
- Root cause (Util.java): Raw property values are concatenated into exception messages instead of being redacted or truncated.
- Propagation enabler (PrometheusPropertiesException): Being an unchecked
RuntimeException, the exception bypasses all intermediate error handling.
- Disclosure vector (HttpExchangeAdapter): The full stack trace, including the exception message with raw property values, is written to the HTTP response body.
4. Vulnerable Code
4.1 Root Cause: Raw Property Values in Exception Messages
File: prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java
All type-parsing methods in Util.java include the verbatim property value in exception messages. There are six instances:
Instance 1 -- loadBoolean() (lines 26-27):
throw new PrometheusPropertiesException(
String.format("%s: Expecting 'true' or 'false'. Found: %s", fullKey, property));
Instance 2 -- loadDoubleList() (lines 90-91):
throw new PrometheusPropertiesException(
fullKey + "=" + property + ": Expecting comma separated list of double values");
Instance 3 -- loadInteger() (lines 132-133):
throw new PrometheusPropertiesException(
fullKey + "=" + property + ": Expecting integer value");
Instance 4 -- loadDouble() (lines 148-149):
throw new PrometheusPropertiesException(
fullKey + "=" + property + ": Expecting double value");
Instance 5 -- loadLong() (lines 164-165):
throw new PrometheusPropertiesException(
fullKey + "=" + property + ": Expecting long value");
Instance 6 -- assertValue() (lines 196-197):
String fullMessage = String.format("%s: %s Found: %s", fullKey, message, number);
throw new PrometheusPropertiesException(fullMessage);
In every case, the raw, unredacted value of property is interpolated directly into the exception message string.
4.2 Exception Type: Unchecked RuntimeException
File: prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesException.java
@StableApi
public class PrometheusPropertiesException extends RuntimeException {
private static final long serialVersionUID = 0L;
public PrometheusPropertiesException(String msg) {
super(msg);
}
public PrometheusPropertiesException(String msg, Exception cause) {
super(msg, cause);
}
}
Because PrometheusPropertiesException extends RuntimeException, it is not required to be declared in method signatures and is not required to be caught. This allows it to propagate unchecked through the entire call stack to the HTTP handler layer.
4.3 Propagation Path: PrometheusScrapeHandler
File: prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java (lines 99-105)
public void handleRequest(PrometheusHttpExchange exchange) throws IOException {
try {
// ... scrape metrics ...
} catch (IOException e) {
exchange.handleException(e);
} catch (RuntimeException e) {
exchange.handleException(e); // <-- PrometheusPropertiesException caught here
} finally {
exchange.close();
}
}
The catch (RuntimeException e) clause catches any PrometheusPropertiesException and forwards it to the exchange adapter's handleException() method without sanitizing the exception message.
4.4 Disclosure Vector: HTTP Response with Stack Trace
File: prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java (lines 98-114)
@Override
public void handleException(RuntimeException e) {
sendErrorResponseWithStackTrace(e);
}
private void sendErrorResponseWithStackTrace(Exception requestHandlerException) {
if (!responseSent) {
responseSent = true;
try {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
printWriter.write("An Exception occurred while scraping metrics: ");
requestHandlerException.printStackTrace(new PrintWriter(printWriter));
byte[] stackTrace = stringWriter.toString().getBytes(StandardCharsets.UTF_8);
httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
httpExchange.sendResponseHeaders(500, stackTrace.length);
httpExchange.getResponseBody().write(stackTrace);
} catch (IOException errorWriterException) {
// ...
}
}
}
The requestHandlerException.printStackTrace() call serializes the full exception chain -- including the PrometheusPropertiesException message containing the raw property value -- into a StringWriter, which is then written verbatim to the HTTP response body.
4.5 Call Sites: Properties Loaded During Initialization
File: prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterHttpServerProperties.java (lines 34-44)
static ExporterHttpServerProperties load(PropertySource propertySource)
throws PrometheusPropertiesException {
Integer port = Util.loadInteger(PREFIX, PORT, propertySource); // <-- can throw with raw value
Util.assertValue(port, t -> t > 0, "Expecting value > 0.", PREFIX, PORT);
Boolean preferUncompressedResponse =
Util.loadBoolean(PREFIX, PREFER_UNCOMPRESSED_RESPONSE, propertySource);
return new ExporterHttpServerProperties(
port, preferUncompressedResponse != null && preferUncompressedResponse);
}
The Util.loadInteger() call parses the io.prometheus.exporter.http_server.port property. If the value is not a valid integer, the exception message includes the raw value.
5. CVSS 3.1 Detailed Breakdown
| Metric |
Value |
Justification |
| Attack Vector (AV) |
Network (N) |
The /metrics HTTP endpoint is network-accessible. The HTTPServer binds to a network socket (default: all interfaces). |
| Attack Complexity (AC) |
Low (L) |
No special conditions required beyond network access to the metrics port. The attacker only needs to send a standard HTTP request to the endpoint during a configuration error condition. |
| Privileges Required (PR) |
None (N) |
The HTTPServer does not require authentication by default. The Authenticator is an optional builder parameter that is null unless explicitly configured. |
| User Interaction (UI) |
None (N) |
No user interaction is needed. The attacker sends an HTTP request directly to the endpoint. |
| Scope (S) |
Unchanged (U) |
The vulnerability affects only the confidentiality of the vulnerable component; it does not cross trust boundaries to impact other components. |
| Confidentiality (C) |
Low (L) |
The disclosed information consists of raw configuration property values. If those values happen to contain credentials, API keys, or tokens (due to misconfiguration), the impact escalates, but the base vulnerability provides limited information. |
| Integrity (I) |
None (N) |
No data modification occurs. |
| Availability (A) |
None (N) |
No denial-of-service impact. |
CVSS 3.1 Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CVSS 3.1 Base Score: 5.3 (Medium)
6. Attack Chain Analysis
6.1 Primary Attack Chain: Lazy Servlet Initialization
The most realistic exploitation scenario involves servlet-based deployments where initialization is deferred until the first HTTP request:
- A web application registers
PrometheusMetricsServlet with load-on-startup unset or set to a negative value.
- The servlet container defers servlet initialization until the first request to the servlet's URL pattern.
- An environment variable or system property intended for a different purpose (e.g.,
IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT) is set to a non-numeric sensitive value.
- On the first request, the servlet container calls
PrometheusMetricsServlet() constructor, which calls PrometheusProperties.get(), triggering PrometheusPropertiesLoader.load().
ExporterHttpServerProperties.load() calls Util.loadInteger(), which throws PrometheusPropertiesException with the raw value in the message.
- For the httpserver module,
HttpExchangeAdapter.sendErrorResponseWithStackTrace() writes the full exception to the HTTP response. For servlet modules, the exception propagates to the container, which may display it in its default error page.
6.2 Secondary Attack Chain: Static Initializer Wrapping
When PrometheusProperties class initialization fails:
PrometheusProperties.instance is initialized via static field initializer: PrometheusPropertiesLoader.load().
- If
load() throws PrometheusPropertiesException, the JVM wraps it in ExceptionInInitializerError.
- The original
PrometheusPropertiesException (containing the raw property value) is preserved as the cause.
- When
printStackTrace() is called, the full cause chain is printed, including the PrometheusPropertiesException message with the raw value.
6.3 Tertiary Attack Chain: Custom Collectors Triggering Config Reload
If application code calls PrometheusProperties.get() or accesses configuration lazily within a custom Collector.collect() method, any resulting PrometheusPropertiesException will propagate through PrometheusScrapeHandler.handleRequest() to HttpExchangeAdapter.sendErrorResponseWithStackTrace(), exposing the raw property value in the HTTP response.
7. Proof of Concept
7.1 Prerequisites
- A Java application using
prometheus-metrics-exporter-httpserver with the built-in HTTPServer
- Network access to the metrics HTTP port
7.2 Reproduction Steps
Step 1: Set a configuration property to a value that will fail type parsing.
The value should represent sensitive data that an operator might accidentally place in a Prometheus configuration property. For example, set the histogram upper bounds to a value containing a credential:
# Simulates an operator who accidentally assigns a database password
# to a numeric configuration property
export IO_PROMETHEUS_METRICS_HISTOGRAM_CLASSIC_UPPER_BOUNDS="db_pass:S3cretK3y!@host:5432"
Alternatively, to target the HTTP server port property:
export IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT="my_secret_api_key_12345"
Step 2: Start the application with a Prometheus HTTP server.
import io.prometheus.metrics.core.metrics.Histogram;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
public class VulnerableApp {
public static void main(String[] args) throws Exception {
// Register a histogram to ensure histogram config properties are read
Histogram histogram = Histogram.builder()
.name("example_histogram")
.help("An example histogram")
.register();
HTTPServer server = HTTPServer.builder()
.port(9400)
.buildAndStart();
System.out.println("Server started on port " + server.getPort());
Thread.currentThread().join();
}
}
Step 3: Request the metrics endpoint.
curl -s http://localhost:9400/metrics
Step 4: Observe the raw property value in the HTTP response.
Expected HTTP 500 response body:
An Exception occurred while scraping metrics: io.prometheus.metrics.config.PrometheusPropertiesException:
io.prometheus.metrics.histogram.classic_upper_bounds=db_pass:S3cretK3y!@host:5432: Expecting comma separated list of double values
at io.prometheus.metrics.config.Util.loadDoubleList(Util.java:90)
at io.prometheus.metrics.config.MetricsProperties.load(MetricsProperties.java:...)
at io.prometheus.metrics.config.PrometheusPropertiesLoader.load(PrometheusPropertiesLoader.java:25)
at io.prometheus.metrics.config.PrometheusProperties.<clinit>(PrometheusProperties.java:17)
at io.prometheus.metrics.exporter.common.PrometheusScrapeHandler.<init>(PrometheusScrapeHandler.java:37)
at io.prometheus.metrics.exporter.httpserver.MetricsHandler.<init>(MetricsHandler.java:18)
at io.prometheus.metrics.exporter.httpserver.HTTPServer.<init>(HTTPServer.java:88)
...
The raw value db_pass:S3cretK3y!@host:5432 is fully visible in both the exception message and the HTTP response body.
7.3 Servlet-Based Reproduction
For servlet containers with lazy initialization:
web.xml (no load-on-startup):
<servlet>
<servlet-name>prometheus</servlet-name>
<servlet-class>
io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet
</servlet-class>
<!-- No load-on-startup: servlet initialized on first request -->
</servlet>
<servlet-mapping>
<servlet-name>prometheus</servlet-name>
<url-pattern>/metrics</url-pattern>
</servlet-mapping>
Step 1: Set an invalid property with a sensitive value.
export IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT="Bearer_eyJhbGciOiJIUzI1NiJ9.secret"
Step 2: Deploy the application and send the first request.
curl -s http://localhost:8080/metrics
Step 3: The servlet container receives the PrometheusPropertiesException.
The Jakarta and Javax servlet adapters re-throw the exception (throw e; in handleException()), delegating error handling to the servlet container. Many servlet containers (Tomcat, Jetty) include the exception message in their default error page, disclosing the raw property value.
7.4 Integer Port Property Example
# Set port to a value containing an OAuth token
export IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
When ExporterHttpServerProperties.load() calls Util.loadInteger(), the thrown exception message will be:
io.prometheus.exporter.http_server.port=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: Expecting integer value
This message, containing the full GitHub Personal Access Token, is then written to the HTTP response body via sendErrorResponseWithStackTrace().
8. Impact Analysis
8.1 Direct Impact: Sensitive Configuration Value Disclosure
Raw configuration property values are exposed to unauthenticated HTTP clients. The severity depends on what those values contain:
| Scenario |
Leaked Data |
Severity |
| Database password in wrong property |
Full connection string/password |
Critical |
| API key/token in wrong property |
Full API key or bearer token |
Critical |
| Internal URL with embedded credentials |
Host, port, username, password |
High |
| Non-sensitive misconfiguration |
Configuration value |
Low |
8.2 Root Cause vs. Symptom Distinction
This vulnerability is distinct from the general stack trace disclosure issue (CVE Report #2). While stack trace disclosure reveals internal implementation details (class names, method names, line numbers), this vulnerability specifically leaks the content of configuration property values. The distinction matters because:
- Stack traces reveal structural information about the application (reconnaissance value).
- Property values may contain credentials, tokens, or secrets (direct exploitation value).
The two vulnerabilities compound: the stack trace provides the delivery mechanism, and the raw property value in the exception message provides the sensitive payload.
8.3 Why Misconfiguration Happens
Configuration errors that trigger this vulnerability are not hypothetical:
- Environment variable collisions: In containerized environments, environment variables from different services may overlap. An environment variable like
IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT might be set by an orchestration tool to a value containing metadata or credentials.
- Copy-paste errors: Operators copying configuration blocks may accidentally assign a credential value to a Prometheus property.
- Configuration management tools: Templating errors in tools like Ansible, Terraform, or Helm can substitute incorrect values into property placeholders.
- Twelve-factor app patterns: Applications following the twelve-factor methodology store all configuration in environment variables, increasing the chance of sensitive values being present in the environment.
8.4 Deployment Context
The HTTPServer component is commonly used in:
- Standalone Java applications without a servlet container
- Java agents for JVM instrumentation
- Microservices exposing metrics alongside application traffic
In Kubernetes deployments, the metrics port is frequently reachable from any pod in the cluster via the pod IP or service IP, and Prometheus scrape targets are often discoverable through service discovery. An attacker with network access to the cluster can enumerate and scrape all metrics endpoints.
9. Scope Clarification
Affected Modules
| Module |
Role |
Impact |
prometheus-metrics-config |
Root cause -- raw values embedded in exception messages |
All 6 throw sites in Util.java include verbatim property values |
prometheus-metrics-exporter-httpserver |
Direct disclosure vector -- sendErrorResponseWithStackTrace() writes full exception to HTTP response |
Raw values visible in HTTP 500 response body |
prometheus-metrics-exporter-servlet-jakarta |
Indirect disclosure vector -- handleException() re-throws to servlet container |
Raw values may appear in container error pages (depends on container configuration) |
prometheus-metrics-exporter-servlet-javax |
Indirect disclosure vector -- handleException() re-throws to servlet container |
Same as Jakarta variant |
Comparison: httpserver vs. Servlet Adapters
| Behavior |
httpserver |
servlet-jakarta / servlet-javax |
handleException(RuntimeException e) |
Calls sendErrorResponseWithStackTrace(e) -- writes full stack trace to response |
throw e; -- re-throws to servlet container |
| Disclosure guaranteed? |
Yes -- always writes stack trace to response body |
Depends on container error page configuration |
| Authentication by default? |
None (optional Authenticator) |
Depends on servlet container and web.xml security constraints |
10. Recommended Remediation
10.1 Primary Fix: Redact Property Values in Exception Messages (Util.java)
Replace raw property value interpolation with a redacted form that indicates the value type and length without disclosing its content:
// BEFORE (vulnerable):
throw new PrometheusPropertiesException(
fullKey + "=" + property + ": Expecting integer value");
// AFTER (remediated):
throw new PrometheusPropertiesException(
fullKey + "=<redacted " + property.length() + " chars>: Expecting integer value");
Apply this pattern to all six throw sites in Util.java:
loadBoolean() line 27
loadDoubleList() line 91
loadInteger() line 133
loadDouble() line 149
loadLong() line 165
assertValue() line 197
10.2 Secondary Fix: Sanitize Error Responses in HttpExchangeAdapter
In HttpExchangeAdapter.sendErrorResponseWithStackTrace(), replace the full stack trace with a generic error message and log the details server-side:
private void sendErrorResponseWithStackTrace(Exception requestHandlerException) {
if (!responseSent) {
responseSent = true;
try {
// Log full details server-side only
Logger.getLogger(this.getClass().getName())
.log(Level.SEVERE,
"An Exception occurred while scraping metrics.",
requestHandlerException);
// Send generic message to client -- no exception details
byte[] message = "An internal error occurred while scraping metrics.\n"
.getBytes(StandardCharsets.UTF_8);
httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
httpExchange.sendResponseHeaders(500, message.length);
httpExchange.getResponseBody().write(message);
} catch (IOException errorWriterException) {
// ... existing fallback logging ...
}
}
}
10.3 Structural Fix: Fail-Fast at Startup
Move all configuration validation to application startup time rather than allowing lazy initialization to defer errors to scrape time:
- Call
PrometheusProperties.get() eagerly during HTTPServer.builder() (already done) and during servlet init().
- Validate all properties during
PrometheusPropertiesLoader.load() (already done for known properties).
- Document that applications should call
PrometheusProperties.get() during startup to surface configuration errors early.
10.4 Defense in Depth: Make PrometheusPropertiesException Checked
Consider changing PrometheusPropertiesException to extend Exception instead of RuntimeException. This would force all callers to explicitly handle or declare the exception, preventing accidental propagation to HTTP clients. Note: this is a breaking API change and would require a major version bump.
10.5 Remediation Priority
| Fix |
Priority |
Breaking Change |
Effort |
| 10.1 Redact values in exception messages |
P0 -- Critical |
No |
Low |
| 10.2 Sanitize HTTP error responses |
P0 -- Critical |
No |
Low |
| 10.3 Fail-fast at startup |
P1 -- High |
No |
Medium |
| 10.4 Make exception checked |
P2 -- Medium |
Yes |
High |
Fixes 10.1 and 10.2 provide independent layers of defense and should both be applied. Fix 10.1 eliminates the root cause (sensitive values in exception messages). Fix 10.2 eliminates the disclosure vector (stack traces in HTTP responses). Together they provide defense in depth.
11. References
Security Report: Sensitive Configuration Values Leaked via PrometheusPropertiesException to HTTP Clients
1. Vulnerability Summary
prometheus-metrics-configare likely affectedprometheus-metrics-config(root cause),prometheus-metrics-exporter-httpserver(disclosure vector)prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.javaCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N2. Vulnerability Description
The
Utilclass in theprometheus-metrics-configmodule embeds raw, unredacted configuration property values directly intoPrometheusPropertiesExceptionerror messages. When a property value cannot be parsed as the expected type (integer, double, long, boolean, or double list), the exception message includes the full property key and its verbatim value using the patternfullKey + "=" + property + ": Expecting <type> value".PrometheusPropertiesExceptionextendsRuntimeException(unchecked), which means it propagates freely through the call stack without requiring explicit handling. When this exception reaches theHttpExchangeAdapterin theprometheus-metrics-exporter-httpservermodule, thesendErrorResponseWithStackTrace()method serializes the complete exception -- including the exception message containing the raw property value and the full Java stack trace -- into an HTTP 500 response body and sends it directly to the requesting client.This creates an information disclosure vulnerability: if a configuration property inadvertently contains sensitive data (API keys, database passwords, tokens) and that value fails type parsing, the sensitive value is leaked verbatim to any unauthenticated HTTP client that triggers the error path. The
/metricsendpoint served by the built-inHTTPServeris accessible without authentication by default.3. Affected Component Architecture
The vulnerability involves a two-stage pipeline: (1) raw values are embedded in exception messages at the configuration layer, and (2) those messages are forwarded unredacted to HTTP clients at the exporter layer.
Key Design Flaws
RuntimeException, the exception bypasses all intermediate error handling.4. Vulnerable Code
4.1 Root Cause: Raw Property Values in Exception Messages
File:
prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.javaAll type-parsing methods in
Util.javainclude the verbatim property value in exception messages. There are six instances:Instance 1 --
loadBoolean()(lines 26-27):Instance 2 --
loadDoubleList()(lines 90-91):Instance 3 --
loadInteger()(lines 132-133):Instance 4 --
loadDouble()(lines 148-149):Instance 5 --
loadLong()(lines 164-165):Instance 6 --
assertValue()(lines 196-197):In every case, the raw, unredacted value of
propertyis interpolated directly into the exception message string.4.2 Exception Type: Unchecked RuntimeException
File:
prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesException.javaBecause
PrometheusPropertiesExceptionextendsRuntimeException, it is not required to be declared in method signatures and is not required to be caught. This allows it to propagate unchecked through the entire call stack to the HTTP handler layer.4.3 Propagation Path: PrometheusScrapeHandler
File:
prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java(lines 99-105)The
catch (RuntimeException e)clause catches anyPrometheusPropertiesExceptionand forwards it to the exchange adapter'shandleException()method without sanitizing the exception message.4.4 Disclosure Vector: HTTP Response with Stack Trace
File:
prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java(lines 98-114)The
requestHandlerException.printStackTrace()call serializes the full exception chain -- including thePrometheusPropertiesExceptionmessage containing the raw property value -- into aStringWriter, which is then written verbatim to the HTTP response body.4.5 Call Sites: Properties Loaded During Initialization
File:
prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterHttpServerProperties.java(lines 34-44)The
Util.loadInteger()call parses theio.prometheus.exporter.http_server.portproperty. If the value is not a valid integer, the exception message includes the raw value.5. CVSS 3.1 Detailed Breakdown
/metricsHTTP endpoint is network-accessible. TheHTTPServerbinds to a network socket (default: all interfaces).HTTPServerdoes not require authentication by default. TheAuthenticatoris an optional builder parameter that isnullunless explicitly configured.CVSS 3.1 Vector String:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NCVSS 3.1 Base Score: 5.3 (Medium)
6. Attack Chain Analysis
6.1 Primary Attack Chain: Lazy Servlet Initialization
The most realistic exploitation scenario involves servlet-based deployments where initialization is deferred until the first HTTP request:
PrometheusMetricsServletwithload-on-startupunset or set to a negative value.IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT) is set to a non-numeric sensitive value.PrometheusMetricsServlet()constructor, which callsPrometheusProperties.get(), triggeringPrometheusPropertiesLoader.load().ExporterHttpServerProperties.load()callsUtil.loadInteger(), which throwsPrometheusPropertiesExceptionwith the raw value in the message.HttpExchangeAdapter.sendErrorResponseWithStackTrace()writes the full exception to the HTTP response. For servlet modules, the exception propagates to the container, which may display it in its default error page.6.2 Secondary Attack Chain: Static Initializer Wrapping
When
PrometheusPropertiesclass initialization fails:PrometheusProperties.instanceis initialized via static field initializer:PrometheusPropertiesLoader.load().load()throwsPrometheusPropertiesException, the JVM wraps it inExceptionInInitializerError.PrometheusPropertiesException(containing the raw property value) is preserved as the cause.printStackTrace()is called, the full cause chain is printed, including thePrometheusPropertiesExceptionmessage with the raw value.6.3 Tertiary Attack Chain: Custom Collectors Triggering Config Reload
If application code calls
PrometheusProperties.get()or accesses configuration lazily within a customCollector.collect()method, any resultingPrometheusPropertiesExceptionwill propagate throughPrometheusScrapeHandler.handleRequest()toHttpExchangeAdapter.sendErrorResponseWithStackTrace(), exposing the raw property value in the HTTP response.7. Proof of Concept
7.1 Prerequisites
prometheus-metrics-exporter-httpserverwith the built-inHTTPServer7.2 Reproduction Steps
Step 1: Set a configuration property to a value that will fail type parsing.
The value should represent sensitive data that an operator might accidentally place in a Prometheus configuration property. For example, set the histogram upper bounds to a value containing a credential:
Alternatively, to target the HTTP server port property:
Step 2: Start the application with a Prometheus HTTP server.
Step 3: Request the metrics endpoint.
Step 4: Observe the raw property value in the HTTP response.
Expected HTTP 500 response body:
The raw value
db_pass:S3cretK3y!@host:5432is fully visible in both the exception message and the HTTP response body.7.3 Servlet-Based Reproduction
For servlet containers with lazy initialization:
web.xml (no
load-on-startup):Step 1: Set an invalid property with a sensitive value.
Step 2: Deploy the application and send the first request.
Step 3: The servlet container receives the
PrometheusPropertiesException.The Jakarta and Javax servlet adapters re-throw the exception (
throw e;inhandleException()), delegating error handling to the servlet container. Many servlet containers (Tomcat, Jetty) include the exception message in their default error page, disclosing the raw property value.7.4 Integer Port Property Example
When
ExporterHttpServerProperties.load()callsUtil.loadInteger(), the thrown exception message will be:This message, containing the full GitHub Personal Access Token, is then written to the HTTP response body via
sendErrorResponseWithStackTrace().8. Impact Analysis
8.1 Direct Impact: Sensitive Configuration Value Disclosure
Raw configuration property values are exposed to unauthenticated HTTP clients. The severity depends on what those values contain:
8.2 Root Cause vs. Symptom Distinction
This vulnerability is distinct from the general stack trace disclosure issue (CVE Report #2). While stack trace disclosure reveals internal implementation details (class names, method names, line numbers), this vulnerability specifically leaks the content of configuration property values. The distinction matters because:
The two vulnerabilities compound: the stack trace provides the delivery mechanism, and the raw property value in the exception message provides the sensitive payload.
8.3 Why Misconfiguration Happens
Configuration errors that trigger this vulnerability are not hypothetical:
IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORTmight be set by an orchestration tool to a value containing metadata or credentials.8.4 Deployment Context
The
HTTPServercomponent is commonly used in:In Kubernetes deployments, the metrics port is frequently reachable from any pod in the cluster via the pod IP or service IP, and Prometheus scrape targets are often discoverable through service discovery. An attacker with network access to the cluster can enumerate and scrape all metrics endpoints.
9. Scope Clarification
Affected Modules
prometheus-metrics-configUtil.javainclude verbatim property valuesprometheus-metrics-exporter-httpserversendErrorResponseWithStackTrace()writes full exception to HTTP responseprometheus-metrics-exporter-servlet-jakartahandleException()re-throws to servlet containerprometheus-metrics-exporter-servlet-javaxhandleException()re-throws to servlet containerComparison: httpserver vs. Servlet Adapters
handleException(RuntimeException e)sendErrorResponseWithStackTrace(e)-- writes full stack trace to responsethrow e;-- re-throws to servlet containerAuthenticator)web.xmlsecurity constraints10. Recommended Remediation
10.1 Primary Fix: Redact Property Values in Exception Messages (Util.java)
Replace raw property value interpolation with a redacted form that indicates the value type and length without disclosing its content:
Apply this pattern to all six throw sites in
Util.java:loadBoolean()line 27loadDoubleList()line 91loadInteger()line 133loadDouble()line 149loadLong()line 165assertValue()line 19710.2 Secondary Fix: Sanitize Error Responses in HttpExchangeAdapter
In
HttpExchangeAdapter.sendErrorResponseWithStackTrace(), replace the full stack trace with a generic error message and log the details server-side:10.3 Structural Fix: Fail-Fast at Startup
Move all configuration validation to application startup time rather than allowing lazy initialization to defer errors to scrape time:
PrometheusProperties.get()eagerly duringHTTPServer.builder()(already done) and during servletinit().PrometheusPropertiesLoader.load()(already done for known properties).PrometheusProperties.get()during startup to surface configuration errors early.10.4 Defense in Depth: Make PrometheusPropertiesException Checked
Consider changing
PrometheusPropertiesExceptionto extendExceptioninstead ofRuntimeException. This would force all callers to explicitly handle or declare the exception, preventing accidental propagation to HTTP clients. Note: this is a breaking API change and would require a major version bump.10.5 Remediation Priority
Fixes 10.1 and 10.2 provide independent layers of defense and should both be applied. Fix 10.1 eliminates the root cause (sensitive values in exception messages). Fix 10.2 eliminates the disclosure vector (stack traces in HTTP responses). Together they provide defense in depth.
11. References
Util.javaHttpExchangeAdapter.javaPrometheusPropertiesException.javaPrometheusScrapeHandler.java