Skip to main content

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 flask import Flask
from zerov4 import arx

app = Flask(__name__)

@app.route("/")
def index():
    return "my site"

arx.run(app)          # asks for a port, then serves — protected

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.

Early development

ZeroV4 moves fast right now. Check for updates before you rely on it:

pip install --upgrade zerov4

In production, pin the version and upgrade deliberately. This is a security layer sitting in front of your app — an unplanned update is its own kind of outage.


Your first run

Make a file called run.py:

from flask import Flask
from zerov4 import arx

app = Flask(__name__)

@app.route("/")
def index():
    return "<h1>my site</h1>"

@app.route("/search")
def search():
    from flask import request
    return f"you searched: {request.args.get('q')}"

arx.run(app)

Run it:

python run.py

Answer the questions (Enter accepts every default), then open http://localhost:5000:

Try this You should get
/ your page, normally
/search?q=hello your page, normally
/search?q=union select 1 429 — blocked before your route ran
/.env 403 — honeypot, instant ban
/panel the SOC panel

Banned yourself while testing? That is the system working. Let yourself back in:

python -c "from zerov4 import arx; arx.unblock('127.0.0.1')"

Or delete the logs/ folder to reset everything.

Throughout this README, myapp means your own file, e.g. myapp.py in the same folder. It is not something you install. If your app lives in shop.py as app, then it's from shop import app and arx.run("shop:app"). Avoid naming your file after a package that already exists on PyPI — Python will import that one instead of yours.


Usage

The quick way

If your app is in the same file, hand it over directly:

from zerov4 import arx

arx.run(app)                 # menu, then serve
arx.run(app, port=8080)      # port answered, so not asked
arx.run(interactive=False)   # no questions at all

If it lives in another file — say myapp.py, with the app named app — import it, or let ZeroV4 do the importing:

from myapp import app       # your file, in this folder
arx.run(app)
arx.run("myapp:app")        # module:attribute, same thing

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 — myapp.py in this folder, app inside it
zerov4 status            # what the firewall knows
zerov4 unblock 1.2.3.4   # let an address back in
zerov4 whitelist 10.0.0.5
zerov4 signatures        # what's loaded
zerov4 feedback events   # every block/throttle, with an id
zerov4 feedback wrong ID # mark one as a false positive

Run these from the folder your app is in — zerov4 run imports it the same way Python would.


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).

Apache 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zerov4-0.1.9.tar.gz (233.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zerov4-0.1.9-py3-none-any.whl (541.2 kB view details)

Uploaded Python 3

File details

Details for the file zerov4-0.1.9.tar.gz.

File metadata

  • Download URL: zerov4-0.1.9.tar.gz
  • Upload date:
  • Size: 233.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for zerov4-0.1.9.tar.gz
Algorithm Hash digest
SHA256 58d21d35e2945ee2004414e9491392f52b5ccb42098a78e1ec177394197090ba
MD5 b7e3b01083d55376396e98a294cebdad
BLAKE2b-256 47354149211bbbb0d991617f792c73f1aa49c2990bf61a6371ae4e8487992ceb

See more details on using hashes here.

File details

Details for the file zerov4-0.1.9-py3-none-any.whl.

File metadata

  • Download URL: zerov4-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 541.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for zerov4-0.1.9-py3-none-any.whl
Algorithm Hash digest
SHA256 7821caffa7a4ad4a19df72cae8c57cff3188a789bf3b50ad93c78f7f5a81301e
MD5 5117e72cc8a4a76ef5d90cba819b8ee4
BLAKE2b-256 87ced2b038af0f23cfd57acff3079f0e18e016482d00c35459b0d834e48afd1f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page