Description
Calling GET /v1/alerts?has_active_decision=true&origin=X against the local LAPI hangs and eventually times out (>25s, observed up to 20s+ on every call), while the same request without the origin= parameter completes in ~13ms with HTTP 200. This makes cscli decisions list --origin X unusable in scripts (and breaks any tooling that relies on it, e.g. cron jobs that filter decisions by origin).
The bottleneck is not SQLite — the underlying SQL query completes in <100ms with or without an index on decisions.origin. The hang is somewhere in the Go handler for the origin filter on this route.
Environment
- CrowdSec:
v1.7.8-debian-pragmatic-amd64-63227459 (Codename: alphaga, BuildDate: 2026-05-11)
- GoVersion: 1.26.2
- OS: Ubuntu 24.04.4 LTS, kernel 6.17.0-23-generic
- DB backend: SQLite (44 MB, ~44k rows in
decisions, ~33k active)
- LAPI binding:
127.0.0.1:8080 (local-only)
Steps to reproduce
# Get a JWT
LOGIN=$(grep "^login:" /etc/crowdsec/local_api_credentials.yaml | awk '{print $2}')
PASS=$(grep "^password:" /etc/crowdsec/local_api_credentials.yaml | awk '{print $2}')
JWT=$(curl -s -X POST http://127.0.0.1:8080/v1/watchers/login \
-H "Content-Type: application/json" \
-d "{\"machine_id\":\"$LOGIN\",\"password\":\"$PASS\"}" \
| jq -r .token)
# Fast — completes in ~13ms
time curl -s -m 25 -H "Authorization: Bearer $JWT" \
"http://127.0.0.1:8080/v1/alerts?has_active_decision=true&include_capi=false&limit=100" \
-o /dev/null -w "HTTP %{http_code} time=%{time_total}s\n"
# Hangs — times out (HTTP 000 after 25s)
time curl -s -m 25 -H "Authorization: Bearer $JWT" \
"http://127.0.0.1:8080/v1/alerts?has_active_decision=true&include_capi=false&limit=100&origin=cscli" \
-o /dev/null -w "HTTP %{http_code} time=%{time_total}s\n"
Observed output:
HTTP 200 time=0.013825s
HTTP 000 time=25.002199s
Same behaviour through cscli:
$ time cscli decisions list -o json | wc -c # ~0.3s, returns full payload
$ time cscli decisions list --origin cscli -o json # always 15s timeout (cscli's internal timeout)
$ time cscli decisions list --origin cscli # same timeout in table mode
$ time cscli decisions list --value 1.2.3.4 -o json # ~0.2s, works
$ time cscli decisions list --scope Ip -o json # ~0.2s, works
Only the origin filter is affected. Reproducible with any value of origin (cscli, crowdsec, lists, CAPI).
Investigation
To rule out the database as the bottleneck:
- Existing indexes on
decisions: decision_value, decision_until, decision_alert_decisions, decision_start_ip_end_ip. No index on origin.
- Direct SQLite benchmark of the equivalent JOIN (
alerts ⨝ decisions filtered by origin and until > now()): 70ms (uses decision_until index).
- Created
CREATE INDEX idx_decision_origin_until ON decisions(origin, until) + ANALYZE → SQL query drops to 21ms with EXPLAIN QUERY PLAN confirming use of the new index.
cscli --origin still times out at 20s with the new index in place. Index dropped afterwards.
→ The handler is not SQL-bound. The hang happens elsewhere in the Go path for the origin= query parameter. Possible candidates: a misbehaving filter loop, an N+1 fetch, a deadlock, or unbounded pagination. The TCP connection from cscli/curl stays in ESTABLISHED state during the whole hang, then the client times out.
Expected behavior
GET /v1/alerts?origin=X should return in the same order of magnitude as the unfiltered query (10s–100s of ms), since filtering reduces the result set rather than expanding it.
Workaround
Drop the origin= filter and filter client-side:
# Fetch all active alerts (one call, ~300ms)
result = subprocess.run(
["cscli", "decisions", "list", "-o", "json"],
capture_output=True, text=True, timeout=15,
)
alerts = json.loads(result.stdout)
# Filter by origin in Python
cscli_bans = {
dec["value"]
for alert in alerts
for dec in (alert.get("decisions") or [])
if dec.get("origin") == "cscli"
and dec.get("type") == "ban"
and dec.get("scope", "").lower() == "ip"
}
This is a strict regression for callers that need the filter, but works around the issue completely.
Happy to provide additional diagnostics (pprof, strace, journalctl, full schema dump) if useful.
Description
Calling
GET /v1/alerts?has_active_decision=true&origin=Xagainst the local LAPI hangs and eventually times out (>25s, observed up to 20s+ on every call), while the same request without theorigin=parameter completes in ~13ms with HTTP 200. This makescscli decisions list --origin Xunusable in scripts (and breaks any tooling that relies on it, e.g. cron jobs that filter decisions by origin).The bottleneck is not SQLite — the underlying SQL query completes in <100ms with or without an index on
decisions.origin. The hang is somewhere in the Go handler for theoriginfilter on this route.Environment
v1.7.8-debian-pragmatic-amd64-63227459(Codename:alphaga, BuildDate: 2026-05-11)decisions, ~33k active)127.0.0.1:8080(local-only)Steps to reproduce
Observed output:
Same behaviour through
cscli:Only the
originfilter is affected. Reproducible with any value oforigin(cscli,crowdsec,lists,CAPI).Investigation
To rule out the database as the bottleneck:
decisions:decision_value,decision_until,decision_alert_decisions,decision_start_ip_end_ip. No index onorigin.alerts⨝decisionsfiltered byoriginanduntil > now()): 70ms (usesdecision_untilindex).CREATE INDEX idx_decision_origin_until ON decisions(origin, until)+ANALYZE→ SQL query drops to 21ms withEXPLAIN QUERY PLANconfirming use of the new index.cscli --originstill times out at 20s with the new index in place. Index dropped afterwards.→ The handler is not SQL-bound. The hang happens elsewhere in the Go path for the
origin=query parameter. Possible candidates: a misbehaving filter loop, an N+1 fetch, a deadlock, or unbounded pagination. The TCP connection fromcscli/curlstays in ESTABLISHED state during the whole hang, then the client times out.Expected behavior
GET /v1/alerts?origin=Xshould return in the same order of magnitude as the unfiltered query (10s–100s of ms), since filtering reduces the result set rather than expanding it.Workaround
Drop the
origin=filter and filter client-side:This is a strict regression for callers that need the filter, but works around the issue completely.
Happy to provide additional diagnostics (pprof, strace, journalctl, full schema dump) if useful.