Skip to main content

Bot-challenge Python middleware issuing brief challenges, granting solvers signed access cookies.

Project description

𐌅𐌋𐌀𐌔𐌊-ᕓꝊ𐌵𐌂𐋅

Bot-challenge middleware for Flask — intercepts unrecognized visitors, issues proof-of-work or CAPTCHA challenges, and grants HMAC-signed JWT access cookies to solvers.

PyPI Python License Issues Stars Downloads

from flask import Flask
from flask_vouch import Vouch

app = Flask(__name__)
vouch = Vouch(app, secret="change-me")

Bots get a browser challenge page. Humans solve it once, get a cookie, browse freely.

Install

pip install flask-Vouch

Optional extras:

pip install flask-Vouch[image]      # image-based captchas (Pillow)
pip install flask-Vouch[rotation]   # RotationCaptcha (Pillow, numpy)
pip install flask-Vouch[audio]      # audio captcha (numpy, scipy)

How it works

  1. Every request without a valid signed cookie is intercepted.
  2. A proof-of-work challenge (SHA-256 Balloon by default) is issued.
  3. The browser solves it in JavaScript and POSTs to /.tollbooth/verify.
  4. A valid solution sets a signed JWT cookie — subsequent requests pass through.

Quick start

from flask import Flask
from flask_vouch import Vouch

app = Flask(__name__)
vouch = Vouch(app, secret="change-me")

@app.route("/")
def index():
    return "You passed the challenge!"

@app.route("/internal")
@vouch.exempt
def internal():
    return "ok"

Application factory:

vouch = Vouch(secret="change-me")

def create_app():
    app = Flask(__name__)
    vouch.init_app(app)
    return app

SECRET_KEY fallback — if no secret= is passed, app.config["SECRET_KEY"] is used automatically:

app.config["SECRET_KEY"] = "change-me"
vouch = Vouch()
vouch.init_app(app)

Configuration

Pass as kwargs or via app.config with the VOUCH_ prefix:

Parameter Default Description
secret SECRET_KEY HMAC/JWT signing key
policy default rules Policy instance
exclude [] Path regexes to skip entirely
json_mode False Return JSON challenge instead of HTML
cookie_name _tollbooth Access cookie name
cookie_ttl 604800 Cookie lifetime in seconds (7 days)
verify_path /.tollbooth/verify Challenge verification endpoint
challenge_handler SHA256Balloon Challenge implementation
blocklist None IPBlocklist instance or list of them
app.config["VOUCH_COOKIE_NAME"] = "_v"
app.config["VOUCH_COOKIE_TTL"] = 3600

Route decorators

Decorator Behavior
@vouch.exempt Skip challenge entirely for this route
@vouch.protect Always run challenge check (overrides global allow)
@vouch.challenge Always issue a challenge regardless of policy
@vouch.block Deny detected crawlers outright; challenge or pass others

Custom rules

from flask_vouch import Vouch, Policy, Rule

policy = Policy(
    rules=[
        Rule(name="allow-google", action="allow", user_agent="Googlebot"),
        Rule(name="block-scrapers", action="deny", user_agent="AhrefsBot|SemrushBot"),
        Rule(name="challenge-curl", action="challenge", difficulty=8, user_agent="curl"),
    ]
)

vouch = Vouch(app, secret="s", policy=policy)

Load the built-in ruleset from rules.json:

from flask_vouch import load_policy

vouch = Vouch(app, secret="s", policy=load_policy())

Rule fields:

Field Type Description
name str Identifier
action str allow · deny · challenge · weigh
user_agent str (regex) Match on User-Agent header
path str (regex) Match on request path
headers dict Match on arbitrary headers (regex values)
remote_addresses list[str] CIDR ranges to match
difficulty int Challenge difficulty (default: policy)
weight int Score added when action=weigh
blocklist bool Match IPs in the loaded blocklist
bogon_ip bool Match non-global / bogon IPs
crawler bool Match detected crawler user agents

