diff --git a/skills/FLASK-SECURITY.md b/skills/FLASK-SECURITY.md new file mode 100644 index 0000000..03488a0 --- /dev/null +++ b/skills/FLASK-SECURITY.md @@ -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'] = # e.g. 16 * 1024 * 1024 for 16MB +app.config['MAX_FORM_MEMORY_SIZE'] = # default 500kB +app.config['MAX_FORM_PARTS'] = # 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: ``, never ``. +- **`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 +
+ + ... +
+``` + +--- + +## 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