Skip to content

feat(bluebubbles): optional exact allowed_chat_guids ingress gate - #63990

Open
sene1337 wants to merge 2 commits into
NousResearch:mainfrom
sene1337:feat/bluebubbles-chat-guid-allowlist
Open

feat(bluebubbles): optional exact allowed_chat_guids ingress gate#63990
sene1337 wants to merge 2 commits into
NousResearch:mainfrom
sene1337:feat/bluebubbles-chat-guid-allowlist

Conversation

@sene1337

Copy link
Copy Markdown

Summary

BlueBubbles currently can authorize senders, but it cannot optionally scope exact conversations. That means an allowed participant in an off-scope group or DM can still reach the adapter path.

This PR adds an optional positive exact chat-GUID allowlist that runs after chat GUID resolution and before side effects:

  • attachment fetch
  • read receipts
  • handle_message / session / model / tools / outbound

Related prior art (not equivalent): #33489 / #33541 (BLUEBUBBLES_ALLOWED_CHATS / IGNORE_GROUP_CHATS). That design is fail-open when the allowlist is empty/unset and does not provide the three-state exact-GUID ingress contract below.

Config

  • YAML: platforms.bluebubbles.extra.allowed_chat_guids
  • Env: BLUEBUBBLES_ALLOWED_CHAT_GUIDS (comma-separated or JSON list)

Semantics:

State Behavior
Setting absent Stock behavior (backward compatible; no chat-GUID gate)
Explicitly empty Deny all chats (fail closed)
Non-empty list Exact match only for DM + group chat GUIDs

Participant allowlist (BLUEBUBBLES_ALLOWED_USERS / pairing) and require_mention remain independent layers and continue to apply after chat-GUID admission.

Why not just #33541?

#33541 is useful for group filtering, but differs in material ways:

  1. Empty/unset BLUEBUBBLES_ALLOWED_CHATS fails open (if not allowed_chats: return True)
  2. Gate is later in the webhook path than this PR’s pre-attachment boundary
  3. Different key names / no three-state “present even if empty” contract
  4. Does not express exact fail-closed empty allowlist as a first-class security posture

This PR is intentionally narrower: exact GUID admission only, stock-absent + fail-closed-empty, early denial.

Tests

PYTHONPATH=. python -m pytest tests/gateway/test_bluebubbles.py -q
# 73 passed (includes TestBlueBubblesChatGuidAllowlist)

Rebased cleanly onto current main before opening.

Docs

  • website/docs/user-guide/messaging/bluebubbles.md
  • website/docs/reference/environment-variables.md
  • hermes_cli/config.py env catalog entry

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have labels Jul 13, 2026
Add a positive exact chat-GUID allowlist for BlueBubbles so only
explicitly listed DM/group chats enter the agent path. Setting absent
preserves stock behavior; explicitly empty fails closed. Gate runs after
chat GUID resolution and before attachment download, handle_message,
session/model/tools, and read receipts. Participant allowlisting and
require_mention remain independent layers.
@sene1337
sene1337 force-pushed the feat/bluebubbles-chat-guid-allowlist branch from 59ee919 to e718019 Compare July 13, 2026 21:44
@darbsllim

Copy link
Copy Markdown

Trying to add my Hermes agent to group chats with people on my team and vendors who only use iMessage, to have the agent monitor chats - and this is what a council of Sol + Fable + Grok 4.5 suggests.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for adding a narrowly scoped early ingress boundary; current main does not provide an exact BlueBubbles chat-GUID gate before attachment handling (gateway/platforms/bluebubbles.py:934-1006).

Problems

  • The stock-absent path changes existing fallback behavior. Current main accepts payload.guid unconditionally as the last chat-GUID candidate (gateway/platforms/bluebubbles.py:970-975) and uses it as session_chat_id (:1006). Commit e718019d0d7e accepts that fallback only when it contains ;. A legacy payload with only a non-semicolon top-level guid will therefore get a different chat/session identity when the new setting is absent, contrary to the documented compatibility contract.

Suggested changes

  • Preserve the existing payload.guid resolution after an absent-setting path, while retaining strict GUID admission whenever allowed_chat_guids is configured.
  • Add a webhook-flow regression test for that legacy fallback, covering both absent-setting preservation and configured-list fail-closed behavior.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
@sene1337

Copy link
Copy Markdown
Author

Maintainer context from a real deployment

We are carrying this patch in our live Hermes checkout because stock BlueBubbles cannot express a security boundary required for disclosed, observe-only iMessage groups.

Use case

Hermes is intentionally present in a small allowlist of team/vendor iMessage groups. In those approved rooms it should be able to observe ordinary conversation, capture actionable follow-ups internally, and remain silent unless someone explicitly invokes the agent.

The same authorized people also participate in other iMessage chats that must not enter the agent at all. That makes the chat itself—not only the sender—a required admission boundary.

