Problem
HITL replies are authorized by channel membership only. Any Slack user who can post in the bound channel, or see the ask message, can answer an ask meant for a specific operator.
- Thread replies:
routeHitlThreadReply (src/ingress/adapters/slack-events.ts:391) resolves the text to a selection and calls applyHitlReply (src/ingress/adapters/slack-events.ts:470) without checking who sent it.
- Button taps:
processSlackInteractive (src/ingress/adapters/slack-events.ts:496) takes the correlation id from action_id and calls applyHitlReply (src/ingress/adapters/slack-events.ts:543), again without a user check.
The first confirmed pick wins. As a v1 default this is reasonable, but a flow cannot restrict who answers.
Proposed fix
Add a per-binding allowlist of responders:
channels:
ingress:
type: slack
channel: C0EXAMPLE
hitl:
allowed_users: [U012ABCDEF, U034GHIJKL] # absent = current behavior
- Enforce it in both reply paths before
applyHitlReply.
- Log an unauthorized attempt as
rejected_unauthorized, alongside the existing rejected_malformed and rejected_ambiguous outcomes, and leave the card held. No card flip and no trigger spawn.
- Validate at load: non-empty strings only, with a typed error.
- When the key is absent, behavior is unchanged.
Acceptance criteria
- A thread reply and a button tap from a user outside
allowed_users both log rejected_unauthorized and leave the card held.
- The same actions from an allowed user resolve the hold as today.
- A binding without
hitl.allowed_users behaves exactly as it does now.
Problem
HITL replies are authorized by channel membership only. Any Slack user who can post in the bound channel, or see the ask message, can answer an ask meant for a specific operator.
routeHitlThreadReply(src/ingress/adapters/slack-events.ts:391) resolves the text to a selection and callsapplyHitlReply(src/ingress/adapters/slack-events.ts:470) without checking who sent it.processSlackInteractive(src/ingress/adapters/slack-events.ts:496) takes the correlation id fromaction_idand callsapplyHitlReply(src/ingress/adapters/slack-events.ts:543), again without a user check.The first confirmed pick wins. As a v1 default this is reasonable, but a flow cannot restrict who answers.
Proposed fix
Add a per-binding allowlist of responders:
applyHitlReply.rejected_unauthorized, alongside the existingrejected_malformedandrejected_ambiguousoutcomes, and leave the card held. No card flip and no trigger spawn.Acceptance criteria
allowed_usersboth logrejected_unauthorizedand leave the card held.hitl.allowed_usersbehaves exactly as it does now.