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.
This commit is contained in:
Mark Eaton
2026-02-23 16:33:07 -05:00
parent f861a659d7
commit 59761f9bd9
+206
View File
@@ -0,0 +1,206 @@
# 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):
```python
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:
```python
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
```
```html
<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:
```python
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.
---
## 6. Cookie and Session Security
Verify these session/cookie configuration values:
```python
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:
```python
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:
```python
app.config['TRUSTED_HOSTS'] = ['example.com', 'www.example.com']
```
If the app runs behind a reverse proxy, verify `ProxyFix` is configured correctly:
```python
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