Security essentials for Flask APIs — CORS, Talisman security headers, rate
99
94%
Does it follow best practices?
Impact
100%
1.17xAverage score across 10 eval scenarios
Passed
No known issues
{
"context": "The agent was given a Flask API with in-memory rate limiting and no reverse proxy middleware, deployed behind nginx with multiple Gunicorn workers. The scorer checks app_fixed.py for ProxyFix middleware and Redis-backed storage, and diagnosis.md for correct root cause identification.",
"type": "weighted_checklist",
"checklist": [
{
"name": "ProxyFix imported",
"description": "app_fixed.py imports ProxyFix from werkzeug.middleware.proxy_fix",
"max_score": 8
},
{
"name": "ProxyFix applied",
"description": "app_fixed.py applies ProxyFix to app.wsgi_app (e.g., app.wsgi_app = ProxyFix(app.wsgi_app, ...))",
"max_score": 10
},
{
"name": "ProxyFix x_for=1",
"description": "ProxyFix is called with x_for=1",
"max_score": 7
},
{
"name": "ProxyFix x_proto=1 and x_host=1",
"description": "ProxyFix is called with both x_proto=1 and x_host=1",
"max_score": 7
},
{
"name": "Redis storage_uri present",
"description": "The Limiter is configured with a storage_uri parameter pointing to a Redis URL (e.g., redis://localhost:6379/0 or similar)",
"max_score": 12
},
{
"name": "No in-memory default",
"description": "The Limiter does NOT rely on in-memory storage — it does not omit storage_uri or use storage_uri=None",
"max_score": 8
},
{
"name": "IP diagnosis mentioned",
"description": "diagnosis.md explains that without ProxyFix, Flask sees the proxy IP (127.0.0.1) as the client IP, causing all requests to share one rate-limit bucket",
"max_score": 12
},
{
"name": "Multi-worker diagnosis mentioned",
"description": "diagnosis.md explains that in-memory storage means each Gunicorn worker maintains its own counter, so limits are multiplied by the worker count and reset on restart",
"max_score": 12
},
{
"name": "Redis fix rationale",
"description": "diagnosis.md explains that a shared backend (Redis) is required so all workers use the same counters",
"max_score": 8
},
{
"name": "requirements.txt present",
"description": "A requirements.txt file is present and includes flask-limiter with the redis extra (e.g., flask-limiter[redis]) or lists redis separately",
"max_score": 8
},
{
"name": "Existing routes preserved",
"description": "All three original routes (GET /api/products, POST /api/orders, DELETE /api/orders/<id>) are still present in app_fixed.py",
"max_score": 8
}
]
}evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
skills
flask-security-basics
verifiers