Request-level protection for WSGI applications: signatures, rate limiting, honeypots, IP reputation and a bot challenge, inspected before your app sees the request.
Project description
ZeroV4
Request-level protection for WSGI applications. Signatures, rate limiting, honeypots, IP reputation, and a bot challenge — all applied before your app sees the request.
from zerov4 import arx
from myapp import app
arx.run(app)
That's it. ZeroV4 asks for a port, a debug flag and a protection level, then serves your app with every request inspected first.
Install
pip install zerov4
Optional extras:
pip install zerov4[redis] # Redis-backed storage
pip install zerov4[geoip] # country lookup for blocked IPs
pip install zerov4[prod] # waitress, for serving in production
pip install zerov4[all]
Core dependency: werkzeug. That's the whole list. A security package that pulls in half of PyPI has a larger attack surface than the attacks it stops.
Usage
The quick way
from zerov4 import arx
from myapp import app
arx.run(app) # menu, then serve
arx.run(app, port=8080) # port answered, so not asked
arx.run("myapp:app") # import it by name
arx.run(interactive=False) # no questions at all
Under gunicorn
run() serves the app itself. If gunicorn/uwsgi is doing that, wrap instead:
# wsgi.py
from zerov4 import arx
from myapp import app
application = arx.wrap(app)
gunicorn wsgi:application -w 4
As middleware
from flask import Flask
from zerov4 import ZeroGuard, make_config
app = Flask(__name__)
app.wsgi_app = ZeroGuard(app.wsgi_app, cfg=make_config("balanced"))
It's plain WSGI, so Django, Bottle, Pyramid and anything else that speaks the protocol work the same way.
From the shell
zerov4 init # configure interactively, write zerov4.toml
zerov4 run myapp:app # serve
zerov4 status # what the firewall knows
zerov4 unblock 1.2.3.4
zerov4 whitelist 10.0.0.5
zerov4 signatures # what's loaded
Protection levels
permissive |
balanced (default) |
paranoid |
|
|---|---|---|---|
| Ban threshold | 20 hits | 10 hits | 10 hits |
| Rate limit | 300 / 10s | 100 / 10s | 10 / 1s |
| 404s count as attacks | no | no | yes |
| Non-JSON body is an attack | no | no | yes |
| Fuzzy keyword matching | no | no | yes |
| Session bound to IP | no | no | yes |
| Bot challenge | off | on | on |
| Pattern analysis | off | on | on |
| Blocking throttle | no | no | yes |
Balanced is tuned for a site with real visitors and is what you want unless you have a reason.
Paranoid is the original Autonomous Agent Zero-V4 behaviour, kept intact. It treats ordinary traffic as hostile: every 404, every HTML form post, and fuzzy keyword matches all count toward a ban. Mobile users switching from WiFi to cellular change IP, which paranoid reads as session theft. It is for a target you control, not a public site. ZeroV4 asks you to confirm before enabling it.
What it does
Signatures. Keyword matching plus stateful detect() functions that count across a time window. Keywords are word-bounded, so select does not match /products/selectbox.
Escalating throttle. Hit 1 costs 2 seconds, hit 2 costs 4, hit 9 costs 300 (capped). Hit 10 is a permanent ban. Throttling returns 429 with Retry-After immediately — it does not hold a worker asleep.
IP reputation. Banned addresses are remembered and blocked on sight next time, without earning hits again. Entries expire when they haven't been seen for a while, so an address that changed hands isn't blocked forever.
Honeypots. /.env, /wp-admin, /phpmyadmin and friends. One request is an instant ban.
Bot challenge. Throttled browsers get an arithmetic question plus pointer-movement analysis instead of a flat 429, so a real person can prove it and carry on. Touch devices are not punished for having no mousemove.
Pattern analysis. Correlates across IPs: coordinated attacks, slow reconnaissance, multi-category probing, rapid escalation.
Feedback loop. Mark a decision wrong; after enough marks for the same signature, ZeroV4 tells you which file to look at. It never edits its own rules, never unbans on its own, never weakens anything by itself. The decision is yours.
SOC panel. Live stats, blocked list, reputation, whitelist, event log, suggestions. Password-protected, hash in .env.
Configuration
from zerov4 import make_config, arx
cfg = make_config(
"balanced",
port=8080,
trusted_proxies=("10.0.0.0/8",), # required behind nginx/Cloudflare
redis_url="redis://localhost:6379",
geoip_db="GeoLite2-Country.mmdb",
)
arx.run(app, cfg=cfg)
Or zerov4.toml:
[zerov4]
level = "balanced"
port = 5000
storage = "auto"
panel_enabled = true
trusted_proxies = ["10.0.0.0/8"]
Or the environment: ZEROV4_PORT=8080 ZEROV4_LEVEL=paranoid.
Behind a proxy
Set trusted_proxies if anything sits in front of your app. Forwarding headers are only honoured when the direct peer is on that list. Without it, an attacker sends X-Real-IP: <your customer> and gets your customer banned while staying clean. With no proxy configured, REMOTE_ADDR is used and headers are ignored.
Hooks
ZeroV4 does not touch your machine's network, kill processes, or click anything. If you want that, wire it up:
def on_coordinated(ips, reason):
print(f"{len(ips)} attackers: {reason}")
# your own response here
cfg = make_config("balanced",
on_coordinated_attack=on_coordinated,
on_ban=lambda ip, attack, entry: notify(ip),
on_threat_cleared=lambda: print("clear"))
Custom signatures
A signature is a module with a NAME and either KEYWORDS, a detect() function, or both.
# signatures/my_rule.py
NAME = "Admin Panel Probing"
KEYWORDS = ["/wp-login", "/administrator"]
def detect(line, parsed, now, counters):
ip = parsed["ip"]
if "admin" in line.lower():
counters["admin_probe"][ip].append(now)
counters["admin_probe"][ip] = [
t for t in counters["admin_probe"][ip] if now - t <= 60
]
if len(counters["admin_probe"][ip]) >= 5:
return f"{len(counters['admin_probe'][ip])} admin probes in 60s"
return None
Point at your own directory:
cfg = make_config("balanced", signatures_dir="./signatures")
A signature that raises is logged and quarantined for the run, not allowed to take the request down with it.
Loading a signature directory imports and executes those .py files. Don't point it at a directory other people can write to.
Panel
zerov4 init # asks for a panel password
The password is never written to disk — only a PBKDF2-SHA256 hash, into .env at mode 0600. Add .env to your .gitignore; ZeroV4 warns you if you haven't.
Panel lives at /panel by default. It fails closed: no password set means no logins, not an open door.
API
from zerov4 import arx
arx.run(app) # serve
arx.wrap(app) # wrap only (gunicorn)
arx.stop()
arx.status() # stats dict
arx.blocked() # list of blocked IPs
arx.reputation() # learned bad IPs
arx.attacks(20) # recent detections
arx.is_blocked("1.2.3.4")
arx.unblock("1.2.3.4")
arx.ban("1.2.3.4")
arx.whitelist("10.0.0.5") # never blocked
arx.whitelist() # list
arx.events() # firewall decisions, with ids
arx.wrong(event_id) # mark a false positive
arx.suggestions() # signatures worth reviewing
arx.set_panel_password()
arx.reload_signatures()
Notes
Locked out? A whitelisted address is never blocked by any rule. Add yours before tightening anything: zerov4 whitelist YOUR.IP. Whitelisting an already-banned IP clears the ban.
Storage. Redis if reachable, JSON files otherwise, both automatic. storage="redis" fails loudly instead of silently degrading — a security system whose bans quietly stop persisting is worse than one that tells you.
Multiple workers. Under gunicorn -w 4, use Redis. With JSON, each worker keeps its own view and rate limits count per worker.
Failure mode. If ZeroV4 itself crashes mid-inspection, the request is allowed and the error is logged. A bug here should cost you protection, not uptime.
Threads. A background agent handles pattern analysis and cleanup, and a saver persists state periodically. Both are daemon threads that stop cleanly.
Status
Beta. Test it against your own traffic before putting it in front of anything that matters — start at permissive or balanced, watch what gets blocked, then tighten. A misconfigured WAF either bans your users or waves the attacker through, and only your traffic can tell you which one you have.
Credits
Derived from Autonomous Agent Zero-V4 by Barış (BRSX-Labs).
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zerov4-0.1.0.tar.gz.
File metadata
- Download URL: zerov4-0.1.0.tar.gz
- Upload date:
- Size: 233.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6fdc3775845e93d61d3daef8a265b7ab687ef425bfb668b775e283c138e16250
|
|
| MD5 |
56cc13ee7ffd1960bbc04a191324f1c8
|
|
| BLAKE2b-256 |
f182ee94a0a47bbc3fedbf007cc6df8ab90f41b3ad154c9d4695c16df622f3ad
|
File details
Details for the file zerov4-0.1.0-py3-none-any.whl.
File metadata
- Download URL: zerov4-0.1.0-py3-none-any.whl
- Upload date:
- Size: 546.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5d31d6eae3ece2a224a02759a744b44aa3d5b331c71b8e21f0473ebc1f87661
|
|
| MD5 |
1607d7e4cd8f7cbfe1f64bc1eb1a6045
|
|
| BLAKE2b-256 |
a619adab1ed6121377b94bb6f412062ac65bc38c72398bcc286774f150b335e0
|