Flask security
Skill hlsitechio/claude-skills-security/appsec-stack-pack/flask-security
Defensive security audit skills for Claude — tech-stack-keyed and audit-domain-keyed packs for SaaS apps.
npx -y skills add hlsitechio/claude-skills-security --skill flask-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Security audit for Flask applications including Jinja2 autoescape bypass, Flask-Login session handling, Flask-WTF CSRF protection, Blueprint structure, app.config secrets, SQL via Flask-SQLAlchemy, file uploads, custom decorators for auth, and Flask-specific extensions. Use this skill whenever the user mentions Flask, flask app, Blueprint, Flask-Login, Flask-WTF, Flask-SQLAlchemy, Flask-RESTful, Flask-Admin, render_template, or asks "audit my Flask app", "Flask security review". Trigger when the codebase contains `flask` in `requirements.txt` / `pyproject.toml` or `from flask import` patterns.
SKILL.md
7.8 KB, as published. Nobody here has run it
Flask Security Audit
Audit Flask applications. Flask is less opinionated than Django, so security depends heavily on developer choices.
When this skill applies
- Reviewing Flask app structure, routes, blueprints
- Auditing Jinja2 templates for XSS
- Reviewing Flask-Login / Flask-WTF / Flask-SQLAlchemy setup
- Checking app.config for secret handling
Workflow
Follow ../_shared/audit-workflow.md.
Phase 1: Stack detection
grep -E '^[Ff]lask|"flask"' requirements.txt pyproject.toml 2>/dev/null
python -c "import flask; print(flask.__version__)" 2>/dev/null
Phase 2: Inventory
# App factory pattern
grep -rn 'def create_app\|Flask(__name__)' . --include='*.py' 2>/dev/null
# Routes
grep -rn '@app.route\|@.*\.route\|@bp\.' . --include='*.py' 2>/dev/null | head -50
# Templates
grep -rn 'render_template\|render_template_string' . --include='*.py' 2>/dev/null
# Jinja safe filter / autoescape
grep -rn '|safe\|autoescape\|Markup(' . --include='*.py' --include='*.html' 2>/dev/null
# Extensions
grep -nE 'flask-login|flask-wtf|flask-sqlalchemy|flask-restful|flask-admin|flask-cors' requirements.txt pyproject.toml 2>/dev/null
Phase 3: Detection — the checks
App configuration
- FLK-CFG-1
SECRET_KEYfrom env, not hardcoded. Generate withsecrets.token_urlsafe(64). - FLK-CFG-2 Different config classes for dev/prod,
DEBUG=Falsein prod. - FLK-CFG-3
SESSION_COOKIE_SECURE=True,SESSION_COOKIE_HTTPONLY=True,SESSION_COOKIE_SAMESITE='Lax'. - FLK-CFG-4
PERMANENT_SESSION_LIFETIMEset to a sensible value. - FLK-CFG-5
WTF_CSRF_TIME_LIMITnotNone(or explicitly chosen).
class ProductionConfig:
SECRET_KEY = os.environ['FLASK_SECRET_KEY']
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
PERMANENT_SESSION_LIFETIME = timedelta(hours=12)
Templates — Jinja2 XSS
- FLK-XSS-1 Jinja autoescape ON by default for
.htmlfiles viarender_template. Don't disable globally. - FLK-XSS-2
{{ var|safe }}filter and{% autoescape false %}reviewed — content must be trusted. - FLK-XSS-3
Markup(user_input)in Python flags user input as safe — same review needed. - FLK-XSS-4
render_template_string(template, ...)with user-controlled template → SSTI (Server-Side Template Injection). Critical vulnerability.
# CRITICAL — SSTI
@app.route('/preview')
def preview():
return render_template_string(request.args['template'])
# FIXED — render a static template, pass user input as variable
return render_template('preview.html', user_input=request.args['template'])
CSRF (Flask-WTF)
- FLK-CSRF-1
CSRFProtect(app)initialized; protects all POST/PUT/DELETE by default. - FLK-CSRF-2
{{ csrf_token() }}in forms (or Flask-WTF's{{ form.csrf_token }}). - FLK-CSRF-3 API endpoints (JSON, token-auth) exempted via
@csrf.exemptconsciously, not because of CSRF failures. - FLK-CSRF-4 Webhook endpoints exempted but verified via signature (see
saas-security-pack/saas-api-security/references/webhook-security.md).
Authentication (Flask-Login)
- FLK-AUTH-1
LoginManager.login_viewset; protected routes use@login_required. - FLK-AUTH-2
current_userchecked in views for ownership / role. - FLK-AUTH-3 Password hashing:
werkzeug.security.generate_password_hash(defaults to scrypt or pbkdf2) orpasslibwith Argon2/bcrypt. - FLK-AUTH-4 Login view rate-limited (per IP, per user). Flask-Limiter typically.
- FLK-AUTH-5
login_user(user, remember=True)— remember-me tokens stored securely, can be revoked. - FLK-AUTH-6
logout_user()invalidates the server-side session.
Authorization
- FLK-AZ-1 Per-resource ownership checks in view functions.
@login_requiredonly verifies authentication. - FLK-AZ-2 Role checks via custom decorator:
def admin_required(f): @wraps(f) def decorated(*args, **kwargs): if not current_user.is_authenticated or not current_user.is_admin: abort(403) return f(*args, **kwargs) return decorated
SQL (Flask-SQLAlchemy)
Same as FastAPI's SQLAlchemy section:
- FLK-SQL-1
db.session.execute(text(...))with parameterized queries. - FLK-SQL-2 ORM filter expressions safe; raw string concatenation isn't.
- FLK-SQL-3
query.from_statement(text(...))reviewed.
Mass assignment
- FLK-MA-1 Don't pass
request.formdirectly toModel(**data). Use Flask-WTF Form (validates allowed fields) or Marshmallow schema. - FLK-MA-2 Updates via
setattrover a dict require explicit allowlist.
File uploads
- FLK-UP-1
MAX_CONTENT_LENGTHset (limits request size). - FLK-UP-2
secure_filename(filename)used on uploaded filenames. - FLK-UP-3 File type validated by content (magic bytes), not extension.
- FLK-UP-4 Upload directory not in
static/or any web-served path without auth.
CORS (flask-cors)
- FLK-CORS-1
CORS(app, origins=['https://app.yourorg.com'], supports_credentials=True)— specific origins. - FLK-CORS-2 Not
CORS(app)(wildcard).
Headers
Flask doesn't apply helmet-equivalent headers by default. Use Flask-Talisman:
- FLK-HDR-1
Talisman(app)registered with appropriate CSP, HSTS, X-Content-Type-Options, etc.
from flask_talisman import Talisman
Talisman(app, content_security_policy={
'default-src': "'self'",
'img-src': ["'self'", 'data:', 'https://images.yourcdn.com'],
# ...
})
Debugging and error pages
- FLK-DBG-1 Werkzeug debugger NEVER enabled in production.
app.debug = False,app.run(debug=False). - FLK-DBG-2 Custom error handlers for 404, 500 don't leak internal details.
- FLK-DBG-3
app.config['PROPAGATE_EXCEPTIONS']not unintentionally True in prod.
The Werkzeug debugger PIN-protected console can be brute-forced; absolutely never expose to production.
Flask-Admin
- FLK-ADM-1 Flask-Admin views protected with auth check (
is_accessiblemethod on ModelView). - FLK-ADM-2 Admin URL not at predictable
/admin/; access logged. - FLK-ADM-3 Sensitive columns excluded from Flask-Admin display (passwords, secrets).
Blueprints
- FLK-BP-1 Blueprints with
url_prefix='/api'etc. — auth applied at blueprint level via@blueprint.before_request. - FLK-BP-2
url_forused consistently; no hardcoded URLs that could lead to open redirects.
Open redirects
- FLK-OR-1
redirect(url)withurlfromrequest.args.get('next')validated against allowlist or relative-only:from urllib.parse import urlparse next_url = request.args.get('next', '/') if urlparse(next_url).netloc: next_url = '/' # external origin not allowed return redirect(next_url)
Dependencies
- FLK-DEP-1 Flask version current (3.x).
- FLK-DEP-2 Werkzeug version matches Flask requirements. CVE-2024-34069 (Werkzeug debugger pin bypass on certain configs) — bump Werkzeug.
- FLK-DEP-3
pip-auditclean.
Phase 4: Triage
Critical: Werkzeug debugger reachable; SSTI via render_template_string; SECRET_KEY hardcoded; CSRF disabled globally.
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with FLK-.