Lightweight validation for Flask request args and form data
Project description
🚀 flask-request-validate
Lightweight, decorator-based input validation for Flask apps
🚧 Pre-release Feedback Wanted
I'm looking for feedback before publishing to PyPI:
👉 https://github.com/blainekwilson/flask-request-validate/discussions
Would especially appreciate input on API design and validation patterns.
🎯 Why this exists
If you are building Flask apps, there are established frameworks you should consider first.
- 🧱 Server-side rendered HTML apps → use Flask-WTF
- 🔌 REST APIs → use Marshmallow or Pydantic
👉 In practice, a large number of Flask applications still don’t use a structured validation framework and rely on ad hoc validation instead.
👉 flask-request-validate exists to provide a lightweight, auditable alternative for those cases.
✨ What makes this different
- ✅ Decorator-based validation — no forms, no schemas
- ✅ Works with query strings + form data
- ✅ Field-level error handling
- ✅ Custom error responses (HTML, JSON, anything)
- 🔥 Security audit tool to detect unprotected routes
⚡ Quick example
from flask import Flask, request
import flask_request_validate as fv
app = Flask(__name__)
@app.route("/submit", methods=["POST"])
@fv.validate({
"args": {
"st": {"required": True, "rules": fv.US_STATE}
},
"form": {
"zip": {"required": False, "rules": fv.US_ZIP}
}
})
def submit():
return f"State: {request.args['st']}"
🧠 Error handling (simple → advanced)
Default (HTML response)
@fv.validate(schema)
def route():
...
Custom error handler (recommended)
def json_error_handler(result):
return {"errors": result["errors"]}, 400
@fv.validate(schema, on_error=json_error_handler)
def route():
...
Field-level errors
{
"errors": {
"zip": ["Invalid ZIP code"],
"st": ["Invalid US state"]
}
}
🔐 Built-in Security Audit
Find routes that accept input without validation.
Run it:
python -m audit_security app:app
What it detects
- ✅ Routes protected with @validate
- ⚪ Routes explicitly excluded
- ❌ Routes missing validation (potential risk)
Example output
🔍 Flask Validate Security Audit Report
==================================================
📊 OVERALL SUMMARY:
Total routes analyzed: 42
✅ Protected routes: 38
⚪ Excluded routes: 2
❌ Unprotected routes: 2
🔒 Security Score: 95.2%
❌ UNPROTECTED ROUTES:
🚨 POST /api/admin (high priority)
ℹ️ GET / (low priority)
🧩 When to use this
Use flask-validate when:
- You want simple, explicit validation and you aren't using an existing framework
🚫 When NOT to use this
- UI apps → use Flask-WTF
- API-first apps → use Pydantic or Marshmallow
📦 Installation (development)
pip install -e .
Planned: PyPI release as flask-request-validate
🛡️ Security-first design
- Inspired by OWASP validation guidance
- Built to reduce common input validation mistakes
- Includes runtime auditing for missing protections
🔐 Security Response Headers (enabled by default)
flask-validate now applies several security HTTP response headers by default to every Flask response: How it works
- Headers applied:
Content-Security-Policy,X-Frame-Options,X-Content-Type-Options,Referrer-Policy,Permissions-Policy, andStrict-Transport-Security(configurable).
How to customize
- Global defaults (process-wide): mutate
fv.SECURITY_HEADER_DEFAULTSbefore creating yourFlaskapp. - Per-app override: set
app.config['FLASK_REQUEST_VALIDATE_SECURITY_HEADERS']to a dict of header overrides before running the app. - Per-route override: pass
security_headersto the@fv.validate(..., security_headers=...)decorator for route-level control.
Server header stripping
- The package attempts to remove/blank the
Serverheader to avoid leaking server/version information. It installs WSGI middleware that filtersServerfrom response headers and also monkeypatches Werkzeug's handler version string when possible. - Note: some development servers may still emit an empty
Server:header; removing the header entirely is best handled at the HTTP server/proxy layer (nginx/Gunicorn, etc.).
Auto-initialization and init_app
- By default
flask_request_validate.init_app(app)is called automatically for everyFlaskapp created after the module is imported (we monkeypatchFlask.__init__to callinit_app). You can still callfv.init_app(app)explicitly — it is idempotent. init_appwrapsapp.wsgi_appwith the Server-header-stripping middleware and registers theafter_requesthook that applies headers and (optionally) injects CSRF tokens.
Exports
fv.SECURITY_HEADER_DEFAULTS: module-level dict of header defaults (exported so callers can mutate before app creation).
Tests and examples
- Test coverage for headers is in
tests/test_security_headers.py. - Examples:
examples/sample_login.py(now demonstrates CSRF injection),examples/example_override_header.py.
Automatic CSRF injection and validation
-
Default: Automatic CSRF injection/validation is enabled by default via
fv.AUTO_CSRF_DEFAULT = True.- Disable globally before creating apps: set
fv.AUTO_CSRF_DEFAULT = False. - Disable per-app:
app.config['FLASK_REQUEST_VALIDATE_AUTO_CSRF'] = False.
- Disable globally before creating apps: set
-
Sessions required: You must set
app.secret_key(or configure a session backend) for server-side token storage. The library only injects/enforces CSRF when a session secret is available.
app.secret_key = 'a-secure-secret'
What it does when enabled
- Injects a hidden input named
flask_request_validate_csrf_tokeninto any HTML response that contains</form>. - Stores a server-side token in the user's session using a randomized session key (prefixed with
fv_csrf_). Tokens are single-use and consumed on successful validation. - For inbound form submissions (
application/x-www-form-urlencodedormultipart/form-data) on routes decorated with@fv.validate(...), the decorator will validate the submitted token against session-stored tokens.
Notes & guidance
- Tokens are single-use and stored under randomized session keys to avoid predictable session layout.
- If you prefer custom behavior (different field name, TTL, persistent tokens), disable
AUTO_CSRF_DEFAULTand implement your own lifecycle.
Check-unprotected-routes secret-key detection
fv.check_unprotected_routes(app)now detects whetherapp.secret_keyis set and will emit a warning if sessions are not configured (CSRF injection/validation will not function). The returned status dict includes the boolean keysecret_key_set.
🧭 Roadmap
- form field validation
- query string validation
- audit for unprotected endpoints
- throw error if additional fields found
- support error function callback and HTML output
- CSRF Token Support (by default)
- Security Response Headers (by default)
- PyPI release
- Authentication and Authorization
- Custom rule extensions
- Enhanced reporting + CLI options
🤝 Contributing
⭐ Why this project stands out
Most validation libraries focus on:
- full frameworks
- API schemas
This one focuses on Flask applications with no validation frameworks.
And adds something most don’t:
🔥 Runtime detection of missing validation (security auditing)
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 flask_request_validate-0.1.2.tar.gz.
File metadata
- Download URL: flask_request_validate-0.1.2.tar.gz
- Upload date:
- Size: 31.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bfbbda4db3994a134ff0f7ba7e57e4598901454dc1f176c7a27a66e06b35f43c
|
|
| MD5 |
cd801c80694a94413b3e7813c4c8caa2
|
|
| BLAKE2b-256 |
174b54262a7c7f1013ee42f764cff60e6690c9091ddddbfd46b49563809b5a35
|
File details
Details for the file flask_request_validate-0.1.2-py3-none-any.whl.
File metadata
- Download URL: flask_request_validate-0.1.2-py3-none-any.whl
- Upload date:
- Size: 22.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a068e6663412ee3059c99e53d4c6d018204e98dd62a021514a5aec032bc8320c
|
|
| MD5 |
18f0bd5a5512785037f70f08e560fd29
|
|
| BLAKE2b-256 |
36988aaee44a2a963dd7fa325d7283c9bb0f9fba96c22c8686f4e69197e85daf
|