Skip to content

fix(bluebubbles): redact the password embedded in API URLs from error output - #65705

Open
Frowtek wants to merge 2 commits into
NousResearch:mainfrom
Frowtek:fix/bluebubbles-password-url-leak
Open

fix(bluebubbles): redact the password embedded in API URLs from error output#65705
Frowtek wants to merge 2 commits into
NousResearch:mainfrom
Frowtek:fix/bluebubbles-password-url-leak

Conversation

@Frowtek

@Frowtek Frowtek commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

BlueBubbles authenticates by putting ?password=<pw> in the request URL — its REST/webhook API has no header-based auth path (see _api_url). httpx raises HTTPStatusError / RequestError whose str() includes that URL, so every path that logged or returned the raw exception leaked the password on any BlueBubbles API failure (server offline, wrong-password 401, chat-not-found 404, timeout):

  • the three send paths that returned SendResult(error=str(exc)),
  • the connect() "cannot reach server" log,
  • the _register_webhook / _unregister_webhook failure logs (the status logs already used the redacted _webhook_register_url_for_log, but the exception logs interpolated the raw error).

The global secret redactor does not catch this: it intentionally passes URL query strings through unchanged (#34029, to preserve OAuth callbacks / magic links / presigned URLs), and the webhook-access-log guard added in #31690 only matches METHOD /path?... HTTP/1.1 request-target lines. Neither masks the for url '...?password=...' shape an httpx error produces. Verified end-to-end: a failed _api_post returns SendResult.error containing the password, and it survives redact_sensitive_text(..., force=True).

Same class as Telegram's token-in-URL leak (#58594). Add an adapter-local _redact_bb_error_text (mirroring _redact_telegram_error_text) and route every BlueBubbles error/log site through it — without touching the global "web URLs pass through" behavior.

Related Issue

Fixes # — no existing issue. Parity sibling of the merged Telegram transport-error redaction (#58594); complements #31690 (inbound webhook access-log / registration URL) with the outbound send/connect/register error paths it did not cover.

Type of Change

  • 🔒 Security fix

Changes Made

  • gateway/platforms/bluebubbles.py:
    • Add _redact_bb_error_text(error) + _BB_URL_PASSWORD_RE — mask a ?password=/&password= value (raw or URL-encoded) in any error string.
    • Route all error/log sites through it: the 3 SendResult(error=str(exc/e)) send failures, the connect() unreachable log, and the _register_webhook / _unregister_webhook failure logs.
  • tests/gateway/test_bluebubbles.py:
    • TestBlueBubblesPasswordRedaction (4 tests) + TestBlueBubblesWebhookErrorRedaction (1 test).

How to Test

  1. pytest tests/gateway/test_bluebubbles.py -q → 65 passed (60 existing + 5 new).
  2. New coverage:
    • the redactor masks ?password= (raw, URL-encoded, and &password= forms) while leaving non-secret query params (guid=x) and benign errors intact;
    • a failed send() (mocked _api_post raising an HTTPStatusError built from the real _api_url) returns a SendResult whose .error has no password;
    • a failed _register_webhook no longer writes the password into its log (asserted via caplog).
  3. Re-run any of these against the pre-fix code (return/log str(exc) verbatim) to confirm the password leaks there.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes)
  • I've tested on my platform

Documentation & Housekeeping

  • I've updated relevant documentation (docstrings/comments) — the helper carries an inline rationale — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact per the compatibility guide — pure string masking, no OS-specific behavior
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Security

Closes a credential leak: the BlueBubbles auth password, embedded in the API request URL, reached SendResult.error and logs on any API failure. Masked at the adapter boundary (mirroring the existing Telegram token-in-URL redaction); the global URL-passthrough behavior other features rely on is unchanged.

Screenshots / Logs

Before (send failure, current code):

SendResult.error = Client error '401 Unauthorized' for url
  'http://mac.local:1234/api/v1/message/text?password=Sup3rS3cr3tPw'

After (this branch):

SendResult.error = Client error '401 Unauthorized' for url
  'http://mac.local:1234/api/v1/message/text?password=***'

… output

