πŒ…πŒ‹πŒ€πŒ”πŒŠ-α•“κŠπŒ΅πŒ‚π‹…

v1.2.0

Bot challenge middleware for Flask. Issues CAPTCHA challenges, grants signed JWT cookies, supports Redis β€” drop-in protection.

Challenge Previews

Checking your browser

Starting challenge…

Checking your browser

Starting challenge…

Type the characters shown

CAPTCHA

Select the matching image

Reference

Select the matching orientation

Reference
Reference orientation
Your choice
Choice A

Slide the piece into place

Click the incomplete circle

Trace the curve

Press and drag from the green dot to the red dot.
Awaiting trace…

Select all images of

smiling dog

Type the characters you hear

Checking your browser

Challenge types

Eight built-in challenges. Swap handlers at construction time β€” no template changes required.

PoW

SHA-256 / Balloon

Client-side proof-of-work. Configurable difficulty β€” cheap for browsers, expensive for bots at scale.

IMG

Image / Grid CAPTCHA

Distorted character images and click-grid challenges that defeat OCR-based solvers.

UX

Rotation / Sliding / Circle

Drag-based interaction challenges. High pass rate for humans, hard to automate reliably.

3P

Third-party CAPTCHAs

Altcha, Arkose, CaptchaFox, GeeTest, MTCaptcha β€” pass your credentials, Vouch handles the verification flow.

🎧

Audio CAPTCHA

Spoken digit sequences for accessibility. Layered noise makes automated transcription difficult.

Nav

Navigator Attestation

Checks browser API surface β€” navigator, screen, plugins β€” to detect headless environments without a visible challenge.

IP

IP Blocklist

Built-in blocklist with CIDR support. Combines with policy rules to hard-deny known bad actors before any challenge is issued.

app.py
from flask import Flask
from flask_vouch import Vouch

app = Flask(__name__)
vouch = Vouch(app, secret="change-me")  # wraps every route

# Application factory pattern
vouch = Vouch(secret="change-me")
vouch.init_app(app)

# Route decorators
@vouch.exempt  # skip check entirely
@vouch.protect  # always challenge
@vouch.block   # deny non-vouched, no challenge

# Access claims in view
import flask
claims = flask.g.vouch  # is_crawler, crawler_name, score …