Documentation index

The collector publishes security events through mycount-server.service’s systemd journal. Fail2ban can count these events per IP across all collector workers and invoke BMCyber’s existing ban action. MyCount publishes events without waiting for acknowledgement, keeping counters, or calling BMCyber.

Published events

Event When it is emitted Agreed companion threshold
collection_request Once for each POST /count with a verified client address, before origin checking, JSON parsing, geolocation, or persistence. 60 events from one IP within 60 seconds.
malformed_payload Additionally when an allowed-origin POST has malformed JSON, an unsupported content type, an oversized body, or fails visit-payload validation. 5 events from one IP within 60 seconds.

A malformed submission contributes to both counts. Counts combine all sites and page URLs for an IP. Health requests, OPTIONS preflights, counter reads, other routes and unsupported methods do not publish either event. An origin rejection publishes only collection_request: its body is not inspected. Database/geolocation outages are operational errors, not malformed payloads. Existing bot classifications do not produce a ban event.

Each journal MESSAGE contains exactly one line, for example:

2026-11-01 10:00:00 SECURITY event=collection_request ip=192.0.2.10
2026-11-01 10:00:00 SECURITY event=malformed_payload ip=192.0.2.10

The timestamp is local time at second precision. Event names are fixed application constants. Records contain no URL, payload, user-agent text, or other free text supplied by a visitor. The security logger writes the message without additional prefixes to stderr; the service explicitly sends stdout and stderr to journald. Normal diagnostics keep their existing configuration.

Trusted source attribution

Caddy overwrites X-MyCount-Client-IP with its observed client address. MyCount accepts this header only when the immediate peer matches its configured local backend/proxy address. Otherwise it uses the directly observed peer, ignoring forwarded headers. The existing deployment binds the backend to loopback; maintain that proxy/listener boundary.

Sources must be numeric IPv4/IPv6 addresses. Missing, malformed, scoped, unspecified, multicast, or multiple addresses are rejected. A trusted proxy request missing the client header returns 400 rather than substituting the proxy’s address. Requests without trustworthy attribution produce neither event. Validated addresses are bound only for the current request and are restored even when the handler raises.

Companion configuration and activation

MyCount does not install or configure Fail2ban or BMCyber. Required companion changes are recorded in the unreleased changelog. They include separate, initially disabled flood and malformed-payload jails, native backend = systemd, and a journal match restricted to _SYSTEMD_UNIT=mycount-server.service. The consumer must match these event names rather than the older fail2safe event. BMCyber’s current file-based jail cannot consume this producer without those changes.

The host Fail2ban interpreter needs native systemd journal bindings (such as the distribution’s python3-systemd package); MyCount needs no new Python package. Fail2ban owns the two count/window thresholds. Its ticket interval and BMCyber’s existing 20–24-hour kernel block duration remain separate. A resulting ban affects inbound traffic to the entire host.

Before activation, verify real requests through Caddy and the installed collector service. Inspect the journal using:

journalctl -u mycount-server.service -o cat
journalctl -u mycount-server.service -o verbose

Confirm one collection_request per POST, an additional malformed_payload only for the cases above, original IPv4/IPv6 addresses, and the actual trusted _SYSTEMD_UNIT metadata. On a disposable systemd host with the companion configuration, use native Fail2ban journal matching to verify timestamps, both thresholds and their windows, exclusions, multiple workers, and receipt of the correct BMCyber ban request. Logger/WSGI tests alone do not prove that native journal integration works.

Journal publication is best effort: rate limiting or output failures may lose events. This is asynchronous abuse detection, not a synchronous HTTP rate limiter or DoS protection. MyCount does not return 429 merely because an IP has reached a companion threshold, and it makes no delivery or ban guarantee.