security(line): synthesize strict LINE auth boundary hardening

LINE auth boundary hardening synthesis for inbound webhook authn/z/authz:
- account-scoped pairing-store access
- strict DM/group allowlist boundary separation
- fail-closed webhook auth/runtime behavior
- replay and duplicate handling with in-flight continuity for concurrent redeliveries

Source PRs: #26701, #26683, #25978, #17593, #16619, #31990, #26047, #30584, #18777
Related continuity context: #21955

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>
Co-authored-by: davidahmann <46606159+davidahmann@users.noreply.github.com>
Co-authored-by: harshang03 <58983401+harshang03@users.noreply.github.com>
Co-authored-by: haosenwang1018 <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: coygeek <65363919+coygeek@users.noreply.github.com>
Co-authored-by: lailoo <20536249+lailoo@users.noreply.github.com>
This commit is contained in:
Tak Hoffman
2026-03-03 00:21:15 -06:00
committed by GitHub
parent fe92113472
commit dbccc73d7a
10 changed files with 619 additions and 113 deletions

View File

@@ -71,14 +71,12 @@ export function createLineWebhookMiddleware(
return;
}
res.status(200).json({ status: "ok" });
if (body.events && body.events.length > 0) {
logVerbose(`line: received ${body.events.length} webhook events`);
void onEvents(body).catch((err) => {
runtime?.error?.(danger(`line webhook handler failed: ${String(err)}`));
});
await onEvents(body);
}
res.status(200).json({ status: "ok" });
} catch (err) {
runtime?.error?.(danger(`line webhook error: ${String(err)}`));
if (!res.headersSent) {
@@ -99,9 +97,17 @@ export function startLineWebhook(options: StartLineWebhookOptions): {
path: string;
handler: (req: Request, res: Response, _next: NextFunction) => Promise<void>;
} {
const channelSecret =
typeof options.channelSecret === "string" ? options.channelSecret.trim() : "";
if (!channelSecret) {
throw new Error(
"LINE webhook mode requires a non-empty channel secret. " +
"Set channels.line.channelSecret in your config.",
);
}
const path = options.path ?? "/line/webhook";
const middleware = createLineWebhookMiddleware({
channelSecret: options.channelSecret,
channelSecret,
onEvents: options.onEvents,
runtime: options.runtime,
});