CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl-labs/flask-security-basics

Security essentials for Flask APIs — CORS, Talisman security headers, rate

99

1.17x
Quality

94%

Does it follow best practices?

Impact

100%

1.17x

Average score across 10 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

criteria.jsonevals/scenario-7/

{
  "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

tile.json