Skip to content

Redact connection-URL credentials from exceptions; stop echoing ES failure detail to DLQ - #933

Open
Jainam Jain (jjain1259) wants to merge 1 commit into
14.1.xfrom
jjain/log-leak-audit-followup
Open

Redact connection-URL credentials from exceptions; stop echoing ES failure detail to DLQ#933
Jainam Jain (jjain1259) wants to merge 1 commit into
14.1.xfrom
jjain/log-leak-audit-followup

Conversation

@jjain1259

Copy link
Copy Markdown
Member

Summary

Two latent (defense-in-depth, not currently-observed-with-sensitive-content) log-leak paths found by a systematic log-leak audit of this connector on 15.0.x, confirmed to also exist unchanged on 14.1.x. Both are follow-ups adjacent to the CC-42849/CC-36331 fixes, but outside the scope of what those covered.

1. Connection-URL credentials can reach exception messages via 4 unredacted HttpHost.create()/new URI() call sites

connection.url can carry an embedded user:pass@host credential (the code already documents this — see ConfigCallbackHandler's redactedConnectionUrls() javadoc). Four places parsed the raw URL with no redaction, so a malformed credentialed URL would echo the raw credential into an exception message:

  • ElasticsearchClient's constructor (the initial HttpHost[] build, unguarded, fires on every task start)
  • ConfigCallbackHandler.configureAuthentication() (same call, only reached when username/password auth is configured)
  • Validator.createClient() (validation-time client build — logged at ERROR and returned via the Connect config-validate REST API)
  • ElasticsearchSinkConnectorConfig's UrlListValidator (wrapped and rethrown uncaught by ElasticsearchSinkConnector.start())

Added ConfigCallbackHandler.createRedactedHttpHost(), which reuses the existing (already-correct, from CC-42849) redactUserInfo() helper to redact the URL before it can appear in a parse-failure exception's message, and applied it at all four sites. UrlListValidator also now passes a redacted URL list to ConfigException itself, since Kafka's own config-validation framework renders that argument verbatim in its generated message.

Production check: queried logs.json.connect (prod OpenSearch) for the last 30 days. The general catch-all this feeds into fired ~104,651 times at ERROR; sampled >99.9% of that volume (top 50 distinct exception messages, including 3 confirmed HttpHost.create() parse failures) — no embedded credential in any observed instance. Latent, not an incident, but real: customers who embed a credential in a malformed URL are exposed today across 4 code paths, one of which is directly returned by the REST API validate response, not just logs.

2. ElasticsearchClient's DLQ report echoed Elasticsearch's raw failure message

For bulk items failing with a malformed-document error (document_parsing_exception, mapper_parsing_exception, etc.), the connector reported "Indexing failed: " + response.getFailureMessage() to the errant-record reporter verbatim. Elasticsearch's own document_parsing_exception can include a preview of the offending field's value in that message. The DLQ report now includes only the op-type and index name; the full ES failure detail is still available at DEBUG (matching the existing DEBUG/TRACE precedent from CC-42849).

Production check: zero hits for "Indexing failed:" across the whole index in the last 30 days — not observed firing recently.

Test plan

  • mvn clean compile and mvn test-compile succeed
  • mvn test (excluding the Docker-dependent ElasticsearchClientTest) — 196/196 pass
  • New unit tests: ConfigCallbackHandlerTest (createRedactedHttpHost parses valid URLs, redacts credential on parse failure), ElasticsearchSinkConnectorConfigTest (UrlListValidator redacts credential in ConfigException message)
  • New integration test added to ElasticsearchClientTest (testReporterDoesNotLeakElasticsearchFailureMessage, modeled on the existing testReporterWithFail) — compiles cleanly but couldn't be run locally (no Docker in this environment); will run in CI

🤖 Generated with Claude Code

…ng ES failure detail to DLQ

Two latent (not-observed-in-production) log-leak paths found by a
systematic audit of this connector, both defense-in-depth follow-ups
to CC-42849/CC-36331:

- connection.url can carry an embedded user:pass@ credential. Four
  call sites parsed it via HttpHost.create()/new URI() with no
  redaction, so a malformed credentialed URL would echo the raw
  credential into an exception message: ElasticsearchClient's
  constructor, ConfigCallbackHandler.configureAuthentication(),
  Validator.createClient(), and ElasticsearchSinkConnectorConfig's
  UrlListValidator (whose ConfigException also surfaces via the
  Connect config-validate REST API, not just logs). Added
  ConfigCallbackHandler.createRedactedHttpHost() and reused the
  existing redactUserInfo() helper at all four sites.

- ElasticsearchClient's DLQ report for malformed-document bulk
  failures passed Elasticsearch's own failure message through
  unstripped; ES's document_parsing_exception can include a preview
  of the offending field's value. The DLQ report now includes only
  op-type/index; the full detail is still available at DEBUG.

Confirmed via production OpenSearch data that neither path has fired
with sensitive content in the last 30 days -- these are hardening
fixes, not incident response.
@jjain1259
Jainam Jain (jjain1259) requested a review from a team as a code owner July 20, 2026 07:49
@confluent-cla-assistant

Copy link
Copy Markdown

🎉 All Contributor License Agreements have been signed. Ready to merge.
Please push an empty commit if you would like to re-run the checks to verify CLA status for all contributors.

@sonarqube-confluent

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant