{"id":50244964,"url":"https://github.com/mr-addams/arxsentinel","last_synced_at":"2026-05-31T03:02:45.576Z","repository":{"id":358660350,"uuid":"1242396190","full_name":"mr-addams/arxsentinel","owner":"mr-addams","description":"Universal HTTP access log threat detector","archived":false,"fork":false,"pushed_at":"2026-05-18T12:46:22.000Z","size":593,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2026-05-18T13:39:35.118Z","etag":null,"topics":["access-log","bruteforce-detection","devsecops","fail2ban","golang","ids","intrusion-detection","ip-blocking","linux","log-analysis","nginx","security","systemd","threat-detection","web-security"],"latest_commit_sha":null,"homepage":"https://mr-addams.github.io/arxsentinel/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mr-addams.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-18T11:44:48.000Z","updated_at":"2026-05-18T12:45:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mr-addams/arxsentinel","commit_stats":null,"previous_names":["mr-addams/arxsentinel"],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/mr-addams/arxsentinel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-addams%2Farxsentinel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-addams%2Farxsentinel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-addams%2Farxsentinel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-addams%2Farxsentinel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mr-addams","download_url":"https://codeload.github.com/mr-addams/arxsentinel/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-addams%2Farxsentinel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33542350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"ssl_error","status_checked_at":"2026-05-26T15:22:15.568Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access-log","bruteforce-detection","devsecops","fail2ban","golang","ids","intrusion-detection","ip-blocking","linux","log-analysis","nginx","security","systemd","threat-detection","web-security"],"created_at":"2026-05-26T23:00:21.308Z","updated_at":"2026-05-26T23:00:44.664Z","avatar_url":"https://github.com/mr-addams.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ArxSentinel\n\n[![Release](https://img.shields.io/github/v/release/mr-addams/arxsentinel?include_prereleases\u0026label=release)](https://github.com/mr-addams/arxsentinel/releases)\n[![Build](https://github.com/mr-addams/arxsentinel/actions/workflows/release.yml/badge.svg)](https://github.com/mr-addams/arxsentinel/actions/workflows/release.yml)\n[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)\n[![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go)](go.mod)\n[![Platforms](https://img.shields.io/badge/linux-amd64%20%7C%20arm64-lightgrey?logo=linux)](https://github.com/mr-addams/arxsentinel/releases)\n[![Packages](https://img.shields.io/badge/packages-deb%20%7C%20rpm%20%7C%20pacman-blue)](https://github.com/mr-addams/arxsentinel/releases)\n\nA vigilant sentinel for your web server — reads HTTP access logs in real time, scores every IP through 8 behavioural detectors, and bans attackers via Fail2Ban. Works with nginx, Apache, Caddy, Traefik, HAProxy, LiteSpeed, and OpenLiteSpeed.\n\nSupports **nginx, Apache, Caddy, Traefik, HAProxy, LiteSpeed, and OpenLiteSpeed** via built-in profiles. nginx works out of the box with no profile needed. Caddy and HAProxy require minimal one-time setup. Custom log formats supported via regex. Watch multiple log files in a single process.\n\n```\naccess.log (or stdin) → Source → Merge → whitelist → tracker → scorer → Sink → Fail2Ban / stdout JSON / …\n```\n\n## Supported HTTP servers\n\n### Compatibility table\n\n| Server | Profile | Setup required |\n|--------|---------|----------------|\n| nginx | *(default — no profile needed)* | None — nginx combined log format works out of the box |\n| Apache | `apache` | None — default CLF format |\n| Traefik | `traefik` | Add `fields.headers.names.User-Agent/Referer: keep` to accessLog — see [`deploy/examples/traefik/`](deploy/examples/traefik/) |\n| LiteSpeed / OpenLiteSpeed | `litespeed` | None — default CLF format |\n| Caddy | `caddy` | [xcaddy](https://github.com/caddyserver/xcaddy) + [transform-encoder](https://github.com/caddyserver/transform-encoder) plugin — see [`deploy/examples/caddy/`](deploy/examples/caddy/) |\n| HAProxy | `haproxy-http` | `http-request capture` + custom `log-format` with UA — see [`deploy/examples/haproxy/`](deploy/examples/haproxy/) |\n\n\u003e Each release includes a **Tested product versions** table with the exact server versions the build was validated against — see [GitHub Releases](https://github.com/mr-addams/arxsentinel/releases).\n\n\u003e **nginx:** no `profile:` setting is needed. The default CombinedParser handles nginx combined log format out of the box. Set only `general.log_file` pointing to your access log.\n\nBuilt-in profiles — no regex or field mapping required. Set `parser.profile` to the server name for Apache, Traefik, Caddy, HAProxy, LiteSpeed, or OpenLiteSpeed:\n\n**Example — Apache:**\n\n```yaml\nparser:\n  profile: \"apache\"\n\ngeneral:\n  log_file: /var/log/apache2/access.log\n\noutput:\n  threat_log: /var/log/arxsentinel/threats.log\n```\n\nReady-made configs for each server are in [`deploy/examples/`](deploy/examples/):\n\n```\ndeploy/examples/\n├── apache/      httpd.conf + sentinel-config.yaml\n├── caddy/       Caddyfile + sentinel-config.yaml\n├── traefik/     traefik.yml + sentinel-config.yaml\n├── haproxy/     haproxy.cfg + sentinel-config.yaml\n└── litespeed/   httpd_config.conf + sentinel-config.yaml\n```\n\n\u003e **Note — LiteSpeed / OpenLiteSpeed:** Both LSWS and OLS emit Apache CLF by default —\n\u003e no server-side changes required. Log path: `/usr/local/lsws/logs/access.log`\n\u003e (server-wide) or `/usr/local/lsws/logs/\u003cvhostname\u003e/access.log` (per virtual host).\n\u003e Behind a reverse proxy: enable \"Use Client IP in Header\" in WebAdmin so `%h` logs\n\u003e the real client IP. See `deploy/examples/litespeed/` for the full config.\n\n\u003e **Note — Caddy:** Caddy v2's built-in JSON encoder outputs nested objects. The\n\u003e `caddy` profile requires the\n\u003e [caddy-transform-encoder](https://github.com/caddyserver/transform-encoder) plugin\n\u003e to produce CLF output. See `deploy/examples/caddy/Caddyfile` for the setup.\n\n## Features\n\n- **8 detectors:** probe scanning, rate anomaly, suspicious User-Agent, bruteforce (404 ratio), sequential crawler, no-asset bot, URL overflow / WAF bypass, community bad-bot blocklist\n- **Chain Guard:** detects Cloudflare/CDN edge IPs and bogon/RFC 1918/CGNAT addresses appearing as client IPs — signals a misconfigured proxy chain before ArxSentinel's detectors go blind\n- **Bot DNS verification:** Googlebot, Bingbot, Yandex, DuckDuckGo and others are verified via rDNS/fDNS — legitimate crawlers are never banned\n- **Multi-stream:** watch multiple log files in one process — full pipeline isolation per stream\n- **Whitelist:** IPs, CIDRs, UA substrings — configurable exclusion lists\n- **Linear score decay:** points decay over `observation_window`, no false bans from old traffic\n- **Prometheus metrics:** `/metrics` on configurable port (default `:9117`), optional bcrypt basic auth; Grafana dashboard included\n- **Health endpoint:** `/health` always returns `200 {\"status\":\"ok\"}` — no credentials required; ready for Docker `HEALTHCHECK`, k8s probes, and load balancers\n- **JSON log format:** switch to JSON log parsing via `parser.log_format: \"json\"` — no recompilation needed\n- **SIGHUP reload:** config, scorer, parser and whitelist are rebuilt without restarting the daemon\n- **Graceful shutdown:** line buffer is drained on SIGTERM\n- **Systemd + logrotate + Fail2Ban:** ready-to-use deploy configs included\n\n## Requirements\n\n- Linux x86_64 or arm64 with systemd\n- Fail2Ban\n- An HTTP server writing access logs in a supported format (nginx, Apache, Caddy, Traefik, HAProxy, LiteSpeed, OpenLiteSpeed — or custom regex)\n\n## Installation\n\n### Quick install — any distro (recommended)\n\nAuto-detects your distro and architecture, downloads the correct package from GitHub Releases,\ninstalls it with your package manager, enables and starts the service:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/mr-addams/arxsentinel/main/scripts/get.sh | sudo bash\n```\n\nWorks on Debian, Ubuntu, Fedora, RHEL, AlmaLinux, Rocky Linux, and Arch Linux.\nRequires `curl` and `sudo`. Fail2Ban is installed automatically if missing.\n\nThe service starts immediately and works with nginx out of the box — no profile needed. Edit the config to switch to another server (apache, caddy, traefik, haproxy-http, litespeed, or a custom regex):\n\n```bash\nsudo nano /etc/arxsentinel/config.yaml\nsudo systemctl kill -s HUP arxsentinel   # reload without restart\n```\n\n---\n\n### Debian / Ubuntu — manual package\n\nDownload the `.deb` package for your architecture from the [Releases](https://github.com/mr-addams/arxsentinel/releases) page and install it:\n\n```bash\n# amd64\nsudo apt install ./arxsentinel_\u003cversion\u003e_linux_amd64.deb\n\n# arm64\nsudo apt install ./arxsentinel_\u003cversion\u003e_linux_arm64.deb\n```\n\n`apt install` automatically resolves dependencies (`fail2ban`), installs the systemd unit, Fail2Ban filter/jail, logrotate config, and creates the `arxsentinel` system user.\n\nAfter installation, edit the config and start the service:\n\n```bash\nsudo nano /etc/arxsentinel/config.yaml\nsudo systemctl enable --now arxsentinel\n```\n\n### Fedora / RHEL / AlmaLinux / Rocky Linux\n\nDownload the `.rpm` package for your architecture from the [Releases](https://github.com/mr-addams/arxsentinel/releases) page and install it:\n\n```bash\n# amd64\nsudo dnf install ./arxsentinel_\u003cversion\u003e_linux_amd64.rpm\n\n# arm64\nsudo dnf install ./arxsentinel_\u003cversion\u003e_linux_arm64.rpm\n```\n\n`dnf install` resolves dependencies, installs the systemd unit to `/usr/lib/systemd/system/`, Fail2Ban filter/jail, logrotate config, and creates the `arxsentinel` system user.\n\nAfter installation, edit the config and start the service:\n\n```bash\nsudo nano /etc/arxsentinel/config.yaml\nsudo systemctl enable --now arxsentinel\n```\n\n\u003e **RHEL 8 / CentOS Stream 8:** use `dnf` or `rpm -i` directly. Fail2Ban may require the EPEL repository:\n\u003e `sudo dnf install epel-release \u0026\u0026 sudo dnf install fail2ban`\n\n### Arch Linux / Manjaro\n\nDownload the `.pkg.tar.zst` package for your architecture from the [Releases](https://github.com/mr-addams/arxsentinel/releases) page and install it:\n\n```bash\n# amd64\nsudo pacman -U arxsentinel_\u003cversion\u003e_linux_amd64.pkg.tar.zst\n\n# arm64\nsudo pacman -U arxsentinel_\u003cversion\u003e_linux_arm64.pkg.tar.zst\n```\n\nThe package installs the systemd unit to `/usr/lib/systemd/system/`, Fail2Ban config files, logrotate config, and creates the `arxsentinel` system user.\n\nAfter installation, edit the config and start the service:\n\n```bash\nsudo nano /etc/arxsentinel/config.yaml\nsudo systemctl enable --now arxsentinel\n```\n\n\u003e **Fail2Ban on Arch:** install it with `sudo pacman -S fail2ban` before or after installing arxsentinel.\n\n### Build from source\n\nRequires Go 1.19+:\n\n```bash\ngit clone https://github.com/mr-addams/arxsentinel\ncd arxsentinel\nsudo ./scripts/install.sh\nsudo systemctl enable --now arxsentinel\n```\n\n### Docker\n\nDistroless image (~12 MB), runs as non-root uid 65532, exposes Prometheus metrics on `:9117`.\n\n```bash\ndocker run -d \\\n  -v /var/log/nginx/access.log:/var/log/nginx/access.log:ro \\\n  -v /var/log/arxsentinel:/var/log/arxsentinel \\\n  -p 127.0.0.1:9117:9117 \\\n  ghcr.io/mr-addams/arxsentinel:latest\n```\n\nSee [README.docker.md](deploy/container/docker/README.md) for Docker Compose setup, volume mounts, env var overrides, and Fail2Ban integration.\n\n### Kubernetes (Helm)\n\nDaemonSet topology — one pod per node, reads the node's access log via `hostPath`.\n\n```bash\nhelm install arxsentinel ./deploy/container/k8s/arxsentinel \\\n  --set logVolume.hostPath=/var/log/nginx \\\n  --set threatLog.hostPath=/var/log/arxsentinel\n```\n\nSee [README.helm.md](deploy/container/k8s/arxsentinel/README.md) for values reference, Prometheus Operator integration, and cloud deployment notes.\n\n## Configuration\n\nConfig file: `/etc/arxsentinel/config.yaml` (created from `config.yaml` during installation).  \nOverride path: `ARXSENTINEL_CONFIG=/path/to/config.yaml`.\n\nKey parameters:\n\n```yaml\ngeneral:\n  log_file: /var/log/nginx/access.log   # log file to watch (nginx example; see also: streams:)\n  stats_interval: 300s                  # STATS output interval to operational log\n\nparser:\n  # profile: \"apache\"  # set for non-nginx servers: apache | caddy | traefik | haproxy-http | litespeed\n  #                     # nginx combined log format works without any profile setting\n\nscoring:\n  alert_threshold: 50    # score → WARN in threat log\n  ban_threshold: 80      # score → THREAT + Fail2Ban ban\n  observation_window: 300s  # score accumulation / decay window\n\ndetectors:\n  probe:\n    enabled: true\n    score: 25\n    paths: [/.env, /.git/config, /wp-config.php, ...]  # probe path list\n\n  rate:\n    enabled: true\n    threshold: 100   # requests per window\n    window: 60s\n    score: 25\n\n  useragent:\n    enabled: true\n    scanner_score: 40     # Nuclei, sqlmap, Nikto\n    grabber_score: 20     # wget, HTTrack\n    automation_score: 15  # python-requests, aiohttp\n    empty_ua_score: 30\n\n  bruteforce:\n    enabled: true\n    min_requests: 10\n    ratio_threshold: 0.6  # \u003e60% of responses are 404\n    score: 30\n\n  crawler:\n    enabled: true\n    min_sequential: 5  # /page/1, /page/2, ... N in a row\n    score: 20\n\n  noasset:\n    enabled: true\n    min_page_requests: 3\n    asset_ratio_threshold: 0.1  # \u003c10% of requests go to static assets\n    score: 20\n\n  overflow:\n    enabled: true\n    max_url_length: 2048\n    suspicious_params: [bypass, shell, cmd, exec, eval]\n    score: 30\n\n  badbot:\n    enabled: true\n    score: 60\n    check_ua: true\n    check_referrer: false   # opt-in: also match the Referer header (~7108 referrer patterns)\n\nblocklist:\n  storage: \"\"              # \"\" = in-memory; file path = bbolt (survives restarts)\n  lists:\n    - name: badbot-ua\n      refresh_interval: 24h\n      sources:\n        - url: \"https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list\"\n          format: plain_text\n    - name: badbot-ref\n      refresh_interval: 24h\n      sources:\n        - url: \"https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-referrer-words.list\"\n          format: plain_text\n\nwhitelist:\n  fake_bot_score: 35      # penalty for a bot UA that fails DNS verification\n  dns_verify_timeout: 2s  # DNS verification timeout per pipeline request\n  custom:\n    ips: [127.0.0.1]\n    cidrs: [10.0.0.0/8]\n    ua_substrings: [internal-monitor]\n\noutput:\n  threat_log: /var/log/arxsentinel/threats.log\n  operational_log: /var/log/arxsentinel/sentinel.log\n```\n\n\u003e **yaml.v3 limitation:** if a section is present in config.yaml (e.g., `scoring:`), it must include **all** fields — any omitted fields will be zeroed out. Sections missing from the file entirely will use Go defaults.\n\n## Detectors\n\n| Detector | Trigger | Default score |\n|----------|---------|---------------|\n| **probe** | request to .env, .git, wp-config.php, etc. | 25 per request |\n| **rate** | \u003e100 requests per 60s | 25 |\n| **useragent** | scanner / grabber / automation / empty UA | 15–40 |\n| **bruteforce** | \u003e60% of responses are 404 with ≥10 requests | 30 |\n| **crawler** | ≥5 sequential numeric URLs (/page/1..N) | 20 |\n| **noasset** | \u003c10% requests to static assets with ≥3 page requests | 20 |\n| **overflow** | URL \u003e2048 chars or WAF bypass keywords | 30 |\n| **badbot** | UA (or Referer) matches community blocklist (~685 patterns) | 60 |\n\nScore accumulates with linear decay over `observation_window`. Reaching `alert_threshold` writes a WARN; reaching `ban_threshold` writes a THREAT and triggers Fail2Ban.\n\n## Whitelist\n\nArxSentinel provides automatic bot verification (search engines) and custom exclusion lists\n(IPs, CIDRs, User-Agent substrings). Whitelisted requests skip all detectors entirely.\n\nSee [README.whitelist.md](deploy/examples/README.whitelist.md) for configuration details and examples.\n\n## Architecture\n\n```\n[Source: file]  ─┐    FileSource (inotify, logrotate-aware)\n[Source: stdin] ─┼──→ Merge() ──→ entries chan (*LogEntry)\n[Source: http]  ─┘    (Phase 2+)\n                              │\n                  whitelist.Matcher ──→ custom IP/CIDR/UA? → skip\n                              │\n                  chaincheck.Checker ──→ Cloudflare/bogon IP? → warnings.log\n                              │\n                  whitelist.Verifier ──→ bot UA? → rDNS/fDNS → verified? → skip\n                              │                              → fake bot? → +FakeBotScore\n                  tracker.Update(*IPState)\n                    ├── TotalRequests, Requests404\n                    ├── pathBuf (ring buffer, last 64 paths)\n                    └── sliding window rate counters\n                              │\n                  scorer.Evaluate(ipState, entry)\n                    ├── decay accumulated score\n                    ├── run 8 detectors\n                    └── determine verdict (score → level)\n                              │\n                  [Sink: Fail2Ban file]  ──→ threats.log ──→ Fail2Ban ──→ iptables ban\n                  [Sink: stdout JSON]    ──→ log aggregator (Loki, Splunk, Datadog)\n                  [Sink: Splunk/Kafka]       (Phase 2+)\n                              │\n                  sentinel.log (operational)\n```\n\nThe default configuration (Fail2Ban file sink) is fully backward compatible — existing\n`general.log_file` and `output.threat_log` settings work unchanged.\n\nBackground goroutines:\n- **FileSource** — file watching via fsnotify, handles mv/copytruncate logrotate\n- **GC** — removes inactive IPs every `gc_interval` (default 60s)\n- **Stats** — prints `STATS processed/tracked/threats/suspicious` every `stats_interval`\n- **SIGHUP listener** — converts the signal into a channel event for the main loop\n\n## Multi-stream monitoring\n\nRun one sentinel process that watches multiple log files simultaneously — one pipeline per domain, full isolation.\n\n### Config\n\n```yaml\nstreams:\n  - name: site1\n    log_file: /var/log/nginx/site1.access.log\n    threat_log: /var/log/arxsentinel/site1.threats.log\n  - name: site2\n    log_file: /var/log/apache2/site2.access.log\n    threat_log: /var/log/arxsentinel/site2.threats.log\n    profile: apache\n```\n\n\u003e **Note:** `streams:` and `general.log_file` are mutually exclusive. Use one or the other.\n\nEach stream gets its own tracker, scorer, whitelist state, and threat log. A crash or slow scan on one stream does not affect others.\n\n### Backward compatibility\n\nThe classic single-file config (`general.log_file`) keeps working — it is silently converted to a single unnamed stream (`stream=\"\"` label on metrics). No config migration needed.\n\n### Fail2Ban multi-stream\n\nEach stream writes its own `threat_log` file. Create one Fail2Ban jail per file:\n\n```ini\n# /etc/fail2ban/jail.d/arxsentinel-site1.conf\n[arxsentinel-site1]\nenabled  = true\nfilter   = arxsentinel\nlogpath  = /var/log/arxsentinel/site1.threats.log\nmaxretry = 1\nbantime  = 86400\n\n[arxsentinel-site2]\nenabled  = true\nfilter   = arxsentinel\nlogpath  = /var/log/arxsentinel/site2.threats.log\nmaxretry = 1\nbantime  = 86400\n```\n\n### Grafana\n\nThe dashboard includes a **Stream** variable. Select one or multiple streams to filter all panels. Import `deploy/grafana/arxsentinel-dashboard.json` (v2).\n\n---\n\n## Logs\n\n**Operational log** (`/var/log/arxsentinel/sentinel.log`) — daemon's working log:\n\n```\n2026-04-02 14:33:10 [STARTUP] arxsentinel v1.0.0 started\n2026-04-02 14:33:12 [THREAT] 45.134.26.8 score=85 modules=probe,rate reason=\"...\"\n2026-04-02 14:38:10 [STATS] processed=14320 tracked=87 threats=3 suspicious=12\n```\n\nTags: `STARTUP`, `SHUTDOWN`, `CONFIG`, `THREAT`, `WHITELIST`, `STATS`, `GC`, `ERROR`, `WARN`.  \nDebug tags (`PARSER`, `TAIL`, `DETECTOR`, `SCORER`) are visible only when `logging.debug: true`.\n\n**Threat log** (`/var/log/arxsentinel/threats.log`) — read by Fail2Ban:\n\n```\n2026-04-02T14:33:12Z THREAT 45.134.26.8 score=85 modules=probe,rate reason=\"probe:/.env,rate:142rps\"\n2026-04-02T14:35:01Z WARN   92.63.104.12 score=55 modules=useragent reason=\"ua:Nuclei/3.1.0\"\n```\n\nFail2Ban failregex: `THREAT \u003cHOST\u003e score=\\d+` (file `deploy/fail2ban/filter.d/arxsentinel.conf`).\n\n**Warnings log** (`chain_guard.warnings_log`) — infrastructure misconfiguration alerts:\n\n```\n2026-05-20T12:34:56Z CHAIN_WARN cloudflare-ip-as-client ip=172.64.0.1 cidr=172.64.0.0/13 log=/var/log/nginx/access.log\n2026-05-20T12:34:57Z CHAIN_WARN bogon-ip-as-client ip=10.0.0.1 cidr=10.0.0.0/8 log=/var/log/nginx/access.log\n```\n\nWarnings are distinct from threats: `CHAIN_WARN` means ArxSentinel cannot reliably identify\nthe real attacker IP. Fix the underlying infrastructure issue (see [Chain Guard](#chain-guard--detecting-broken-ip-extraction))\nand the warnings will stop.\n\n## Management\n\n```bash\n# Status and logs\nsystemctl status arxsentinel\njournalctl -u arxsentinel -f\n\n# Reload config without restart (SIGHUP)\nkill -HUP $(cat /var/run/arxsentinel.pid)\n# or\nsystemctl kill -s HUP arxsentinel\n\n# Stop (graceful — drains the line buffer)\nsystemctl stop arxsentinel\n\n# Manual ban/unban via Fail2Ban\nfail2ban-client status arxsentinel\nfail2ban-client set arxsentinel unbanip 1.2.3.4\n```\n\n**What is updated on SIGHUP:** scorer (detectors + thresholds), whitelist matcher, debug/color flags, log file paths.  \n**What is NOT updated:** tracker (IP state), DNS cache, TailReader (access.log path requires a restart).\n\n## Log Formats\n\nArxSentinel supports three log format modes: **combined** (default nginx), **JSON** (no recompilation needed), and **custom regex** for arbitrary text formats.\n\nSee [README.log-formats.md](deploy/examples/README.log-formats.md) for full configuration examples, field mappings, and common mistakes.\n\n## Reverse proxy \u0026 Chain Guard\n\nFull guide for deployment behind a reverse proxy (HAProxy, Traefik, Caddy, nginx), including IP extraction configuration and Chain Guard (broken IP chain detection).\n\nSee [`deploy/examples/reverse-proxy/README.md`](deploy/examples/reverse-proxy/README.md).\n\n## CMS-specific configurations\n\nReady-made `probe.paths` overrides for the most common PHP stacks are in\n`deploy/examples/cms/`. Copy the relevant paths into your `config.yaml`:\n\n| File | Target |\n|------|--------|\n| [`wordpress.yaml`](deploy/examples/cms/wordpress.yaml) | WordPress — `wp-login.php`, `xmlrpc.php`, REST user enumeration |\n| [`laravel.yaml`](deploy/examples/cms/laravel.yaml) | Laravel — `.env`, `/storage/`, `/vendor/`, Telescope, Horizon |\n| [`drupal.yaml`](deploy/examples/cms/drupal.yaml) | Drupal — `/user/login`, `settings.php`, `update.php` |\n| [`joomla.yaml`](deploy/examples/cms/joomla.yaml) | Joomla — `/administrator/`, `configuration.php` |\n| [`generic-php.yaml`](deploy/examples/cms/generic-php.yaml) | Custom PHP apps — phpinfo, phpMyAdmin, Adminer, backup files |\n\n**How to apply a CMS config:**\n\n1. Open `deploy/examples/cms/\u003ccms\u003e.yaml` and copy the `paths:` list.\n2. Paste it into your `config.yaml` under `detectors.probe.paths:`.\n3. Reload without restart: `kill -HUP $(pgrep arxsentinel)` — or `systemctl kill -s HUP arxsentinel`.\n\nThe paths **extend** (not replace) the built-in sensitive-path list by default.\nTo use only your custom list, set `detectors.probe.paths:` to exactly the paths you want.\n\n\n---\n\n## Prometheus metrics\n\nEnable metrics in `config.yaml`, configure Prometheus scraping, set up bcrypt password hashing, and import the Grafana dashboard.\n\nFull guide: [`deploy/grafana/README.md`](deploy/grafana/README.md)\n\n---\n\n## Troubleshooting\n\n**Daemon fails to start — threat log error:**  \nCheck permissions on `/var/log/arxsentinel/` — the directory must be owned by the `arxsentinel` user.\n\n**Fail2Ban is not banning — check log format:**  \n```bash\nfail2ban-regex /var/log/arxsentinel/threats.log /etc/fail2ban/filter.d/arxsentinel.conf\n```\n\n**Too many false WARNs — reduce sensitivity:**  \nLower the `score` or raise thresholds (`threshold`, `ratio_threshold`) in the config, then `kill -HUP`.\n\n**Debug pipeline — enable debug mode:**  \n```yaml\nlogging:\n  debug: true\n```\nRestart or `kill -HUP`. The operational log will show `[PARSER]`, `[DETECTOR]`, `[SCORER]` lines for every request.\n\n**High memory usage:**  \nReduce `state.max_tracked_ips` (default 100000; each IP ≈ 2.5 KB → 100k ≈ 250 MB).\n\n---\n\n## Third-party data\n\nThe **badbot** detector fetches its blocklists from [nginx-ultimate-bad-bot-blocker](https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker), an outstanding community project created and maintained by **[Mitchell Krog (@mitchellkrogza)](https://github.com/mitchellkrogza)** and its contributors. The project curates ~685 bad User-Agent patterns and ~7108 bad referrer words, updated almost daily — an enormous effort that benefits the entire web.\n\nLicensed under [MIT](https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/blob/master/LICENSE.md). The lists are downloaded at runtime and are not bundled with ArxSentinel.\n\nHeartfelt thanks to Mitchell Krog and every contributor to that project — your dedication makes the web a safer place for everyone.\n\n---\n\n[Русская документация → README.ru.md](README.ru.md) | [Українська документація → README.uk.md](README.uk.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmr-addams%2Farxsentinel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmr-addams%2Farxsentinel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmr-addams%2Farxsentinel/lists"}