From 5e05052bb9ab84288f1ea92cb7e3a771979d83b4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 6 May 2026 07:35:10 +0100 Subject: [PATCH] fix(line): require wildcard for open dm policy --- CHANGELOG.md | 1 + docs/channels/line.md | 18 +++++++- extensions/line/src/config-schema.test.ts | 51 +++++++++++++++++++++++ extensions/line/src/config-schema.ts | 36 +++++++++++++--- 4 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 extensions/line/src/config-schema.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3226787f692..f3abfb7a0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Feishu: hydrate missing native topic starter thread IDs before session routing so first turns and follow-ups stay in the same topic session. Fixes #78262. Thanks @joeyzenghuan. +- LINE: reject `dmPolicy: "open"` configs without wildcard `allowFrom` so webhook DMs fail validation instead of being acknowledged and silently blocked before inbound processing. Fixes #78316. - Providers/xAI: stop sending OpenAI-style reasoning effort controls to native Grok Responses models, so `xai/grok-4.3` no longer fails live Docker/Gateway runs with `Invalid reasoning effort`. - Providers/xAI: clamp the bundled xAI thinking profile to `off` so live Gateway runs cannot send unsupported reasoning levels to native Grok Responses models. - Matrix/approvals: retry approval delivery up to 3 times with a short backoff so transient Matrix send failures do not strand pending approval prompts. (#78179) Thanks @Patrick-Erichsen. diff --git a/docs/channels/line.md b/docs/channels/line.md index 64f775394d3..3af01855f7a 100644 --- a/docs/channels/line.md +++ b/docs/channels/line.md @@ -68,6 +68,22 @@ Minimal config: } ``` +Public DM config: + +```json5 +{ + channels: { + line: { + enabled: true, + channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN", + channelSecret: "LINE_CHANNEL_SECRET", + dmPolicy: "open", + allowFrom: ["*"], + }, + }, +} +``` + Env vars (default account only): - `LINE_CHANNEL_ACCESS_TOKEN` @@ -119,7 +135,7 @@ openclaw pairing approve line Allowlists and policies: - `channels.line.dmPolicy`: `pairing | allowlist | open | disabled` -- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs +- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs; `dmPolicy: "open"` requires `["*"]` - `channels.line.groupPolicy`: `allowlist | open | disabled` - `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups - Per-group overrides: `channels.line.groups..allowFrom` diff --git a/extensions/line/src/config-schema.test.ts b/extensions/line/src/config-schema.test.ts new file mode 100644 index 00000000000..945cc7f29ae --- /dev/null +++ b/extensions/line/src/config-schema.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { LineConfigSchema } from "./config-schema.js"; + +describe("LineConfigSchema", () => { + it('rejects dmPolicy="open" without wildcard allowFrom', () => { + const result = LineConfigSchema.safeParse({ + channelAccessToken: "token", + channelSecret: "secret", + dmPolicy: "open", + }); + + expect(result.success).toBe(false); + expect(result.error.issues).toEqual([ + expect.objectContaining({ + path: ["allowFrom"], + message: 'channels.line.dmPolicy="open" requires channels.line.allowFrom to include "*"', + }), + ]); + }); + + it('accepts dmPolicy="open" with wildcard allowFrom', () => { + const result = LineConfigSchema.safeParse({ + channelAccessToken: "token", + channelSecret: "secret", + dmPolicy: "open", + allowFrom: ["*"], + }); + + expect(result.success).toBe(true); + }); + + it('rejects account dmPolicy="open" without wildcard allowFrom', () => { + const result = LineConfigSchema.safeParse({ + accounts: { + work: { + channelAccessToken: "token", + channelSecret: "secret", + dmPolicy: "open", + }, + }, + }); + + expect(result.success).toBe(false); + expect(result.error.issues).toEqual([ + expect.objectContaining({ + path: ["accounts", "work", "allowFrom"], + message: 'channels.line.dmPolicy="open" requires channels.line.allowFrom to include "*"', + }), + ]); + }); +}); diff --git a/extensions/line/src/config-schema.ts b/extensions/line/src/config-schema.ts index 372fd8cd966..4618d62b790 100644 --- a/extensions/line/src/config-schema.ts +++ b/extensions/line/src/config-schema.ts @@ -1,4 +1,8 @@ -import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema"; +import { + buildChannelConfigSchema, + requireOpenAllowFrom, +} from "openclaw/plugin-sdk/channel-config-schema"; +import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared"; import { z } from "openclaw/plugin-sdk/zod"; const DmPolicySchema = z.enum(["open", "allowlist", "pairing", "disabled"]); @@ -15,7 +19,7 @@ const ThreadBindingsSchema = z }) .strict(); -const LineCommonConfigSchema = z.object({ +const LineCommonConfigSchemaBase = z.object({ enabled: z.boolean().optional(), channelAccessToken: z.string().optional(), channelSecret: z.string().optional(), @@ -42,15 +46,35 @@ const LineGroupConfigSchema = z }) .strict(); -const LineAccountConfigSchema = LineCommonConfigSchema.extend({ +const LineAccountConfigSchema = LineCommonConfigSchemaBase.extend({ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional(), -}).strict(); +}) + .strict() + .superRefine((value, ctx) => { + requireChannelOpenAllowFrom({ + channel: "line", + policy: value.dmPolicy, + allowFrom: value.allowFrom, + ctx, + requireOpenAllowFrom, + }); + }); -export const LineConfigSchema = LineCommonConfigSchema.extend({ +export const LineConfigSchema = LineCommonConfigSchemaBase.extend({ accounts: z.record(z.string(), LineAccountConfigSchema.optional()).optional(), defaultAccount: z.string().optional(), groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional(), -}).strict(); +}) + .strict() + .superRefine((value, ctx) => { + requireChannelOpenAllowFrom({ + channel: "line", + policy: value.dmPolicy, + allowFrom: value.allowFrom, + ctx, + requireOpenAllowFrom, + }); + }); export const LineChannelConfigSchema = buildChannelConfigSchema(LineConfigSchema);