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([]);
+ });
});