From beb77229c0518434de661e2d0305be099b74f672 Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sun, 15 Feb 2026 19:25:11 -0800 Subject: [PATCH] fix (security/line): fail closed when webhook auth is missing --- extensions/line/src/channel.ts | 17 ++++++++++++++--- src/line/monitor.ts | 15 ++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/extensions/line/src/channel.ts b/extensions/line/src/channel.ts index 96c0a51d795..d5ca3ca1855 100644 --- a/extensions/line/src/channel.ts +++ b/extensions/line/src/channel.ts @@ -119,12 +119,13 @@ export const linePlugin: ChannelPlugin = { }, }; }, - isConfigured: (account) => Boolean(account.channelAccessToken?.trim()), + isConfigured: (account) => + Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()), describeAccount: (account) => ({ accountId: account.accountId, name: account.name, enabled: account.enabled, - configured: Boolean(account.channelAccessToken?.trim()), + configured: Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()), tokenSource: account.tokenSource ?? undefined, }), resolveAllowFrom: ({ cfg, accountId }) => @@ -603,7 +604,7 @@ export const linePlugin: ChannelPlugin = { probeAccount: async ({ account, timeoutMs }) => getLineRuntime().channel.line.probeLineBot(account.channelAccessToken, timeoutMs), buildAccountSnapshot: ({ account, runtime, probe }) => { - const configured = Boolean(account.channelAccessToken?.trim()); + const configured = Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()); return { accountId: account.accountId, name: account.name, @@ -626,6 +627,16 @@ export const linePlugin: ChannelPlugin = { const account = ctx.account; const token = account.channelAccessToken.trim(); const secret = account.channelSecret.trim(); + if (!token) { + throw new Error( + `LINE webhook mode requires a non-empty channel access token for account "${account.accountId}".`, + ); + } + if (!secret) { + throw new Error( + `LINE webhook mode requires a non-empty channel secret for account "${account.accountId}".`, + ); + } let lineBotLabel = ""; try { diff --git a/src/line/monitor.ts b/src/line/monitor.ts index c09b471951c..24572a91287 100644 --- a/src/line/monitor.ts +++ b/src/line/monitor.ts @@ -129,6 +129,15 @@ export async function monitorLineProvider( webhookPath, } = opts; const resolvedAccountId = accountId ?? "default"; + const token = channelAccessToken.trim(); + const secret = channelSecret.trim(); + + if (!token) { + throw new Error("LINE webhook mode requires a non-empty channel access token."); + } + if (!secret) { + throw new Error("LINE webhook mode requires a non-empty channel secret."); + } // Record starting state recordChannelRuntimeState({ @@ -142,8 +151,8 @@ export async function monitorLineProvider( // Create the bot const bot = createLineBot({ - channelAccessToken, - channelSecret, + channelAccessToken: token, + channelSecret: secret, accountId, runtime, config, @@ -281,7 +290,7 @@ export async function monitorLineProvider( pluginId: "line", accountId: resolvedAccountId, log: (msg) => logVerbose(msg), - handler: createLineNodeWebhookHandler({ channelSecret, bot, runtime }), + handler: createLineNodeWebhookHandler({ channelSecret: secret, bot, runtime }), }); logVerbose(`line: registered webhook handler at ${normalizedPath}`);