Challenge types

from flask_vouch import (
    SHA256Balloon,              # default, proof of work    (balloon hashing)
    SHA256,                     # lightweight SHA-256 PoW
    CharacterCaptcha,           # text CAPTCHA
    ImageCaptcha,               # image CAPTCHA             (requires Pillow)
    RotationCaptcha,            # rotation CAPTCHA          (requires Pillow, numpy)
    SlidingCaptcha,             # sliding puzzle            (requires Pillow)
    CircleCaptcha,              # circle select CAPTCHA     (requires Pillow)
    ImageGridCaptcha,           # image grid CAPTCHA        (requires Pillow)
    AudioCaptcha,               # audio CAPTCHA             (requires numpy, scipy)
    NavigatorAttestation,       # browser signal attestation
    ThirdPartyCaptchaChallenge, # embed external CAPTCHAs
)

vouch = Vouch(app, secret="s", challenge_handler=CharacterCaptcha())

IP blocklist

from flask_vouch import Vouch, IPBlocklist

bl = IPBlocklist()   # defaults to bundled blocklist URL
bl.load()
bl.start_updates()   # auto-refresh daily in a daemon thread

vouch = Vouch(app, secret="s", blocklist=bl)

Multiple blocklists:

vouch = Vouch(app, secret="s", blocklist=[bl1, bl2])

Redis backend

For multi-process / multi-worker deployments:

import redis
from flask_vouch.redis import RedisEngine
from flask_vouch import Vouch

r = redis.Redis()
engine = RedisEngine(r, secret="s")
vouch = Vouch(app, engine=engine)

Extras

ErrorHandler

from flask_vouch.extras import ErrorHandler

eh = ErrorHandler(bouncer=vouch)
eh.init_flask(app)

RateLimiter

from flask_vouch.extras import RateLimiter

rl = RateLimiter(default="100/minute")
rl.init_flask(app)

@app.route("/login")
@rl.limit("5/minute")
def login(): ...

ThirdPartyCaptcha

from flask_vouch.extras import ThirdPartyCaptcha

tpc = ThirdPartyCaptcha(turnstile_site_key="...", turnstile_secret="...")
tpc.init_flask(app)

@app.route("/submit", methods=["POST"])
def submit():
    if not tpc.is_turnstile_valid():
        abort(403)
    ...

Formatting

pip install black isort
isort . && black .
npx prtfm

License

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

flask_vouch-1.0.2.tar.gz (111.5 kB view details)

Uploaded Source

Built Distribution

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

flask_vouch-1.0.2-py3-none-any.whl (128.7 kB view details)

Uploaded Python 3

File details

Details for the file flask_vouch-1.0.2.tar.gz.

File metadata

  • Download URL: flask_vouch-1.0.2.tar.gz
  • Upload date:
  • Size: 111.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for flask_vouch-1.0.2.tar.gz
Algorithm Hash digest
SHA256 84fe7cc073cb4d357c2ac5bbf43913de4b5a19cf9e14a2c1fd9c7321635aaff7
MD5 41a9bd0be7221f9b1566da1a0889008a
BLAKE2b-256 579b5479b8a2e0aa88f470a4b391311c850f10dece56a359cc6116e0993bab66

See more details on using hashes here.

Provenance

The following attestation bundles were made for flask_vouch-1.0.2.tar.gz:

Publisher: publish.yml on tn3w/flask-Vouch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file flask_vouch-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: flask_vouch-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 128.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for flask_vouch-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 84bc40cacc167c0437a803ce6248ce0028f2c08b6431e7b2065be21decc1a1be
MD5 ced0cb9963e72b542b095670c75fe5f6
BLAKE2b-256 8bd772d06bffd6ff9838409def5d2ae3583bff70288dc80a306a94b1c3eba073

See more details on using hashes here.

Provenance

The following attestation bundles were made for flask_vouch-1.0.2-py3-none-any.whl:

Publisher: publish.yml on tn3w/flask-Vouch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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