Why the existing controls are not equivalent

  • BLUEBUBBLES_ALLOWED_USERS authorizes a person. It does not restrict that person to one approved chat.
  • require_mention: true prevents ambient observation entirely. require_mention: false sends every authorized sender's group message into the agent path.
  • channel_overrides selects a model, provider, and system prompt after chat resolution. A missing override is not a deny rule.
  • The shared group_allowed_chats authorization fallback is too late for this requirement: the BlueBubbles adapter has already parsed the event and downloaded inbound attachments before shared gateway authorization runs. Sender/pairing authorization can also independently authorize the same event.
  • Hermes registers a global BlueBubbles webhook for new-message and updated-message; the webhook registration itself is not scoped to selected chat GUIDs.

Without this gate, a teammate who is authorized for one monitored group can post in a different group and cause that private chat's message or attachment to enter Hermes processing. A later NO_REPLY from the model prevents a visible response, but it does not undo the privacy boundary crossing.

Required semantics

The deployment needs these semantics at the BlueBubbles ingress boundary:

  1. Setting absent: preserve stock behavior exactly.
  2. Explicitly empty allowlist: deny all chats.
  3. Non-empty allowlist: exact chat-GUID matches only—no wildcard, substring, or participant-derived fallback.
  4. Missing or unresolved chat GUID while configured: fail closed.
  5. Denial occurs before attachment download, handle_message, session creation, model/tools, read receipts, or outbound delivery.
  6. Sender allowlisting/pairing remains a separate required authorization layer.
  7. Group mention policy runs only after chat admission.

Why carrying this locally is painful

Hermes main moves very quickly. Since our last carry-aware update, upstream has advanced by roughly 2,800 commits. We cannot safely use the normal update path because dropping this one commit would silently remove a live privacy boundary.

Every update therefore requires us to:

  1. snapshot the installed revision and profile configuration;
  2. preserve and rebase/cherry-pick the carry;
  3. inspect overlap across the adapter, config loader, tests, and docs;
  4. run the BlueBubbles suite in a hermetic environment so live dotenv policy does not contaminate stock-behavior tests;
  5. reload every running gateway that imports the shared checkout; and
  6. re-verify the approved-chat deny fixture, attachment boundary, webhook cardinality, sender boundary, and live delivery behavior.

That turns routine Hermes upgrades into a security-sensitive fleet migration. We would strongly prefer to run stock Hermes and retire the local carry.

Automated review feedback

We agree with the sweeper finding: when the setting is absent, the implementation should preserve the legacy top-level payload.guid fallback exactly. The strict resolution/fail-closed behavior should apply only when allowed_chat_guids is configured. We can update the PR with that compatibility fix and the requested webhook-flow regression test.

Request

Could a maintainer confirm whether this narrow adapter-level shape and allowed_chat_guids naming are acceptable? If you prefer a generic pre-side-effect chat-admission abstraction shared by platform adapters, we can adapt the implementation, but the BlueBubbles check must still happen before attachments and agent dispatch and must use exact chat GUIDs.

Longer term, BlueBubbles would also benefit from an observer mode similar to Telegram's observe_unmentioned_group_messages, so ambient observation and conversational response are separate paths. That is a distinct feature; exact chat admission is the prerequisite that lets either model operate safely.

We would appreciate prioritizing this because the missing boundary is currently preventing clean, low-risk Hermes updates for a real deployment.

@alt-glitch alt-glitch added the needs-decision Awaiting maintainer decision before any implementation label Jul 27, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #33489 and #33541, but not a duplicate: this patch has an explicit absent-versus-empty fail-closed contract and applies exact GUID admission before attachment handling. Maintainer decision needed on this BlueBubbles-specific contract versus a shared ingress abstraction.

@sene1337

Copy link
Copy Markdown
Author

Follow-up: the compatibility issue is fixed in ae58f7094.

The resolver now has two explicit modes:

  • setting absent: preserves stock behavior, including the legacy top-level payload.guid fallback;
  • setting configured: uses strict chat-GUID resolution and rejects ambiguous non-chat top-level GUIDs fail-closed.

I added webhook regressions for both paths:

  1. absent setting preserves the stock non-semicolon top-level fallback;
  2. configured allowlist rejects that ambiguous top-level value before dispatch.

The focused BlueBubbles suite passes on the corrected carry stack: 74 passed using the declared dev + messaging dependencies with an empty HERMES_HOME.

We also carried the exact reviewed patches through a current-upstream deployment rehearsal: stable patch IDs match e718019d0 and ae58f7094 exactly. Ready for re-review.

@sene1337

Copy link
Copy Markdown
Author

Related follow-up opened: #88550 (feat(gateway): per-channel enabled_toolsets on channel_overrides).

Complementary layers for multi-mode BlueBubbles / messaging:

Neither replaces the other.

@alt-glitch alt-glitch removed the needs-decision Awaiting maintainer decision before any implementation label Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants