Skip to content

Latest commit

Β 

History

36 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

EzyShield

CI CodeQL Go 1.26+ License: AGPL-3.0

EzyShield

Intrusion blocking for Linux servers β€” fail2ban, a decade later.

EzyShield watches your server logs, detects attacking IPs, and bans them with escalating penalties: locally via nftables and at the edge via Cloudflare. A deterministic rule engine scores every event offline and always works; AI is consulted only for the ambiguous cases, so decisions stay cheap and the tool runs fully offline if you never configure a provider. It ships as a single static Go binary β€” no Python, no Java, no runtime to install.

Status: early release (v0.1). Everything described below is implemented, tested in CI, and running today on multiple Linux servers managed by the maintainer. Start in dry-run (the default), watch the decisions, and arm it once you trust them. Interfaces may still change before 1.0 β€” bug reports via issues are very welcome.

See it run: bash scripts/demo/demo.sh replays a full attack against a throwaway dry-run instance β€” detection, the strike ladder escalating on the repeat offense, and the ban receipt β€” in about 90 seconds, no root needed.


Quickstart

curl -sfL https://get.ezyshield.com | sudo sh    # install (verifies SHA-256)
sudo ezyshield init                              # guided setup β€” installs & starts the service
ezyshield status                                 # see what it *would* have banned (dry-run)
sudoedit /etc/ezyshield/policy.yaml              # set `armed: true` when you trust it
sudo systemctl restart ezyshield                 # apply the new policy

Before v0.1.0 ships, every release is a release candidate β€” the install command above detects that and prints instructions instead of installing; see Install for the release-candidate command that works today. No flags will be needed the moment v0.1.0 ships.

That's the whole loop: init leaves the daemon running in dry-run; observe first, arm only once the decisions look right.


Why EzyShield

EzyShield fail2ban CrowdSec SSHGuard
Language / runtime Go, single static binary Python Go C
Setup ezyshield init, dry-run by default jails + regex filters agent + Local API + remediation components (bouncers) small config + firewall backend
Strike escalation built in: 5min β†’ 1h β†’ 24h β†’ 7d β†’ permanent, history kept forever optional (bantime.increment, since 0.11) per-scenario durations via profiles; escalation via custom expressions yes β€” block time doubles per repeat offense
Edge enforcement (CDN/WAF) built in (Cloudflare) via bundled actions (incl. Cloudflare) yes β€” remediation components incl. Cloudflare no β€” local firewall backends only
Shared threat intel no β€” not built in today report-to actions (AbuseIPDB, DShield); no community blocklist yes β€” community blocklist + CTI; this is their core strength no
Mandatory telemetry / account none none signal sharing on by default (opt-out); console account optional none
Anti-lockout guarantees automatic β€” SSH peer + admin CIDRs allowlisted before every rule write manual ignoreip manual whitelists manual whitelisting
AI usage optional, ambiguous cases only; rule engine needs zero AI none none none

fail2ban is battle-tested and great at what it does; CrowdSec's community blocklist is genuinely valuable and something EzyShield simply doesn't have; SSHGuard is admirably small and fast. EzyShield's bet is different: strike escalation, local and edge enforcement, and guardrails that make it hard to ban yourself β€” out of the box, from a single binary. You can even run EzyShield as the brain and keep fail2ban for enforcement.

Comparison verified against each project's docs as of July 2026 β€” fail2ban, CrowdSec, SSHGuard. Corrections welcome via issues.


How it works

flowchart TD
    L["logs: SSH Β· Nginx Β· Apache Β· Caddy Β· Traefik Β· Postfix Β· Dovecot"] --> C["Collector (file tail / journald / docker)"]
    C --> P["Parser β†’ structured event"]
    P --> A["Aggregator (per-IP windows)"]
    A --> R["Rule engine β€” offline scoring, always runs"]
    R -->|ambiguous only| AI["AI analyzer (optional)"]
    AI --> D
    R --> D["Decision engine β€” strikes + TTL escalation"]
    D --> G1{{"allowlist always wins"}}
    G1 --> G2{{"anti-lockout: SSH peer / CDN range"}}
    G2 --> G3{{"dry-run by default"}}
    G3 --> G4{{"ban rate limit"}}
    G4 --> E["Enforcer β€” nftables (local) + Cloudflare / bunny.net / AWS WAF (edge)"]
    G4 --> N["Notifier β€” Telegram / Email / Slack / Discord / webhook"]

    style G1 fill:#f9e79f,stroke:#b7950b
    style G2 fill:#f9e79f,stroke:#b7950b
    style G3 fill:#f9e79f,stroke:#b7950b
    style G4 fill:#f9e79f,stroke:#b7950b
Loading

The yellow diamonds are the safety gates every would-be ban must pass β€” no rule, AI verdict, or feed can skip them. The whole path from parser to decision is side-effect-free and tested against fixture logs. Firewall changes only happen through a small privilege-separated helper (ezyshield-enforcer) that holds CAP_NET_ADMIN and accepts a fixed, minimal verb set β€” the main daemon can never run arbitrary firewall commands.

Want the narrated version? Anatomy of a Ban walks one SSH brute force through every stage, in dry-run and armed; the troubleshooting guide covers the common "why is nothing detected / why is nothing blocked" questions.

Strike escalation (configurable)

Strike Ban duration
1 5 minutes
2 1 hour
3 24 hours
4 7 days
5 permanent

Strike history is kept forever in SQLite, so a repeat offender from last month still escalates today.


Features (today)

  • Escalating bans β€” short first ban, permanent after repeated offences
  • Local enforcement β€” nftables, via a privilege-separated enforcer helper
  • Edge enforcement β€” push IP bans to a Cloudflare list, bunny.net pull-zone blocklists, and/or AWS WAFv2 IPSets for CloudFront/ALB (see the Cloudflare, bunny.net, and AWS WAF guides)
  • SSH, Nginx, Apache, Caddy, Traefik, Postfix & Dovecot parsers with fuzz-tested, panic-safe parsing of hostile input β€” web and mail servers covered out of the box
  • Deterministic rule engine β€” thresholds + scanner signatures; works with zero AI configured; detection quality is measured by a reproducible benchmark on a labeled corpus (currently 6/6 attacks detected, 0 false positives), regression-guarded in CI
  • AI-assisted decisions (optional) β€” Anthropic, any OpenAI-compatible endpoint, or local Ollama, with provider failover, a token budget, and verdict caching
  • Prompt-injection defense β€” log lines are treated as data, never instructions; AI output is schema-validated and clamped by policy (it can only suggest within limits)
  • Anti-lockout β€” active SSH peer + admin CIDRs auto-allowlisted before any rule write; allowlist always wins
  • Dry-run by default β€” nothing is enforced until you set armed: true
  • Ban rate limit β€” max_bans_per_minute (default 30) so a bad rule or poisoned feed can't ban the internet
  • Notifications β€” Telegram, Email (SMTP), Slack, Discord, generic webhook
  • Audit trail β€” every action recorded in SQLite; JSON output for scripting
  • Localhost-only dashboard β€” small web UI over 127.0.0.1 with status, active bans, allowlist, event log, live WebSocket updates and a strike timeline; CSRF-protected manual ban/unban/allow; access remotely via SSH tunnel or Cloudflare Tunnel (see docs and the remote-access guide)
  • Scriptable β€” --json on commands; unix-socket control, no TCP port ever

Your data is yours

No telemetry, no phone-home, no account, no data sharing required for any feature. The rule engine scores everything offline; the only outbound connections are the ones you configure (edge enforcement, notifiers, AI providers) or run yourself (ezyshield update). AI is opt-in, and when enabled the provider never sees your logs: it receives only aggregated counters per IP β€” event kinds, counts, and GeoIP/ASN metadata if you've configured the MaxMind databases β€” never raw log lines. CI gates enforce that secrets and hostile log content can't reach the request (prompt-injection and secret-leak tests). The complete map β€” every outbound connection, its trigger and payload, and the exact zero-outbound configuration β€” is in the data-flow reference.

Our pledge

The local agent will never lose features to a paywall. The code is and stays open under AGPL-3.0. If a paid offering ever exists, it will be about coordination at scale β€” fleets, identity, compliance, support β€” never about the protection itself. If EzyShield defends one server well, that part stays free, forever. β€” Evert


Install

Package manager (apt / dnf)

# Debian / Ubuntu
curl -fsSL https://packages.ezyshield.com/ezyshield.asc | sudo gpg --dearmor -o /usr/share/keyrings/ezyshield.gpg
echo "deb [signed-by=/usr/share/keyrings/ezyshield.gpg] https://packages.ezyshield.com/apt testing main" | sudo tee /etc/apt/sources.list.d/ezyshield.list
sudo apt update && sudo apt install ezyshield

GPG-signed repositories with .deb and .rpm for amd64/arm64 β€” dnf setup and details in the install guide. Every release today is a release candidate, so this uses the testing suite; switch to stable once v0.1.0 ships. See the supported platforms matrix for the distros and architectures exercised by the end-to-end install test.

Specific version (including release candidates)

curl -sfL https://get.ezyshield.com | sudo EZYSHIELD_VERSION=v0.1.0-rc.N sh

Check the releases page for the current tag. This is the install-script method that works today, before v0.1.0 ships.

Install script

curl -sfL https://get.ezyshield.com | sudo sh

Fetches the latest release binaries (ezyshield and ezyshield-enforcer) and verifies their SHA-256 checksums. Before v0.1.0 ships (every release today is a release candidate), this prints install instructions instead β€” see "Specific version" above.

See the install guide for all options (air-gapped mirrors, from source, upgrading).

From source (works today)

git clone https://github.com/evertramos/ezy-shield.git
cd ezy-shield
go build -o ezyshield ./cmd/ezyshield
go build -o ezyshield-enforcer ./cmd/ezyshield-enforcer
sudo mv ezyshield ezyshield-enforcer /usr/local/bin/

Requires Go 1.26+ and Linux with nftables for local enforcement.

Then:

sudo ezyshield init      # create config under /etc/ezyshield
sudo ezyshield doctor    # validate config, permissions, and dependencies

Naming: the binary is ezyshield and behaves exactly as ezy shield would in the wider ezy tool family β€” ezyshield init ≑ ezy shield init.


Basic usage

# The daemon runs as a systemd service (installed and started by `init`)
sudo systemctl status ezyshield

# Inspect the running daemon
ezyshield status

# Manual ban / unban
sudo ezyshield ban 203.0.113.42
sudo ezyshield unban 203.0.113.42

# Permanently allow an IP or CIDR
sudo ezyshield allow 198.51.100.0/24

# See active bans / allowlist / recent events
ezyshield list

# Test a notification channel without waiting for a real event
sudo ezyshield test notifier telegram

# Coming from fail2ban? Generate an equivalent setup + migration report
sudo ezyshield migrate fail2ban

Configuration

File Purpose
/etc/ezyshield/config.yaml Log sources, enforcement backends, AI providers, notifications
/etc/ezyshield/policy.yaml Score thresholds, strike table, allowlists, rate limits
/etc/ezyshield/rules.d/*.yaml Optional drop-in rule customizations β€” the built-in detection rules ship embedded in the binary and update with it; files here merge over them by name. (rules_path, whole-file replacement, is deprecated.)

Secrets (API tokens, SMTP passwords) are never stored in YAML β€” reference them as env:VARNAME or via systemd LoadCredential=. Inline secret values are rejected when the config loads, and ezyshield doctor warns on bad file permissions.

Minimal config.yaml:

data_dir: /var/lib/ezyshield

collectors:
  - kind: journald
    unit: sshd
  - kind: file
    path: /var/log/nginx/access.log

enforce:
  nftables:
    table: inet ezyshield
    set: blocked

notify:
  telegram:
    bot_token: env:EZYSHIELD_TELEGRAM_BOT_TOKEN
    chat_ids: ["-1001234567890"]

Start in dry-run (armed: false in policy.yaml), watch what it would block, then arm it and restart the daemon (sudo systemctl restart ezyshield β€” policy changes are read at startup). The full setup walkthrough β€” collectors, AI, notifications, custom rules β€” is in docs/content/en/getting-started/index.md.


Roadmap

Everything listed under Features is implemented, tested, and shipping in the current release. We are preparing the roadmap for the next versions β€” it will be published here. Ideas and requests are welcome in the issues.


Security

EzyShield is a root-capable security daemon and is built accordingly: privilege separation for firewall writes, unix-socket control (no listening TCP port), a localhost-only dashboard, anti-lockout, action rate limiting, and secrets kept out of config and logs. Every change goes through a mandatory security review.

Found a vulnerability? Please follow SECURITY.md β€” do not open a public issue for security reports.


Contributing

Contributions are welcome. Read CONTRIBUTING.md first; a CLA is required. Every PR ships code + tests + doc updates together, and CI (lint, tests, CodeQL, fuzz, security gates) must be green to merge.


❀️ Sponsors

EzyShield is free and open source, and always will be (AGPL-3.0). If it keeps your servers safer, consider sponsoring β€” it funds focused time to build this in the open, independently.

β†’ Become a sponsor


License

EzyShield is released under AGPL-3.0-only β€” see LICENSE β€” with one deliberate exception: the public SDK package pkg/sdk is Apache-2.0 (its own LICENSE), so plugin and module authors can build proprietary or differently-licensed integrations against the SDK types without AGPL obligations. The boundary is documented in pkg/sdk/README.md; every .go file carries an SPDX-License-Identifier header stating which side it is on, enforced in CI by scripts/spdx-gate.sh.

About

πŸ›‘οΈAI-assisted fail2ban alternative for Linux. Detects attacking IPs in your logs and bans them with escalating strikes β€” locally via nftables and at the Cloudflare edge. Dry-run by default. Self-hosted, AGPL-3.0.

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages