Add security headers middleware and documentation

- Add SecurityHeadersMiddleware to backend/app/main.py
  - Implements Content-Security-Policy: default-src 'self'
  - Implements X-Frame-Options: DENY (clickjacking protection)
  - Implements X-Content-Type-Options: nosniff (MIME-sniffing protection)
  - Implements X-XSS-Protection: 1; mode=block (browser XSS filters)
- Add CSP meta tag to frontend/index.html for defense-in-depth
- Create Docs/Security.md with comprehensive security headers documentation
- Add test suite (backend/tests/test_security_headers_middleware.py) with 5 tests
  - Tests verify headers are present on success and error responses
  - Tests ensure all four security headers are correctly set
- All existing tests continue to pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-30 21:33:08 +02:00
parent 3bd9848a08
commit 400ab1a3f1
5 changed files with 256 additions and 50 deletions

98
Docs/Security.md Normal file
View File

@@ -0,0 +1,98 @@
# Security — Guidelines and Implementation
Security considerations and implementation details for BanGUI.
---
## HTTP Security Headers
BanGUI implements defense-in-depth against client-side attacks by sending security-related HTTP response headers on all responses.
### Headers Implemented
| Header | Value | Purpose |
|---|---|---|
| `Content-Security-Policy` | `default-src 'self'` | Prevents XSS attacks by restricting script, style, font, image, and other resource origins to `self` only. Browsers refuse to load resources from other origins. |
| `X-Frame-Options` | `DENY` | Prevents clickjacking attacks by forbidding the page from being embedded in `<iframe>` tags on any origin. |
| `X-Content-Type-Options` | `nosniff` | Prevents MIME-type sniffing attacks by forcing browsers to respect the declared `Content-Type`. Blocks execution of misidentified scripts. |
| `X-XSS-Protection` | `1; mode=block` | Enables browser XSS filters (legacy header for older browsers). Modern browsers prioritize CSP. |
### Implementation
**Backend:** The `SecurityHeadersMiddleware` in `backend/app/main.py` adds these headers to every HTTP response, including error responses and non-API routes.
```python
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"
```
**Frontend:** The `<meta http-equiv="Content-Security-Policy" content="default-src 'self'" />` tag in `frontend/index.html` provides an additional defense layer in case the backend headers are ever stripped (e.g., by a proxy).
### CSP Policy Details
The current policy `default-src 'self'` means:
- **Allowed:** Inline scripts, stylesheets, fonts, images, and other resources from the same origin (`self`)
- **Blocked:** Resources from external domains, inline event handlers, `eval()`, and `setTimeout(string)`
**Why no `'unsafe-inline'`?**
- `'unsafe-inline'` defeats CSP's primary purpose (XSS prevention) by allowing arbitrarily-embedded scripts
- All scripts and styles must be in separate files (never inline), which is best practice anyway
- The frontend build system (Vite) automatically handles asset bundling and file separation
**If external CDN resources are needed:**
1. Explicitly add the CDN origin to the CSP policy, e.g.: `default-src 'self' https://cdn.example.com`
2. Document the CDN addition with a justification comment
3. Ensure the CDN certificate chain is valid and trusted
4. Consider using Subresource Integrity (SRI) to verify resource authenticity
### Verification
To verify headers are being sent correctly:
1. **Chrome DevTools:**
- Open DevTools (F12)
- Go to Network tab
- Reload the page
- Click on any request and open the Response Headers section
- Look for `Content-Security-Policy`, `X-Frame-Options`, `X-Content-Type-Options`, `X-XSS-Protection`
2. **Command line (curl):**
```bash
curl -I http://localhost:8000/
curl -I http://localhost:5173/
```
3. **Online tools:**
- Use [securityheaders.com](https://securityheaders.com) or [csp-evaluator.withgoogle.com](https://csp-evaluator.withgoogle.com)
### Future Improvements
- **Stricter CSP:** If functionality allows, tighten to `default-src 'none'` and explicitly allow individual resources
- **SRI (Subresource Integrity):** Add integrity attributes to external script/style tags to prevent tampering
- **Preload headers:** Use `Link: <...>; rel=preload` to optimize critical resource delivery
- **HSTS:** Consider adding `Strict-Transport-Security` for production deployments to force HTTPS
---
## Session Security
See `backend/app/middleware/csrf.py` and `backend/app/middleware/rate_limit.py` for CSRF protection and rate limiting.
---
## Password Security
- Passwords are hashed with SHA256 on the frontend before transmission
- The backend never stores plain-text passwords
- See `backend/app/services/auth.py` for authentication implementation
---
## Database Security
- The SQLite database contains no sensitive data (no passwords, API keys, or tokens stored)
- Database queries use parameterized statements to prevent SQL injection
- See `backend/app/repositories/` for data access patterns

View File

@@ -1,50 +1,3 @@
## [CRITICAL] Global rate limiting missing
**Where found**
- `backend/app/routers/auth.py` — only `/api/auth/login` has rate limiting
- All other routers have no rate limiting
**Why this is needed**
Without rate limiting, attackers can spam endpoints to cause CPU spike, database overload, or network bandwidth exhaustion.
**Goal**
Implement global per-IP rate limiting on all endpoints.
**What to do**
1. Add rate limiting middleware to `backend/app/main.py`:
```python
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address, default_limits=["200 per minute"])
app.state.limiter = limiter
```
2. Apply to all routers with appropriate limits per endpoint
3. Return proper HTTP 429 with `Retry-After` header
4. Document limits in API docs
**Possible traps and issues**
- Limits set too low block legitimate users
- Distributed deployments need shared limiter state (Redis-backed)
- Different endpoints may need different limits
- Trusted IPs should bypass limiting
**Docs changes needed**
- Add section in `Docs/Backend-Development.md` § Rate Limiting
- Document default limits in deployment guide
**Doc references**
- `Docs/Backend-Development.md` (rate limiting)
- `backend/app/main.py` (middleware setup)
---
## [CRITICAL] Missing security headers (CSP, X-Frame-Options, etc.)
**Where found**