---
id: GHSA-fq4x-789w-jg5h
title: >-
  AgenticMail: Unauthenticated inbound mail triggers bypassPermissions resume of
  the operator's Claude Code session (bridge-wake)
summary: >-
  AgenticMail: Unauthenticated inbound mail triggers bypassPermissions resume of
  the operator's Claude Code session (bridge-wake)
severity: high
cwe:
  - CWE-306
vendor: agenticmail
product: '@agenticmail/core'
affected:
  - '@agenticmail/core < 0.9.43'
  - '@agenticmail/claudecode < 0.2.39'
  - '@agenticmail/codex < 0.1.33'
  - '@agenticmail/openclaw < 0.5.71'
patched:
  - '@agenticmail/core 0.9.43'
  - '@agenticmail/claudecode 0.2.39'
  - '@agenticmail/codex 0.1.33'
  - '@agenticmail/openclaw 0.5.71'
published: '2026-06-18'
updated: '2026-06-18'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-fq4x-789w-jg5h'
references:
  - url: >-
      https://github.com/agenticmail/agenticmail/security/advisories/GHSA-fq4x-789w-jg5h
  - url: 'https://github.com/advisories/GHSA-fq4x-789w-jg5h'
tags:
  - ghsa
  - npm
ingestedAt: '2026-06-19T03:39:00.793Z'
ecosystem: npm
---

## Overview

## Summary
Two inbound-mail handlers act on a privileged effect without verifying that the sender is the operator, while a sibling handler in the same repo does. The higher-impact one: any external email routed to the bridge inbox causes the dispatcher to resume the operator's Claude Code session with `permissionMode: 'bypassPermissions'`, embedding the attacker-controlled `from`/`subject`/`preview` verbatim into the prompt the resumed agent reads — an indirect prompt injection into a fully-privileged agent (Bash/Write/Edit/WebFetch + the agenticmail MCP toolbelt) running as the operator's OAuth identity. The sibling operator-query email-reply hook gates the same untrusted-From provenance with `isOperatorReplySender(replyFrom, config.operatorEmail)` (fail-closed); the bridge-wake path — a strictly higher-privilege effect — has no equivalent.

## Affected code (current HEAD, commit b95f52e)
Untrusted provenance: external inbound email enters at `packages/api/src/routes/inbound.ts:41` (POST /mail/inbound); the `x-inbound-secret` authenticates only the relay->API hop, not the external sender, so `from`/`subject`/`preview` are attacker-controlled.

Privileged sink (bridge-wake, bypassPermissions):
- `packages/claudecode/src/dispatcher.ts:2040` `handleBridgeMail` extracts `subject`/`from`/`preview` (`:2045-2049`) and calls `planBridgeWake({ session, mail: { ..., from, preview } })` (`:2052`) with NO sender check — routing keys only on session freshness (skip-live / escalate / resume).
- `planBridgeWake` -> `packages/core/src/host-bridge.ts:141` `composeBridgeWakePrompt` embeds the untrusted `from`/`subject`/`preview` (preview sliced to 600 chars at `:144`) verbatim into the prompt.
- `packages/claudecode/src/bridge-wake.ts:103` `resumeBridgeSession` runs the prompt via the Claude Code SDK with `permissionMode: 'bypassPermissions'` against the operator's last session (resume + same mcpServers).

Guarded sibling (same class, authenticated): `packages/api/src/routes/inbound.ts:102` rejects an operator-query email reply unless `isOperatorReplySender(replyFrom, config.operatorEmail)` (def `packages/core/src/phone/realtime-tools.ts:999`, fail-closed when no operatorEmail), with a v0.9.53 security-review comment (`inbound.ts:93-100`) stating inbound mail provenance is untrusted and an emailed answer is only honored when its From matches the configured operator. The Telegram sibling likewise gates on `operatorChatId`. The bridge-wake path ignores this exact lesson.

Secondary instance (same root cause): `packages/core/src/gateway/manager.ts:261` `tryProcessApprovalReply` releases a held outbound email on an "approve" reply matched only by `In-Reply-To` / `notification_message_id`, with no sender check — again unlike the `isOperatorReplySender` sibling.

## Impact
In a configured headless-bridge deployment (operator uses the CLI so a host-session is saved; session fresh <24h per `host-sessions.ts:127`; external mail routed to the bridge inbox via relay sub-addressing or a Cloudflare webhook), an external sender achieves indirect prompt injection into a `bypassPermissions` operator session -> arbitrary OS command execution, filesystem read/write, and exfiltration under the operator's OAuth identity. The auth gap (no sender check on the bridge path) is structural and unconditional; the impact realization is config-conditional and depends on the resumed model following injected instructions.

## Proof of concept (static / request-difference; dynamic on operator's OWN setup only)
Static: the from/subject/preview extracted at dispatcher.ts:2045-2049 flow into composeBridgeWakePrompt (host-bridge.ts:141) and resumeBridgeSession (bridge-wake.ts:103) with no interposed sender check, while the sibling inbound.ts:102 has one — the same untrusted-From provenance is authenticated on one privileged email path and not on the higher-privileged one. Dynamic (own instance only): with a configured bridge + fresh host-session, send mail from a non-operator address into the bridge inbox whose subject/preview contains a benign instruction writing a fresh CSPRNG marker; observe the resumed bypassPermissions session act on it. Use only your own instance; do not target third-party deployments.

## Suggested fix
Mirror the guarded sibling: before any `bypassPermissions` resume (dispatcher.ts handleBridgeMail, before planBridgeWake), require trusted provenance — an internal sub-agent wake OR `isOperatorReplySender(from, config.operatorEmail)`; otherwise deliver the mail normally but do NOT resume. Reuse the existing exported `isOperatorReplySender` from @agenticmail/core so the two privileged email paths share one authentication helper. Defense-in-depth: in composeBridgeWakePrompt, wrap the untrusted fields in explicit untrusted-data delimiters and drop `bypassPermissions` for mail-triggered resumes whose provenance is not the operator. Apply the same sender gate to `tryProcessApprovalReply` (manager.ts:261).

## Affected versions
Present on current HEAD (core 0.9.42 / claudecode 0.2.38, commit b95f52e). No fix retrofits the sender check onto bridge-wake.

## Severity (honest, both ways)
HIGH, plausibly CRITICAL in a configured headless-bridge deployment. Ceiling ~9.0 (unauthenticated external sender -> operator-privileged code execution). Floor ~7.0: the auth gap is unconditional, but full impact requires (1) a fresh <24h host-session, (2) external mail routed to the bridge inbox, (3) the resumed model obeying injected instructions (non-deterministic). Not a deterministic RCE primitive. CWE-306 (missing authentication for the privileged action) + CWE-77/CWE-94 (injected instructions realized as command execution). Novelty: the two existing agenticmail advisories (CVE-2026-50287 MCP missing-auth; CVE-2026-47255 storage SQL) do not cover this sink. Please rate per your deployment assumptions.

## Affected packages

- `@agenticmail/core < 0.9.43`
- `@agenticmail/claudecode < 0.2.39`
- `@agenticmail/codex < 0.1.33`
- `@agenticmail/openclaw < 0.5.71`

## Remediation

Upgrade to a patched release:

- `@agenticmail/core 0.9.43`
- `@agenticmail/claudecode 0.2.39`
- `@agenticmail/codex 0.1.33`
- `@agenticmail/openclaw 0.5.71`
