Implemented for develop on 2026-09-08 and included in RC6.
The WAF tab adds a proxy-host selector and recorded totals for the selected 1h, 6h, 24h, or 7d window. The System tab adds separate nginx and host firewall observations. Existing CrowdSec Prometheus cards remain cumulative engine metrics; their totals need not match these observations.
| Observation | Meaning |
|---|---|
| Confirmed AppSec responses | The nginx bouncer received HTTP 200 or 403 from its AppSec request without a transport error. Includes allow and deny responses. |
| AppSec policy ban actions | The nginx ban plugin was invoked after AppSec rejected a check. Includes configured fail-closed and unreadable-body policy; it is not exclusively a malicious-request count. |
| AppSec response errors | Transport errors or AppSec responses other than 200/403. |
| Checks without an AppSec response | AppSec checks that returned before reaching the response observation, such as unreadable-body handling. |
| HTTP ban actions | Calls to nginx's CrowdSec ban plugin across managed proxy hosts. Includes AppSec policy bans and decision-based bans. |
| Challenge actions | Calls to CrowdSec captcha/challenge plugins. Does not count Anubis challenges. |
| IPv4 host / forwarded packets dropped | Increases in the installer-managed CrowdSec blacklist DROP/REJECT rule counters on INPUT / FORWARD, respectively. |
| Host firewall evidence | Timestamped service-active and required-rule-presence observations. This does not test LAPI synchronization, every firewall path, or continuous protection. |
Remediation calls are observed before the original plugin runs; they do not prove that a client received a complete response. Application-generated HTTP 403s are not counted as CrowdSec ban actions. Locations that bypass AppSec are not counted as inspections. Neither request counts nor packet counts represent unique IPs. Do not sum the HTTP and firewall figures. The firewall observer currently covers IPv4 only, even on installations that also enforce IPv6 rules.
The overview's WAF blocked requests value sums cs_appsec_block_total since
CrowdSec started. It counts requests, including repeated requests from one IP.
The Local active decisions card counts current local LAPI decision records,
excluding community feeds, simulations, and the separately displayed honeypot
decisions. Expired and removed decisions are no longer active.
AppSec can reject the current request without creating an ongoing IP ban; longer remediation depends on scenarios and decision profiles. Therefore two WAF blocks and zero local decisions can both be correct. Neither value counts unique attackers, and the WAF counter alone does not prove current end-to-end enforcement. See CrowdSec's metric definitions and AppSec introduction.
The local card's subtitle describes active decisions. It no longer displays cumulative nonempty decision replies beneath that count: those replies count API responses, including repeated reads and dashboard reads, rather than blocked requests or unique IPs. Bouncer API request statistics remain in the System tab.
The overview and WAF tab explain this distinction directly. These definitions were checked against the backend and frontend; the owner's live server has not been inspected as part of this clarification.
- The image instruments the pinned Lua bouncer at its AppSec HTTP response and
wraps its remediation calls without replacing enforcement behavior. The build
fails if the exact upstream insertion point changes. The private exporter uses
/run/npmplus-telemetry.sock; no TCP listener or browser credential is added. - nginx labels traffic with the numeric managed proxy-host ID. Arbitrary Host headers, IPs, URLs, payloads, and secrets are not stored. A 2 MiB shared dictionary limits observation to 200 host slots per nginx master lifetime. Capacity loss marks history incomplete. Deleted hosts remain identifiable by their numeric ID; displayed domain names come from the current proxy-host configuration.
- Installer v1.56 installs
/usr/local/bin/npmplus-collect-enforcementand a minute cron. This read-only host helper writes aggregate firewall counters atomically to/opt/npmplus/crowdsec/firewall-telemetry.json. It requires the installer-managed firewall-bouncer marker. The backend receives the file through its existing data mount; no Docker socket or firewall capability is added. - The backend samples both sources every 60 seconds independently of open browser
tabs. It persists baselines and five-minute counter deltas in one transaction in
the existing application database's
security_telemetrytable. No additional database service, external telemetry account, or frontend dependency is required. - Data is retained for seven days, plus the current boundary bucket and baselines. Expired buckets are pruned on successful collection. Restarts of the backend cannot replay a saved delta. nginx master restarts and firewall service/boot epoch changes start new baselines; decreasing counters and gaps over 150 seconds skip the affected deltas instead of inventing traffic.
- Windows end at the last completed five-minute boundary. Each delta belongs to the bucket containing the end of its collection interval, so boundary timing is approximate to a collection interval (normally one minute, at most 150 seconds for an accepted interval). The dashboard excludes the current unfinished bucket.
- New installations need a baseline, another sample, and a completed bucket before counts appear. Missing intervals remain partial history. An observed zero is different from unavailable data, which displays a dash. After 150 seconds without a fresh observation, source status becomes stale and rule verification is withheld.
- The per-host response is bounded to 200 entries. Totals include other recorded hosts when history spans more IDs; a separate warning marks the truncated list. The dictionary capacity limit instead drops observations and marks history partial.
The admin-only GET /api/crowdsec/telemetry?window_hours=24 endpoint exposes the
window, timestamps, coverage, per-layer totals, and bounded host list. Only the
existing 1/6/24/168-hour choices are accepted. Anonymous and non-admin requests
remain denied by the existing CrowdSec access gate.
This needs both an image containing these changes and installer v1.56 or newer. Updating only the UI cannot collect nginx traffic, and updating only the image cannot install the host firewall observer. Once a corresponding image is published, use the ordinary installer-managed safe update. Template version 21 regenerates existing proxy configurations with their numeric telemetry IDs. Do not manually expose the Unix exporter or mount the Docker socket into NPMplus.
For custom Compose installations, nginx/WAF history can work with the new image while firewall observations remain unavailable unless equivalent installer-managed host tooling is present. AppSec-disabled installations can still record decision ban actions, while their AppSec response totals can legitimately remain zero. Existing database backups include retained telemetry. Restoring an archive restores its history; collection gaps after restore remain visible.
To investigate missing data, first check the dashboard's observation timestamps,
then the private socket, cron/helper installation, and the firewall snapshot's
collected_at value. A green process status or a nonempty LAPI decision reply alone
is insufficient evidence that traffic was blocked. No live VM deployment or reboot
has been performed as part of this change.
Regression coverage includes real SQLite migration/persistence, duplicate samples, counter resets, epoch changes, stale timestamps, gaps, host limits, pruning, and the admin-only HTTP contract. Linux installer fixtures execute the actual generated helper against known firewall rules. A disposable nginx integration test invokes the real pinned bouncer with fake LAPI, AppSec, and upstream listeners, checking allow/deny, upstream 403, bypass, and AppSec failure behavior.
Run from the repository, with a disposable image containing Python and nginx:
cd backend && pnpm test && node validate-schema.js
cd ../frontend && pnpm test && pnpm exec vite build
# From the repository root, on Linux:
python3 tests/installer-recovery.py
docker run --rm --network none --entrypoint python3 \
-v "$PWD:/repo:ro" ghcr.io/mangyan1/npmplus:develop /repo/tests/telemetry-nginx.pyThe nginx harness copies the instrumentation into its disposable container, so it
also works with the preceding image. Browser fixtures in
backend/.smoke/ui-driver.mjs cover host selection, partial history, missing data,
stale firewall evidence, and responsive layouts. Screenshots remain ignored.
Local verification passed: 81 backend tests (including an unprivileged Linux run),
schema validation, 40 backend smoke checks, 11 executable installer tests, the real
nginx/bouncer test including the production Unix exporter, 8 frontend tests,
TypeScript, and a production Vite build. The 53 Chromium checks cover desktop, 390px, and
320px layouts; the new WAF screenshots were inspected. Both apps pass full Biome
CI mode in a disposable LF copy, preserving unrelated Windows line endings.
Bash syntax, ShellCheck, Dockerfile lint with the repository's CI settings, English
translation ordering, and git diff --check pass. MySQL/PostgreSQL are not
integration-tested; the migration uses a larger text type on MySQL so buckets
spanning host turnover can exceed its ordinary TEXT limit.
These tests do not replace a final rebuilt-image scan or an operator check against live VM traffic. These develop changes have not been deployed to the owner's VM or released.
- CrowdSec remediation usage metrics distinguishes HTTP and firewall measurement units.
- CrowdSec nginx remediation component documents AppSec and remediation behavior.
- Pinned Lua bouncer source is the build-time response-hook contract. Recheck the hook and integration test whenever the bouncer revision changes.
Attack activity and active-ban details show the detector identifier, a suggested
attack category derived from that identifier, detection window, source network,
and up to ten retained events. Native AppSec rule_name and uri metadata now
survive the API allowlist. Query strings and fragments are removed from URI
metadata, values remain limited to 512 characters, and each event is limited to
32 metadata entries. Raw payloads, cookies, and authorization fields remain excluded.
Tool hints use only explicit claims in a recorded http_user_agent (for example
sqlmap, Nmap, Nikto, Nuclei, or curl). User-Agents are spoofable, HTTP libraries
also have legitimate uses, and unknown clients remain unknown. Neither a rule
match nor a tool hint proves a successful exploit, attacker identity, or actual
client-side blocking. WAF top rules remain cumulative aggregate matches, not
individual requests or attacker counts.
Honeypot history offers an on-demand, bounded same-IP CrowdSec alert lookup. Those alerts may represent different activity; compare their timestamps and rules. The honeypot address ledger itself contains no request path, User-Agent, or payload, and its timestamps describe collection observations. Richer direct honeypot attribution requires a separate request logging integration; it cannot be reconstructed from existing address-only records. No additional collector or per-IP Prometheus labels are introduced by this UI change.
Metadata contracts were checked against the official CrowdSec AppSec event generator and generated event documentation.