feat(security): opt-in local CrowdSec LAPI bouncer on the Docker engine
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 30s
CI / tests-unit (push) Successful in 1m37s
CI / tests-integration (push) Successful in 1m55s
CI / tests-ui (push) Successful in 2m23s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m38s

This commit is contained in:
openhands committed 2026-09-24 18:08:18 +02:00
1 parent 3e1a3f92c8
commit 84d53139a9
15 files changed
+1002 -2

No files matched your search

+28
View File
@@ -2,6 +2,7 @@ import { NextRequest } from "next/server";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { invalidateAntiddosConfig } from "@/lib/antiddos-config";
import { resetCrowdsecCache } from "@/lib/crowdsec-api";
import { resetCrowdsecLocalCache } from "@/lib/crowdsec-local";
import { enforceDdosRateLimit } from "@/lib/ddos-guard";
// The gate's block escalation (and thus the CrowdSec hook) only runs when
@@ -123,6 +124,7 @@ describe("anti-DDoS automatic CrowdSec blocks", () => {
state.z.clear();
state.sendAlert.mockReset();
resetCrowdsecCache();
resetCrowdsecLocalCache();
invalidateAntiddosConfig();
fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
@@ -141,6 +143,7 @@ describe("anti-DDoS automatic CrowdSec blocks", () => {
vi.unstubAllEnvs();
state.map.clear();
resetCrowdsecCache();
resetCrowdsecLocalCache();
invalidateAntiddosConfig();
});
@@ -244,4 +247,29 @@ describe("anti-DDoS automatic CrowdSec blocks", () => {
expect(fetchMock).not.toHaveBeenCalled();
});
it("blocks immediately on a local LAPI ban decision (app-layer bouncer)", async () => {
vi.stubEnv("CROWDSEC_LOCAL_ENABLED", "true");
vi.stubEnv("CROWDSEC_LAPI_URL", "http://127.0.0.1:18080");
vi.stubEnv("CROWDSEC_LAPI_API_KEY", "local-bouncer-key");
fetchMock.mockImplementation(async (input) => {
if (String(input).startsWith("http://127.0.0.1:18080/")) {
return jsonResponse([
{
origin: "crowdsec",
type: "ban",
scope: "ip",
value: "198.51.100.88",
duration: "1h",
},
]);
}
return jsonResponse({ message: "unexpected upstream" }, 500);
});
const ip = "198.51.100.88";
const blocks = await pump(proxiedRequest(ip), 1);
expect(blocks).toBe(1);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});