fix(line): keep group allowlists scoped (#106056)

Prevent LINE group allowlist policy from inheriting DM allowFrom entries, so open DMs do not broaden group access. Keep group-specific allowlists and per-group overrides as the only group admission sources, and document the scoped behavior.

Co-authored-by: pgondhi987 <pgondhi987@users.noreply.github.com>
This commit is contained in:
Pavan Kumar Gondhi
2026-07-13 13:48:31 +05:30
committed by GitHub
parent 7ff41d8271
commit ffff72c43a
3 changed files with 28 additions and 7 deletions
+3 -2
View File
@@ -140,8 +140,9 @@ Allowlists and policies:
- `channels.line.dmPolicy`: `pairing | allowlist | open | disabled` (default `pairing`)
- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs; `dmPolicy: "open"` requires `["*"]`
- `channels.line.groupPolicy`: `allowlist | open | disabled` (default `allowlist`)
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom` (plus `enabled`, `requireMention`, `systemPrompt`, `skills`)
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups; DM `allowFrom` entries do not admit group senders
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom` (plus `enabled`, `requireMention`, `systemPrompt`, `skills`). With
`groupPolicy: "allowlist"`, set `groupAllowFrom` or the per-group `allowFrom`; an empty group allowlist blocks group messages even when DMs are open.
- Static sender access groups can be referenced from `allowFrom`, `groupAllowFrom`, and per-group `allowFrom` with `accessGroup:<name>`; see [Access groups](/channels/access-groups).
- Runtime note: if `channels.line` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
+22
View File
@@ -524,6 +524,28 @@ describe("handleLineWebhookEvents", () => {
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
});
it("does not use the DM allowlist when group allowlist policy has no group entries", async () => {
const processMessage = vi.fn();
await expectGroupMessageBlocked({
processMessage,
event: createReplayMessageEvent({
messageId: "m5c",
groupId: "group-1",
userId: "user-open-dm",
webhookEventId: "evt-5c",
isRedelivery: false,
}),
context: createLineWebhookTestContext({
processMessage,
dmPolicy: "open",
allowFrom: ["*"],
groupPolicy: "allowlist",
requireMention: false,
}),
});
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
});
it("blocks group messages without sender id when groupPolicy is allowlist", async () => {
const processMessage = vi.fn();
const event = {
+3 -5
View File
@@ -253,12 +253,10 @@ async function shouldProcessLineEvent(
: groupConfig?.allowFrom !== undefined
? "allowlist"
: runtimeGroupPolicy;
// LINE group allowlists are scoped separately from DM allowFrom.
// The shared ingress policy below intentionally keeps fallback disabled.
const groupAllowFrom = normalizeStringEntries(
firstDefined(
groupConfig?.allowFrom,
account.config.groupAllowFrom,
account.config.allowFrom?.length ? account.config.allowFrom : undefined,
),
firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom),
);
const mentionFacts = (() => {
if (!isGroup || event.type !== "message") {