Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions assistant/src/runtime/routes/channel-inbound-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import * as externalConversationStore from '../../memory/external-conversation-s
import { getPendingConfirmationsByConversation } from '../../memory/runs-store.js';
import { checkIngressForSecrets } from '../../security/secret-ingress.js';
import { IngressBlockedError } from '../../util/errors.js';
import { getConfig } from '../../config/loader.js';
import { getLogger } from '../../util/logger.js';
import { findMember, updateLastSeen } from '../../memory/ingress-member-store.js';
import {
Expand Down Expand Up @@ -186,12 +185,11 @@ export async function handleChannelInbound(
}

// ── Ingress ACL enforcement ──
const inboxConfig = getConfig().assistantInbox;
// Track the resolved member so the escalate branch can reference it after
// recordInbound (where we have a conversationId).
let resolvedMember: ReturnType<typeof findMember> = null;

if (inboxConfig.enabled && inboxConfig.memberAclEnabled && body.senderExternalUserId) {
if (body.senderExternalUserId) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exempt guardian callbacks from member ACL

Applying ingress ACL solely on senderExternalUserId now runs before all guardian handling, so messages from guardians who are only configured via channel_guardian_bindings (and not duplicated in assistant_ingress_members) are rejected as not_a_member and return early. In this state, /guardian_verify ... onboarding and guardian approve/deny callback processing never reach their interceptors later in handleChannelInbound, which can block escalation resolution in normal deployments where guardian identity is managed separately from inbox members.

Useful? React with 👍 / 👎.

resolvedMember = findMember({
sourceChannel,
externalUserId: body.senderExternalUserId,
Comment on lines +192 to 195

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Always-on ACL enforcement silently denies all channel messages when no member records exist

The old condition inboxConfig.enabled && inboxConfig.memberAclEnabled && body.senderExternalUserId required two explicit feature-flag opt-ins before ACL enforcement would run. The new condition body.senderExternalUserId triggers enforcement whenever a sender ID is present.

Root Cause and Impact

The gateway always sends senderExternalUserId for Telegram, SMS, and WhatsApp — it's a required string field in GatewayInboundEventV1.sender (gateway/src/types.ts:27) and is unconditionally forwarded at gateway/src/handlers/handle-inbound.ts:83:

senderExternalUserId: event.sender.externalUserId,

This means every inbound message from these channels will now enter the ACL block. Inside, findMember() is called (assistant/src/memory/ingress-member-store.ts:295), and if no member record exists in the database, the message is immediately denied:

if (!resolvedMember) {
  return Response.json({ accepted: true, denied: true, reason: 'not_a_member' });
}

Member records are only created via explicit admin action through upsertMember in assistant/src/daemon/handlers/config-inbox.ts:212 — there is no auto-provisioning on first message.

Impact: Any existing deployment that uses Telegram/SMS/WhatsApp channels but has not explicitly populated the ingress member store (which was previously unnecessary since the feature was flag-gated) will have all inbound messages silently denied with not_a_member. The response returns HTTP 200 with { accepted: true, denied: true }, so there's no error signal to the gateway or end user — messages simply vanish.

(Refers to lines 192-216)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Expand Down