Skip to content

Fix: CLI's "--debug" mode leaks credentials to stderr (sanitizer misses camelCase keys on the wire) - #5289

Open
XD-DENG wants to merge 3 commits into
apache:mainfrom
XD-DENG:fix/cli-debug-sanitizer-camelcase
Open

Fix: CLI's "--debug" mode leaks credentials to stderr (sanitizer misses camelCase keys on the wire)#5289
XD-DENG wants to merge 3 commits into
apache:mainfrom
XD-DENG:fix/cli-debug-sanitizer-camelcase

Conversation

@XD-DENG

@XD-DENG XD-DENG commented Aug 14, 2026

Copy link
Copy Markdown
Member

Problem

CLI polaris --debug mirrors HTTP request and response bodies to stderr. Bodies are supposed to be redacted before writing, which is the stated purpose of log_sanitizer.py.

However, the sanitizer redacts only client_secret / access_token / refresh_token (snake_case). The Polaris management API serializes bodies by alias (camelCase) so clientSecret, bearerToken, etc. flow through unredacted. Every real management-plane request and response leaks credentials under --debug. The snake_case keys only appear in the /oauth/tokens form body, which is already handled by a URL-based special case, so the sanitizer's key list is effectively dead for real traffic.

How to reproduce the issue (no server required)

Example A — principals reset (top-level clientSecret)

polaris --debug --access-token dummy \
  --base-url http://127.0.0.1:1 \
  principals reset alice --new-client-id NEWCID --new-client-secret LEAK-ME-IF-YOU-CAN
Branch Relevant stderr line
main Body: {"clientId": "NEWCID", "clientSecret": "LEAK-ME-IF-YOU-CAN"}
this PR Body: {"clientId": "NEWCID", "clientSecret": "***REDACTED***"}

Example B — catalogs create --type external with OAuth (nested clientSecret)

polaris --debug --access-token dummy \
  --base-url http://127.0.0.1:1 \
  catalogs create ext --type external \
    --storage-type s3 --default-base-location s3://bucket/base --role-arn arn:aws:iam::123:role/x \
    --catalog-connection-type iceberg-rest --catalog-uri http://remote-catalog \
    --catalog-authentication-type oauth --catalog-token-uri http://remote/token \
    --catalog-client-id remote-cid --catalog-client-secret OAUTH-SECRET-LEAK \
    --catalog-client-scope PRINCIPAL_ROLE:ALL

Stderr Body: excerpt from the connectionConfigInfo.authenticationParameters block:

Branch Excerpt
main "clientSecret": "OAUTH-SECRET-LEAK"
this PR "clientSecret": "***REDACTED***"

Example C — catalogs create --type external with Bearer auth (bearerToken)

Demonstrates the previously-unknown-to-the-sanitizer bearerToken key.

polaris --debug --access-token dummy \
  --base-url http://127.0.0.1:1 \
  catalogs create ext-bearer --type external \
    --storage-type s3 --default-base-location s3://bucket/base --role-arn arn:aws:iam::123:role/x \
    --catalog-connection-type iceberg-rest --catalog-uri http://remote-catalog \
    --catalog-authentication-type bearer --catalog-bearer-token BEARER-TOKEN-LEAK
Branch Excerpt
main "bearerToken": "BEARER-TOKEN-LEAK"
this PR "bearerToken": "***REDACTED***"

Fix

Two changes in log_sanitizer.py:

  1. SENSITIVE_BODY_KEYS becomes a normalized set (case-folded, _/- stripped): {clientsecret, accesstoken, refreshtoken, bearertoken, token, password, secret}. A new _is_sensitive_key(key) helper normalizes each incoming key before lookup, used by both sanitize_data and _sanitize_form_body. Covers clientSecret, client-secret, CLIENT_SECRET, ClientSecret, and the previous snake_case cases in one rule.
  2. Redact unconditionally on match. Previously, a sensitive key with a dict/list/tuple value was recursed into (defeating the control). No current payload has that shape, but the branch was wrong by design.

clientId, tokenType, and other non-credential lookalikes are deliberately preserved (existing tests assert this).

Checklist

  • 🛡️ Don't disclose security issues! (contact security@apache.org)
  • 🔗 Clearly explained why the changes are needed, or linked related issues: Fixes #
  • 🧪 Added/updated tests with good coverage, or manually tested (and explained how)
  • 💡 Added comments for complex logic
  • 🧾 Updated CHANGELOG.md (if needed)
  • 📚 Updated documentation in site/content/in-dev/unreleased (if needed)

…es camelCase keys on the wire)

"polaris --debug" mirrors HTTP request and response bodies to stderr.
Bodies are supposed to be redacted before writing, which is the stated purpose of log_sanitizer.py

However, the sanitizer redacts only client_secret / access_token / refresh_token (snake_case).
The Polaris management API serializes bodies by alias — camelCase — so clientSecret, bearerToken, etc. flow through unredacted.
Every real management-plane request and response leaks credentials under "--debug".
The snake_case keys only appear in the /oauth/tokens form body, which is already handled by a URL-based special case, so the sanitizer's key list is effectively dead for real traffic.
@MonkeyCanCode

Copy link
Copy Markdown
Contributor

Will take a look tomorrow.

# The Polaris management API serializes bodies by alias (camelCase — e.g.
# ``clientSecret``, ``bearerToken``), while the OAuth token endpoint uses
# snake_case. Matching on the normalized key covers both.
SENSITIVE_BODY_KEYS = frozenset(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would preferred we be more specific on this as all sensitive keys should be known. Also, this doesn't cover case for s3, gcs, and adls as they can be present in the body during debug mode as well. I would change to following instead:

         "client_secret",
         "clientSecret",
         "access_token",
         "accessToken",
         "refresh_token",
         "refreshToken",
         "bearerToken",
         "token",
         "password",
         "secret",
         "s3.secret-access-key",
         "s3.session-token",
         "gcs.oauth2.token",
         "adls.sas-token",



def _is_sensitive_key(key: Any) -> bool:
return (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this, we will need to change to following to captured known keys as well as adls specific where they can put adls.sas-token as prefix (reference: https://github.com/apache/polaris/blob/main/site/content/in-dev/unreleased/configuration/config-sections/storage-azure.md?plain=1):

    return key in SENSITIVE_BODY_KEYS or (
        isinstance(key, str) and key.startswith("adls.sas-token")
     )

@XD-DENG

XD-DENG commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

Hi @MonkeyCanCode , thanks for the review.
Both comments were addressed with a2dd3d7. Please take another look when you get time.

@XD-DENG
XD-DENG requested a review from MonkeyCanCode August 14, 2026 18:18
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.

2 participants