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:
@@ -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
|
||||||
Reference in New Issue
Block a user