Install
$ agentstack add skill-hlsitechio-claude-skills-security-flask-security ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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:
``python 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:
``python 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 rendertemplatestring; SECRET_KEY hardcoded; CSRF disabled globally.
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with FLK-.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: hlsitechio
- Source: hlsitechio/claude-skills-security
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.