mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 01:53:54 +00:00
fix(slack): guard relay frame parsing
* fix(slack): guard relay WebSocket frame JSON.parse against malformed input Slack Socket Mode relay receives WebSocket frames from external infrastructure. parseRelayFrame() used bare JSON.parse() which would throw raw SyntaxError on malformed frames, potentially crashing the relay connection. Wrap JSON.parse in try/catch and throw SlackRelayMalformedFrameError with the original SyntaxError as cause, so callers can distinguish transport errors from parse errors. D4 dimension — all competitors have zero coverage. Signed-off-by: lsr911 <liao.shirong@xydigit.com> * fix(slack): repair relay frame guard tests --------- Signed-off-by: lsr911 <liao.shirong@xydigit.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -6,6 +6,8 @@ import {
|
||||
buildRelayWebSocketOptions,
|
||||
buildRelayWebSocketUrl,
|
||||
monitorSlackRelaySource,
|
||||
parseRelayFrame,
|
||||
SlackRelayMalformedFrameError,
|
||||
SLACK_RELAY_MAX_PAYLOAD_BYTES,
|
||||
type SlackRelayIdentity,
|
||||
} from "./relay-source.js";
|
||||
@@ -20,6 +22,10 @@ function deferred<T>() {
|
||||
return { promise, reject, resolve };
|
||||
}
|
||||
|
||||
function relayFrame(text: string): Buffer {
|
||||
return Buffer.from(text, "utf8");
|
||||
}
|
||||
|
||||
describe("Slack relay source", () => {
|
||||
it("builds authenticated relay websocket URLs safely", () => {
|
||||
expect(
|
||||
@@ -198,4 +204,39 @@ describe("Slack relay source", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseRelayFrame", () => {
|
||||
it("parses valid JSON frames", () => {
|
||||
const frame = parseRelayFrame(
|
||||
relayFrame(JSON.stringify({ type: "slack_event", data: { text: "hello" } })),
|
||||
);
|
||||
expect(frame).toEqual({ type: "slack_event", data: { text: "hello" } });
|
||||
});
|
||||
|
||||
it("throws SlackRelayMalformedFrameError for malformed JSON", () => {
|
||||
expect(() => parseRelayFrame(relayFrame("NOT JSON {{{"))).toThrow(
|
||||
SlackRelayMalformedFrameError,
|
||||
);
|
||||
});
|
||||
|
||||
it("wraps the original SyntaxError as the cause", () => {
|
||||
let error: unknown;
|
||||
try {
|
||||
parseRelayFrame(relayFrame("NOT JSON {{{"));
|
||||
} catch (err: unknown) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toBeInstanceOf(SlackRelayMalformedFrameError);
|
||||
expect((error as SlackRelayMalformedFrameError).message).toContain("malformed JSON frame");
|
||||
expect((error as SlackRelayMalformedFrameError).cause).toBeDefined();
|
||||
});
|
||||
|
||||
it("parses empty object frames", () => {
|
||||
expect(parseRelayFrame(relayFrame("{}"))).toEqual({});
|
||||
});
|
||||
|
||||
it("parses array frames", () => {
|
||||
expect(parseRelayFrame(relayFrame("[1, 2, 3]"))).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -293,9 +293,20 @@ function isLocalRelayHost(hostname: string): boolean {
|
||||
return isIP(host) === 4 && host.startsWith("127.");
|
||||
}
|
||||
|
||||
function parseRelayFrame(data: RawData): unknown {
|
||||
export class SlackRelayMalformedFrameError extends Error {
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
this.name = "SlackRelayMalformedFrameError";
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRelayFrame(data: RawData): unknown {
|
||||
const text = rawDataToString(data);
|
||||
return JSON.parse(text) as unknown;
|
||||
try {
|
||||
return JSON.parse(text) as unknown;
|
||||
} catch (cause) {
|
||||
throw new SlackRelayMalformedFrameError("Slack relay received malformed JSON frame", { cause });
|
||||
}
|
||||
}
|
||||
|
||||
function rawDataToString(data: RawData): string {
|
||||
|
||||
Reference in New Issue
Block a user