mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 12:14:46 +00:00
fix(line): reject lowercased LINE-shaped recipients before push (#81628)
Defense-in-depth safety net for #81628: even with the cron-tool fix in place, any other code path that ever produces a 33-char LINE-shaped recipient missing its leading capital (C/U/R) would otherwise hit the LINE API and return HTTP 400 with no permanent-error signal, causing delivery-recovery to retry five times before moving the entry to failed/. normalizeTarget now throws "Recipient is not a valid LINE id ..." when the post-strip value looks like a LINE id but the case was lost. The message matches the existing /recipient is not a valid/i pattern in delivery-queue-recovery's PERMANENT_ERROR_PATTERNS, so recovery moves the entry to failed/ on the first attempt instead of silently retrying. Short fixtures (length < 33) are left alone so existing tests using "U123", "line:user:1", etc. keep working.
This commit is contained in:
@@ -358,6 +358,33 @@ describe("LINE send helpers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects lowercased LINE-shaped recipients (#81628 safety net)", async () => {
|
||||
// 33-char value with lowercase leading char — what an upstream session-key
|
||||
// fragment looked like before the cron-tool fix. LINE rejects with HTTP 400
|
||||
// anyway; throwing locally keeps the failure permanent so delivery-recovery
|
||||
// moves the entry to failed/ immediately instead of silently retrying 5×.
|
||||
await expect(
|
||||
sendModule.pushMessagesLine(
|
||||
"cabcdef0123456789abcdef0123456789",
|
||||
[{ type: "text", text: "hello" }],
|
||||
{ cfg: LINE_TEST_CFG },
|
||||
),
|
||||
).rejects.toThrow(/Recipient is not a valid LINE id/);
|
||||
expect(pushMessageMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts case-exact LINE recipients with the leading capital preserved", async () => {
|
||||
await sendModule.pushMessagesLine(
|
||||
"Cabcdef0123456789abcdef0123456789",
|
||||
[{ type: "text", text: "hello" }],
|
||||
{ cfg: LINE_TEST_CFG },
|
||||
);
|
||||
expect(pushMessageMock).toHaveBeenCalledWith({
|
||||
to: "Cabcdef0123456789abcdef0123456789",
|
||||
messages: [{ type: "text", text: "hello" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("logs HTTP body when push fails", async () => {
|
||||
const err = new Error("LINE push failed") as Error & {
|
||||
status: number;
|
||||
|
||||
@@ -68,6 +68,18 @@ function normalizeTarget(to: string): string {
|
||||
throw new Error("Recipient is required for LINE sends");
|
||||
}
|
||||
|
||||
// Real LINE chat ids are a capital C/U/R followed by 32 lowercase hex chars
|
||||
// (33 chars total) and are case-sensitive — push returns HTTP 400 otherwise.
|
||||
// Reject values that match the LINE id shape but lost their leading capital
|
||||
// so the failure is surfaced as a permanent error (recovery moves the entry
|
||||
// to failed/ immediately instead of silently retrying 5 times). Short test
|
||||
// fixtures (e.g. "U123") are left alone. openclaw/openclaw#81628
|
||||
if (normalized.length >= 33 && !/^[CUR]/.test(normalized)) {
|
||||
throw new Error(
|
||||
`Recipient is not a valid LINE id (case-sensitive; expected leading capital C/U/R): ${normalized.slice(0, 4)}…`,
|
||||
);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user