fix(authz): normalize phone-number handles for the BlueBubbles allowlist - #82549
Open
withzombies wants to merge 1 commit into
Open
fix(authz): normalize phone-number handles for the BlueBubbles allowlist#82549withzombies wants to merge 1 commit into
withzombies wants to merge 1 commit into
Conversation
BlueBubbles (iMessage) handles are phone numbers or Apple ID email addresses, but _is_user_authorized compared BLUEBUBBLES_ALLOWED_USERS entries against the inbound user_id with raw string equality. An operator naturally writes "+1 (555) 123-0001" while the wire delivers "+15551230001" (or vice versa), so the allowlist silently never matched and every allowlisted sender was denied — pushing operators toward BLUEBUBBLES_ALLOW_ALL_USERS, the opposite of SECURITY.md §2.6's rule that every network-exposed adapter be gated by an allowlist. Add a Platform.BLUEBUBBLES branch to the check_ids block, mirroring the WhatsApp alias-normalization branch: a module-level _normalize_bluebubbles_handle() lowercases emails as-is and reduces phone numbers to digits only, applied to both the allowlist entries (as a union, so exact-match setups keep working) and the inbound user_id. Empty normalized forms are never added, so a digit-free entry cannot cross-match. The "*" wildcard, ALLOW_ALL flag, and empty-allowlist default-deny paths are untouched, and no other platform's comparison changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
BLUEBUBBLES_ALLOWED_USERSnever matched phone-number senders unless the operator's entry was byte-for-byte identical to the wire form of the handle.BlueBubbles (iMessage) handles are phone numbers or Apple ID email addresses.
_is_user_authorized(gateway/authz_mixin.py) compared allowlist entries against the inbounduser_idwith raw string equality — thecheck_idsblock has per-platform normalization branches for WhatsApp (phone↔LID/JID aliases) and SimpleX (display-name alias), but nothing for BlueBubbles. An operator naturally writes+1 (555) 123-0001while the wire delivers+15551230001(or they paste the digits without the+), so the allowlist silently denies every listed sender. The observable failure mode is operators "fixing" it withBLUEBUBBLES_ALLOW_ALL_USERS=true— the exact opposite of SECURITY.md §2.6, rule 2: "An allowlist is required for every enabled network-exposed adapter. … Code paths that fail open when no allowlist is configured are code bugs." An allowlist that can't match its own operator's entries pushes deployments into that fail-open posture.The fix adds a
Platform.BLUEBUBBLESbranch to thecheck_idsblock, structurally mirroring the WhatsApp branch:_normalize_bluebubbles_handle(value)(placed with the other normalization helpers): strip/lowercase; entries containing@are treated as emails and kept as-is (lowercased); everything else is reduced to digits only; empty input yields"".user_idis added tocheck_ids."" == "".The
*wildcard,BLUEBUBBLES_ALLOW_ALL_USERS, and the empty-allowlist default-deny paths are untouched (they resolve before this branch), and no other platform's comparison changes — the branch is gated onsource.platform == Platform.BLUEBUBBLES.Related Issue
No existing issue found (searches below). Happy to open one first if preferred.
Fixes # (none — see duplicate search)
Type of Change
(Arguably security-adjacent: it removes the incentive to set
BLUEBUBBLES_ALLOW_ALL_USERS, but the change itself is a matching bug fix.)Changes Made
gateway/authz_mixin.py_normalize_bluebubbles_handle()next to the imported WhatsApp normalizers: emails lowercased as-is, phone numbers reduced to digits only,""for empty input.Platform.BLUEBUBBLESbranch in_is_user_authorized'scheck_idsblock (between the WhatsApp and SimpleX branches): unions normalized allowlist entries intoallowed_idsand adds the normalizeduser_idtocheck_ids, both guarded against empty normalized forms.tests/gateway/test_bluebubbles_authz.py(new)+-prefixed wire form,GATEWAY_ALLOWED_USERSentries for BlueBubbles, case-insensitive email matching both directions, unlisted phone/email still denied, no-allowlist default-deny, digit-free entries not cross-matching via"",*wildcard,BLUEBUBBLES_ALLOW_ALL_USERS, and non-regression on other platforms (a formatted Telegram entry does NOT digit-match a numeric id; exact Telegram ids still match).How to Test
pytest tests/gateway/test_bluebubbles_authz.py -q→ 15 passed.pytest tests/gateway/ -q -k "auth"→ 234 passed, 4 skipped, 1 xfailed (no regressions in the existing authorization suite).main: setBLUEBUBBLES_ALLOWED_USERS="+1 (555) 123-0001", send an iMessage from that number (wire handle+15551230001) → gateway logs an unauthorized-user denial. On this branch the same setup authorizes; an unlisted number is still denied, and unsetting the allowlist still default-denies.python -c "from gateway.authz_mixin import _normalize_bluebubbles_handle as n; print(n('+1 (555) 123-0001'), n('Contact@Example.COM'), repr(n(' ')))"→15551230001 contact@example.com ''.Duplicate search
Run 2026-08-09 with
gh search prs/issues --repo NousResearch/hermes-agent(open + closed):"bluebubbles allowlist"(PRs): 1 hit — fix(gateway): align BlueBubbles allowlist handling and repo hygiene #13620 "fix(gateway): align BlueBubbles allowlist handling and repo hygiene", closed unmerged; it added adapter-side allowlist enforcement plus unrelated repo hygiene, and did not address handle-format normalization in the gateway authz layer. Not a duplicate."bluebubbles allowlist"(issues): no results."allowed users normalize"(PRs): no results."allowed users normalize"(issues): no results."bluebubbles"PR sweep: send-path/webhook/attachment/tapback PRs only (e.g. fix(bluebubbles): raise send_file timeout to cover slow uploads (#77918) #77940, fix(bluebubbles): use UTC epoch for tempGuid instead of naive local time #79603, fix(bluebubbles): use 127.0.0.1 in webhook URL + migrate stale localhost registrations #69593, feat(bluebubbles): optional exact allowed_chat_guids ingress gate #63990) — none touch allowlist matching semantics.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (new file +-k "auth"slice run directly; see How to Test)Documentation & Housekeeping
docs/, docstrings) — docstrings on the new helper and branch; no user-facing env-var semantics doc changes needed (the var behaves as documented, it just matches now)cli-config.yaml.exampleif I added/changed config keys — N/A (no new keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AScreenshots / Logs
🤖 Generated with Claude Code