From ddc5adc91ccd8026c7250619febc564953b4481a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:40:40 -0700 Subject: [PATCH] Fix: handle literal null from crowdsec api alerts (#7088) --- src/widgets/crowdsec/component.jsx | 2 +- src/widgets/crowdsec/component.test.jsx | 11 +++++++++++ src/widgets/crowdsec/proxy.js | 5 +++++ src/widgets/crowdsec/proxy.test.js | 25 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/widgets/crowdsec/component.jsx b/src/widgets/crowdsec/component.jsx index 7f84f8a93..533f933e2 100644 --- a/src/widgets/crowdsec/component.jsx +++ b/src/widgets/crowdsec/component.jsx @@ -21,7 +21,7 @@ export default function Component({ service }) { return ; } - if (!alerts && !bans) { + if (alerts === undefined && bans === undefined) { return ( diff --git a/src/widgets/crowdsec/component.test.jsx b/src/widgets/crowdsec/component.test.jsx index 9802eaed4..f6d81a987 100644 --- a/src/widgets/crowdsec/component.test.jsx +++ b/src/widgets/crowdsec/component.test.jsx @@ -86,4 +86,15 @@ describe("widgets/crowdsec/component", () => { expectBlockValue(container, "crowdsec.alerts", 499); }); + + it("renders null responses as 0 counts", () => { + useWidgetAPI.mockImplementation(() => ({ data: null, error: undefined })); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expectBlockValue(container, "crowdsec.alerts", 0); + expectBlockValue(container, "crowdsec.bans", 0); + }); }); diff --git a/src/widgets/crowdsec/proxy.js b/src/widgets/crowdsec/proxy.js index 9d41e6b2a..43980d6f2 100644 --- a/src/widgets/crowdsec/proxy.js +++ b/src/widgets/crowdsec/proxy.js @@ -102,6 +102,11 @@ export default async function crowdsecProxyHandler(req, res) { return res.status(status).json({ error: "Crowdsec API Error", data }); } + // Crowdsec returns a literal null instead of an empty array when nothing matches + if (data?.toString().trim() === "null") { + return res.status(status).json([]); + } + return res.status(status).send(data); } catch (error) { logger.error("Exception calling Crowdsec API: %s", error.message); diff --git a/src/widgets/crowdsec/proxy.test.js b/src/widgets/crowdsec/proxy.test.js index 157a96a53..253ac3aef 100644 --- a/src/widgets/crowdsec/proxy.test.js +++ b/src/widgets/crowdsec/proxy.test.js @@ -161,4 +161,29 @@ describe("widgets/crowdsec/proxy", () => { expect(res.statusCode).toBe(500); expect(res.body).toEqual({ error: "Failed to authenticate with Crowdsec" }); }); + + it("normalizes a literal null response to an empty array", async () => { + getServiceWidget.mockResolvedValue({ + type: "crowdsec", + url: "http://cs", + username: "machine", + password: "pw", + }); + + httpProxy + .mockResolvedValueOnce([ + 200, + "application/json", + JSON.stringify({ token: "tok", expire: new Date(Date.now() + 60_000).toISOString() }), + ]) + .mockResolvedValueOnce([200, "application/json", Buffer.from("null")]); + + const req = { query: { group: "g", service: "svc", endpoint: "alerts", index: "0" } }; + const res = createMockRes(); + + await crowdsecProxyHandler(req, res); + + expect(res.statusCode).toBe(200); + expect(res.body).toEqual([]); + }); });