BlueBubbles authenticates by putting ?password=<pw> in the request URL
(its REST/webhook API has no header-based auth path — see _api_url). httpx
raises HTTPStatusError/RequestError whose str() includes that URL, so the
send paths that returned SendResult(error=str(exc)) — and the connect()
'cannot reach server' log — leaked the password on any BlueBubbles API
failure (server offline, wrong-password 401, chat-not-found 404, timeout).

The global secret redactor does not catch this: it intentionally passes
URL query strings through unchanged (NousResearch#34029, to preserve OAuth callbacks /
magic links / presigned URLs), and its webhook-access-log guard only
matches 'METHOD /path?...' request-target lines — neither masks the
"for url '...?password=...'" shape an httpx error produces. This is the
same class as telegram's token-in-URL leak (NousResearch#58594): mask it at the
adapter boundary with _redact_bb_error_text, mirroring
_redact_telegram_error_text, and route the send-result + connect error
sites through it.

Adds regression tests: the redactor masks the password (raw, url-encoded,
and &password= forms) while leaving non-secret query params and benign
errors intact, and a failed send() no longer returns the password in
SendResult.error.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 16, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #31690 and #58594: this covers the distinct BlueBubbles outbound httpx-error-string surface, where ?password= can reach SendResult errors or connection logs.

…error logs too

Two remaining raw-exception sites in the same leak class: _register_webhook
and _unregister_webhook log the httpx error verbatim on failure
('failed to register/unregister webhook: %s' % exc). The exception comes
from _api_post / client.delete on _api_url(...), which carries
?password=<pw>, so raise_for_status()'s HTTPStatusError.str() leaks the
password into those logs — the status logs already use the redacted
_webhook_register_url_for_log, but the exception logs did not.

Route both through _redact_bb_error_text, completing the redaction across
every BlueBubbles error/log site (mirrors telegram's 'all remaining raw
exception sites' pass). Adds a caplog regression test asserting the
register-failure log no longer contains the password.
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for addressing the BlueBubbles error-string leak. The main premise is verified on current main: gateway/platforms/bluebubbles.py:165 places the password in the API URL, and several raw exception sinks can expose httpx's URL-bearing error text.

Problems

  • gateway/platforms/bluebubbles.py:800 builds another authenticated API URL for attachment download; after raise_for_status() at :804, its exception handler logs raw exc at :844. This remains a password leak on attachment-download failures, but the PR does not redact that sink.

Suggested changes

  • Apply _redact_bb_error_text(exc) to the warning at gateway/platforms/bluebubbles.py:844 and add a regression test for that log path.

Automated hermes-sweeper review.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
@egilewski

Copy link
Copy Markdown
Contributor

suggesting changes

  • gateway/platforms/bluebubbles.py:818 still passes the password-bearing _api_url(...) to the attachment download, but the exception handler at lines 858-863 logs exc without _redact_bb_error_text. A simulated 401 on that request leaves the BlueBubbles password in the warning text even though this PR masks the other URL-bearing error sinks. Please redact this exception with the same helper and add a regression test for the attachment-download failure log.

Security evidence:

  • trust boundary: the BlueBubbles password is embedded in outbound REST URLs, while adapter warnings are operator-visible output.
  • source/sink/invariant: _api_url() is the credential source and the attachment warning is the sink; every URL-bearing httpx exception exposed by this adapter must pass _redact_bb_error_text().
  • current-main reproduction: a simulated attachment-download 401 includes the password-bearing request URL in the warning.
  • PR-head or patch-replay validation: the five added redaction tests pass and the helper masks the same 401 exception directly, but the attachment warning still contains the password at the exact PR head and on the current-main production replay.
  • positive/negative cases: direct helper use removes the password from a real raise_for_status() exception; routing that exception through _download_attachment() leaves it present in the captured warning.
  • residual bypass search: all _api_url(), raise_for_status(), raw exception return, and logger sites were checked; ignored typing/read-receipt exceptions do not emit output, while the attachment warning is the remaining credential-bearing sink.
  • reviewer validation: the exact PR-head tests, current-main BlueBubbles tests, and isolated source-bound probes confirmed the result.

Not checked:

  • CodeRabbit review

Signed: GPT-5.6-sol-xhigh in Codex

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants