Commit Graph

4 Commits

Author SHA1 Message Date
Chris Zhang
081e4be11e fix(bluebubbles): address aisle re-review on routing-guard PR
Three findings from the second pass:

1. **MEDIUM — Cross-chat short message ID guard bypassed on empty chat
   context (CWE-285).** When `requireKnownShortId=true` and `chatContext`
   was missing or `{}`, `resolveBlueBubblesMessageId` would still resolve
   the short id. Short ids are allocated from a single global counter
   across every account and chat, so an action call without a chat
   scope could silently apply to the wrong conversation. Throw "requires
   a chat scope" instead. The previous behavior was an explicit
   "fail-open" choice with a comment acknowledging the risk; the
   underlying assumption (downstream call carries chatGuid) does not
   hold for every action handler. Test rewritten to expect fail-closed.

2. **LOW — Unsanitized messageId reflected in cross-chat guard error
   (CWE-117 / CWE-200).** The thrown error embedded the raw inputId
   (and the raw chatGuid / chatIdentifier from the cached entry until
   the previous pass). Replace the inputId with a shape descriptor
   (`<short:N-digit>` or `<uuid:prefix…>`) so cross-chat errors no
   longer leak any concrete identifier. Combined with the chat
   identifier redaction in describeChatForError (already in place),
   the error is fully redacted.

3. **LOW — PII exposure via verbose logs (CWE-532).** Untrusted webhook
   identifiers (senderId / messageId / action) were already passed
   through `sanitizeForLog`, but the helper only stripped control
   characters — it did not redact secrets such as `?password=` query
   strings or `Authorization: Bearer …` headers that occasionally
   bleed into error chains. Extend `sanitizeForLog` to redact those
   patterns. All call sites benefit immediately.
2026-04-28 21:06:49 +01:00
Chris Zhang
81fd4d560a fix(bluebubbles): address aisle review on routing-guard PR
Four findings on this PR, all addressed in this commit:

1. **Cross-chat guard bypass when ctx.chatGuid present but cached lacks chatGuid**
   (CWE-697). Earlier `isCrossChatMismatch` gated chatIdentifier and chatId
   fallback comparisons on `!ctxChatGuid`, which let any non-empty
   ctx.chatGuid suppress the fallback checks when the cached entry happened
   to lack chatGuid — letting a short id from chat A be reused while acting
   in chat B. Rewrite the function so chatIdentifier/chatId comparisons
   run independently based on availability on each side, not on whether
   ctx.chatGuid happens to be present.

2. **Sensitive chat identifiers exposed via thrown cross-chat error**
   (CWE-200). `describeChatForError` interpolated raw chatGuid /
   chatIdentifier / chatId into the error message — these can leak phone
   numbers / email addresses / chat GUIDs into agent transcripts, tool
   results, remote channel deliveries, or third-party log aggregators.
   Surface only the *shape* of the chat target with `=<redacted>` values.

3. **Group reaction drop-guard bypass via whitespace chatIdentifier**.
   Earlier guard treated "" as missing but accepted " " / "\t". Trim
   chatGuid/chatIdentifier before the missing-check so a webhook sender
   supplying whitespace cannot satisfy the guard and have peerId degrade
   to the literal "group".

4. **Log injection via webhook senderId/messageId in verbose log lines**
   (CWE-117). Untrusted webhook fields were interpolated directly into
   `logVerbose` calls without sanitization, allowing log forging if a
   sender carried CR/LF/control bytes. Wrap with the existing
   `sanitizeForLog()` helper at all such sites.

Test updates: monitor-reply-cache.test.ts cross-chat error assertions
now expect `chatGuid=<redacted>` instead of raw values.
2026-04-28 21:06:49 +01:00
Chris Zhang
6ade320421 fix(bluebubbles): apply cross-chat guard to full message GUIDs as well
The cross-chat guard added in the prior commit (resolveBlueBubblesMessageId
with chatContext) only ran on numeric short ids — `if (/^\d+$/.test(trimmed))`.
Full GUID input fell through to `return trimmed` with no chat check.

Once the short-id guard started rejecting cross-chat reuses, agents would
retry the same call with the full GUID copied from history or a previous
tool result. That second attempt bypassed the guard entirely and the
group reaction landed in the DM anyway — exactly the symptom the prior
commit was meant to close.

Apply the same `isCrossChatMismatch` check to full GUID input. Cache miss
still falls through (callers may legitimately supply a fresh-from-the-wire
GUID the cache hasn't observed yet), but cache hits with a chat mismatch
throw with a remediation hint pointed at the chat target rather than at
the id format — telling an agent to "retry with the full GUID" makes no
sense when it already supplied one.

Tests (extensions/bluebubbles/src/monitor-reply-cache.test.ts):
- UUID + same chat → resolves
- UUID + different chat → throws (this is the regression)
- UUID + cache miss → passes through (preserves behavior for fresh GUIDs)
- UUID + empty chatContext → passes through (preserves prior behavior)
- UUID error message hints at the chat target, not the id format
- chatIdentifier fallback applies to UUID input too

Local patch for upstream consideration — completes the cross-chat guard
started in the prior commit so both id forms are protected symmetrically.
2026-04-28 21:06:49 +01:00
Chris Zhang
9f97e8c521 fix(bluebubbles): scope short message id resolution to the caller's chat
BlueBubbles short message ids (numeric aliases like "1", "5" that agents
use instead of full GUIDs to save tokens) are allocated from a single
global counter across every account and every chat. Nothing in
resolveBlueBubblesMessageId verified that the resolved GUID was actually
in the chat the caller was acting on, so any time an agent reused or
mis-remembered a short id — especially common after a long group
conversation — the id could silently point at a different chat entirely.

Symptom Chris observed: reactions/tapbacks and quoted replies authored
inside a group would intermittently land in a DM, targeting an old
message the user could no longer see. Tool call looks successful, chat
archive shows a group reaction appearing in the DM transcript.

Add an optional chatContext parameter to resolveBlueBubblesMessageId
(chatGuid / chatIdentifier / chatId). When provided, look up the
cached reply entry for the resolved GUID and compare. A clear mismatch
(same identifier present on both sides, different values) throws with a
message that lists both chats and points at "use the full GUID", so the
agent fails fast and retries with a disambiguated id. Ambiguous cases
(either side missing all identifiers) pass through to preserve existing
behavior for callers that cannot supply chat hints. The comparison
mirrors resolveReplyContextFromCache so outbound and inbound paths agree
on scope.

Update every call site that resolves a short id for outbound BB traffic
to pass chatContext:
- extensions/bluebubbles/src/actions.ts: react, edit, unsend, reply
  (build context from chat* params, then to/target, then the tool's
  currentChannelId)
- extensions/bluebubbles/src/channel.ts sendText: derive context from
  the `to` target
- extensions/bluebubbles/src/media-send.ts: same
- extensions/bluebubbles/src/monitor-processing.ts deliver path: pass
  the chat already resolved for routing

Add buildBlueBubblesChatContextFromTarget to targets.ts so callers can
project a raw target string (`chat_guid:...`, `chat_id:42`,
`imessage:+1...`, bare handle) into the context shape.

Tests:
- extensions/bluebubbles/src/monitor-reply-cache.test.ts (new, 8 cases):
  same-chat resolves, cross-chatGuid throws, ambiguous passes,
  chatIdentifier fallback, chatId fallback, full GUID input bypasses,
  error message identifies both chats, unknown short id still errors.
- extensions/bluebubbles/src/actions.test.ts: update the react short-id
  assertion to verify chatContext now flows through.

Local patch for upstream consideration — same root cause affects every
BB user; plan is to open a separate upstream PR once this bakes locally.
2026-04-28 21:06:49 +01:00