Files
dotfiles/skills/FLASK-SECURITY.md
T
Mark Eaton 59761f9bd9 rewrite FLASK-SECURITY.md as a comprehensive security audit skill
Embed Flask security recommendations inline instead of relying on URL
fetching at runtime. Add structured sections for resource limits, XSS,
CSRF, security headers, CSP, cookies, secrets, CORS, host validation,
and additional checks. Include a grading rubric for audit summaries.
2026-02-23 16:33:07 -05:00

7.1 KiB

Flask Security Audit

Perform a security audit of this Flask (or Quart) application. Work through each section below, report findings, and fix issues. At the end, provide a summary with a letter grade for each area.

Instructions

  • Audit the project against every section below.
  • For each section, report: what is already correct, what needs fixing, and what is not applicable.
  • Fix all issues you find. If a fix would be disruptive or ambiguous, describe the fix and ask before applying it.
  • At the end, produce a Security Audit Summary table with a letter grade (A-F) for each section.

1. Resource Limits (DoS Prevention)

Verify these Flask config values are set explicitly (not left at defaults):

app.config['MAX_CONTENT_LENGTH'] = <appropriate_limit>    # e.g. 16 * 1024 * 1024 for 16MB
app.config['MAX_FORM_MEMORY_SIZE'] = <appropriate_limit>   # default 500kB
app.config['MAX_FORM_PARTS'] = <appropriate_limit>         # default 1000
  • MAX_CONTENT_LENGTH has no default — it must be set.
  • Consider the app's actual needs when choosing values.

2. Cross-Site Scripting (XSS)

Check for these common XSS pitfalls:

  • Unquoted HTML attributes — all template attributes must be quoted: <input value="{{ value }}">, never <input value={{ value }}>.
  • Markup() on user input — never call Markup() or |safe on user-submitted data.
  • HTML generated outside Jinja — any HTML built in Python code must be escaped.
  • Uploaded files served as HTML — uploaded files should be served with Content-Disposition: attachment or stored with validated content types.
  • javascript: URIs — if any href or src attribute uses a user-controlled value, validate that it starts with http://, https://, or /. Enforce via CSP as well.

3. Cross-Site Request Forgery (CSRF)

Flask has no built-in CSRF protection. Verify that:

  • A CSRF library is in use (e.g. Flask-WTF's CSRFProtect, or equivalent).
  • All state-changing forms include CSRF tokens.
  • AJAX state-changing requests include CSRF tokens in headers.
  • If no CSRF protection exists, add it using Flask-WTF:
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    ...
</form>

4. Security Headers

Check that the following response headers are set. Recommend Flask-Talisman if headers are missing or manually managed:

Header Required Value
Strict-Transport-Security max-age=31536000; includeSubDomains
Content-Security-Policy See Section 5 below
X-Content-Type-Options nosniff
X-Frame-Options DENY or SAMEORIGIN

If Flask-Talisman is not in use, suggest adding it:

from flask_talisman import Talisman
Talisman(app, content_security_policy=csp)

5. Content Security Policy (CSP)

Audit the CSP and make it as restrictive as possible. Apply these principles:

  • Start from default-src 'self' and only open directives that the app actually needs.
  • Never allow unsafe-inline for script-src unless absolutely unavoidable. Prefer nonces or hashes.
  • Never allow unsafe-eval for script-src.
  • Avoid wildcard (*) origins — list specific domains.
  • Set form-action 'self' to prevent form hijacking.
  • Set frame-ancestors 'none' (or 'self') to prevent clickjacking (replaces X-Frame-Options).
  • Set base-uri 'self' to prevent base tag hijacking.
  • Set object-src 'none' to block plugins.

Provide:

  1. The current CSP (or note its absence).
  2. A recommended CSP with explanations for each directive.
  3. A letter grade (A-F) assessing how restrictive the CSP is.

Verify these session/cookie configuration values:

app.config.update(
    SESSION_COOKIE_SECURE=True,       # cookies sent over HTTPS only
    SESSION_COOKIE_HTTPONLY=True,      # no JavaScript access to session cookie
    SESSION_COOKIE_SAMESITE='Lax',    # CSRF protection for cookies
    PERMANENT_SESSION_LIFETIME=600,   # session timeout in seconds
)

Also check:

  • Any manually set cookies (response.set_cookie(...)) should use secure=True, httponly=True, and samesite='Lax'.
  • Sessions are cleared on login (session.clear() before setting new session data).
  • SECRET_KEY is cryptographically random and not hardcoded (see Section 7).

7. Secrets Management

Verify that:

  • No secrets are hardcoded in source files. Search for: SECRET_KEY, PASSWORD, API_KEY, TOKEN, database URIs with credentials, and similar patterns.
  • All secrets are loaded from environment variables.
  • A .env file exists for local development, and .env is listed in .gitignore.
  • Use python-dotenv to load the .env file:
from dotenv import load_dotenv
load_dotenv()

app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
  • Create .gitignore if it does not exist. Ensure .env is in it.
  • If a .env.example file does not exist, create one with placeholder values (never real secrets).

8. CORS Configuration

If CORS is configured (e.g. via Flask-CORS), review and explain:

  • Which origins are allowed. Flag * (allow-all) as a problem unless this is a fully public API.
  • Which methods and headers are allowed.
  • Whether supports_credentials=True is set — this is dangerous with broad origins.
  • If CORS is not needed, confirm it is not enabled.

9. Host Header Validation

Check that trusted hosts are configured to prevent host header poisoning:

app.config['TRUSTED_HOSTS'] = ['example.com', 'www.example.com']

If the app runs behind a reverse proxy, verify ProxyFix is configured correctly:

from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)

10. Additional Checks

  • Debug mode: Verify app.debug is False and FLASK_DEBUG is not set to 1 in production config.
  • JSON security: If the app returns JSON arrays at top level, confirm this is intentional.
  • File uploads: If file uploads exist, verify filenames are sanitized with werkzeug.utils.secure_filename().
  • SQL injection: If raw SQL is used, verify parameterized queries. Flag any string concatenation/formatting in SQL.
  • Dependency vulnerabilities: Run pip audit or safety check if available and report results.

Security Audit Summary

After completing all sections, produce a summary in this format:

Section Grade Notes
1. Resource Limits ?
2. XSS Prevention ?
3. CSRF Protection ?
4. Security Headers ?
5. Content Security Policy ?
6. Cookie/Session Security ?
7. Secrets Management ?
8. CORS Configuration ?
9. Host Header Validation ?
10. Additional Checks ?
Overall ?

Grading scale:

  • A: Best practices fully implemented
  • B: Mostly good, minor improvements possible
  • C: Functional but missing important protections
  • D: Significant security gaps
  • F: Critical vulnerabilities present