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-3/

{
  "context": "The agent was asked to review a Flask API with several deliberate security flaws and produce both a written list of issues and a corrected file. The scorer checks security_issues.md for correct identification of problems and restaurant_api_fixed.py for correct remediation of each flaw.",
  "type": "weighted_checklist",
  "checklist": [
    {
      "name": "Wildcard CORS identified",
      "description": "security_issues.md explicitly identifies the CORS wildcard origins='*' as a security problem.",
      "max_score": 6
    },
    {
      "name": "Hardcoded secret identified",
      "description": "security_issues.md identifies the hardcoded SECRET_KEY as a security problem.",
      "max_score": 6
    },
    {
      "name": "Debug mode identified",
      "description": "security_issues.md identifies app.debug = True as unsafe for production.",
      "max_score": 5
    },
    {
      "name": "Permissive rate limit identified",
      "description": "security_issues.md flags the 1000-per-minute global default as excessively permissive.",
      "max_score": 5
    },
    {
      "name": "Missing per-route limits identified",
      "description": "security_issues.md notes that the POST, PATCH, and DELETE routes have no per-route rate limits.",
      "max_score": 5
    },
    {
      "name": "CORS fixed with env var",
      "description": "In restaurant_api_fixed.py, CORS is initialized with origins from os.getenv('ALLOWED_ORIGINS', 'http://localhost:3000').split(',') or equivalent, not with '*'.",
      "max_score": 10
    },
    {
      "name": "SECRET_KEY from env",
      "description": "In restaurant_api_fixed.py, SECRET_KEY is loaded from os.getenv('SECRET_KEY') and not hardcoded.",
      "max_score": 10
    },
    {
      "name": "SECRET_KEY startup guard",
      "description": "In restaurant_api_fixed.py, there is a startup check that raises a RuntimeError (or equivalent) if SECRET_KEY is missing outside TESTING mode.",
      "max_score": 8
    },
    {
      "name": "Global rate limit corrected",
      "description": "In restaurant_api_fixed.py, the global default_limits is set to 200 per hour or less (not 1000 per minute).",
      "max_score": 8
    },
    {
      "name": "POST route limited",
      "description": "In restaurant_api_fixed.py, the POST /orders route has a @limiter.limit(...) decorator with a limit stricter than the global default.",
      "max_score": 7
    },
    {
      "name": "PATCH route limited",
      "description": "In restaurant_api_fixed.py, the PATCH /orders/<id> route has a @limiter.limit(...) decorator with a limit stricter than the global default.",
      "max_score": 7
    },
    {
      "name": "DELETE route limited",
      "description": "In restaurant_api_fixed.py, the DELETE /orders/<id> route has a @limiter.limit(...) decorator with a limit stricter than the global default.",
      "max_score": 7
    },
    {
      "name": "Debug mode disabled",
      "description": "In restaurant_api_fixed.py, app.debug = True is removed or changed to False and no debug=True appears in app.run().",
      "max_score": 6
    },
    {
      "name": "Routes preserved",
      "description": "All four original routes (GET /menu, POST /orders, DELETE /orders/<id>, PATCH /orders/<id>) are still present and functional in restaurant_api_fixed.py.",
      "max_score": 5
    },
    {
      "name": "Both files present",
      "description": "Both security_issues.md and restaurant_api_fixed.py exist as files.",
      "max_score": 5
    }
  ]
}

evals

tile.json