forked from claw/flaskpaste
add security tooling and development workflow
- ruff for linting and formatting - bandit for security scanning - mypy for type checking - pip-audit for dependency vulnerabilities - Makefile with lint/format/security/test targets
This commit is contained in:
265
SECURITY.md
Normal file
265
SECURITY.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Security
|
||||
|
||||
FlaskPaste is designed with security as a primary concern. This document describes security features, best practices, and responsible disclosure procedures.
|
||||
|
||||
## Security Features
|
||||
|
||||
### Authentication
|
||||
|
||||
**Client Certificate Authentication (mTLS)**
|
||||
|
||||
FlaskPaste supports mutual TLS authentication via reverse proxy:
|
||||
|
||||
1. Reverse proxy terminates TLS and validates client certificates
|
||||
2. Proxy extracts SHA1 fingerprint and passes via `X-SSL-Client-SHA1` header
|
||||
3. FlaskPaste uses fingerprint for ownership tracking and authorization
|
||||
|
||||
```nginx
|
||||
# nginx example
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_set_header X-SSL-Client-SHA1 $ssl_client_fingerprint;
|
||||
}
|
||||
```
|
||||
|
||||
**Proxy Trust Validation**
|
||||
|
||||
Defense-in-depth against header spoofing:
|
||||
|
||||
```bash
|
||||
export FLASKPASTE_PROXY_SECRET="shared-secret"
|
||||
```
|
||||
|
||||
The proxy must send `X-Proxy-Secret` header; requests without valid secret are treated as anonymous.
|
||||
|
||||
### Password Protection
|
||||
|
||||
Pastes can be password-protected using PBKDF2-HMAC-SHA256:
|
||||
|
||||
- 600,000 iterations (OWASP 2023 recommendation)
|
||||
- 32-byte random salt per password
|
||||
- Constant-time comparison prevents timing attacks
|
||||
- Passwords never logged or stored in plaintext
|
||||
|
||||
```bash
|
||||
# Create protected paste
|
||||
curl -H "X-Paste-Password: secret" --data-binary @file.txt https://paste.example.com/
|
||||
|
||||
# Access protected paste
|
||||
curl -H "X-Paste-Password: secret" https://paste.example.com/abc123/raw
|
||||
```
|
||||
|
||||
### End-to-End Encryption
|
||||
|
||||
CLI supports client-side AES-256-GCM encryption:
|
||||
|
||||
- Key generated locally, never sent to server
|
||||
- Key appended to URL as fragment (`#key`) which browsers never transmit
|
||||
- Server stores only opaque ciphertext
|
||||
- Zero-knowledge: server cannot read content
|
||||
|
||||
```bash
|
||||
./fpaste create -e secret.txt
|
||||
# Returns: https://paste.example.com/abc123#<base64-key>
|
||||
```
|
||||
|
||||
### Abuse Prevention
|
||||
|
||||
**Content-Hash Deduplication**
|
||||
|
||||
Prevents spam flooding by throttling repeated identical submissions:
|
||||
|
||||
```bash
|
||||
FLASKPASTE_DEDUP_WINDOW=3600 # 1 hour window
|
||||
FLASKPASTE_DEDUP_MAX=3 # Max 3 identical submissions
|
||||
```
|
||||
|
||||
**Proof-of-Work**
|
||||
|
||||
Computational puzzle prevents automated spam:
|
||||
|
||||
```bash
|
||||
FLASKPASTE_POW_DIFFICULTY=20 # Leading zero bits required
|
||||
FLASKPASTE_POW_TTL=300 # Challenge validity (seconds)
|
||||
```
|
||||
|
||||
**Entropy Enforcement**
|
||||
|
||||
Optional minimum entropy requirement to enforce encrypted uploads:
|
||||
|
||||
```bash
|
||||
FLASKPASTE_MIN_ENTROPY=6.0 # Bits per byte (encrypted ~7.5-8.0)
|
||||
FLASKPASTE_MIN_ENTROPY_SIZE=256 # Only check content >= this size
|
||||
```
|
||||
|
||||
### Security Headers
|
||||
|
||||
All responses include:
|
||||
|
||||
| Header | Value |
|
||||
|--------|-------|
|
||||
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` |
|
||||
| `X-Content-Type-Options` | `nosniff` |
|
||||
| `X-Frame-Options` | `DENY` |
|
||||
| `X-XSS-Protection` | `1; mode=block` |
|
||||
| `Content-Security-Policy` | `default-src 'none'` |
|
||||
| `Referrer-Policy` | `no-referrer` |
|
||||
| `Permissions-Policy` | `geolocation=(), microphone=(), camera=()` |
|
||||
| `Cache-Control` | `no-store, no-cache, must-revalidate` |
|
||||
| `Pragma` | `no-cache` |
|
||||
|
||||
### Request Tracing
|
||||
|
||||
All requests receive `X-Request-ID` header for log correlation and debugging. Pass your own ID to trace requests through reverse proxies.
|
||||
|
||||
## Input Validation
|
||||
|
||||
### Paste IDs
|
||||
|
||||
- Hexadecimal only (`[a-f0-9]+`)
|
||||
- Configurable length (default 12 characters)
|
||||
- Validated on all endpoints
|
||||
|
||||
### MIME Types
|
||||
|
||||
- Magic byte detection for binary formats
|
||||
- Sanitized against injection
|
||||
- Only safe characters allowed: `[a-z0-9!#$&\-^_.+]`
|
||||
|
||||
### Size Limits
|
||||
|
||||
```bash
|
||||
FLASKPASTE_MAX_ANON=3145728 # 3 MiB for anonymous
|
||||
FLASKPASTE_MAX_AUTH=52428800 # 50 MiB for authenticated
|
||||
```
|
||||
|
||||
### Password Limits
|
||||
|
||||
- Maximum 1024 characters (prevents DoS via hashing)
|
||||
- Unicode supported
|
||||
- Special characters allowed
|
||||
|
||||
## SQL Injection Prevention
|
||||
|
||||
All database queries use parameterized statements:
|
||||
|
||||
```python
|
||||
# Correct - parameterized
|
||||
db.execute("SELECT * FROM pastes WHERE id = ?", (paste_id,))
|
||||
|
||||
# Never - string concatenation
|
||||
db.execute(f"SELECT * FROM pastes WHERE id = '{paste_id}'")
|
||||
```
|
||||
|
||||
## Database Security
|
||||
|
||||
- SQLite with WAL mode for better concurrency
|
||||
- Foreign keys enforced
|
||||
- Automatic cleanup of expired content
|
||||
- No sensitive data in plaintext (passwords are hashed)
|
||||
|
||||
## Deployment Recommendations
|
||||
|
||||
### Reverse Proxy
|
||||
|
||||
Always deploy behind a TLS-terminating reverse proxy:
|
||||
|
||||
```
|
||||
┌─────────┐ TLS ┌─────────┐ HTTP ┌───────────┐
|
||||
│ Client │ ──────────── │ nginx │ ──────────── │ FlaskPaste│
|
||||
│ │ mTLS opt │ HAProxy │ local │ │
|
||||
└─────────┘ └─────────┘ └───────────┘
|
||||
```
|
||||
|
||||
### Container Security
|
||||
|
||||
```dockerfile
|
||||
# Run as non-root
|
||||
USER app
|
||||
|
||||
# Read-only filesystem where possible
|
||||
# Mount /app/data as writable volume
|
||||
```
|
||||
|
||||
### Network Isolation
|
||||
|
||||
- FlaskPaste should only listen on localhost or internal network
|
||||
- Reverse proxy handles external traffic
|
||||
- Use firewall rules to restrict direct access
|
||||
|
||||
### Secrets Management
|
||||
|
||||
```bash
|
||||
# Use strong, unique secrets
|
||||
FLASKPASTE_PROXY_SECRET="$(openssl rand -hex 32)"
|
||||
FLASKPASTE_POW_SECRET="$(openssl rand -hex 32)"
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### Before Deployment
|
||||
|
||||
- [ ] Deploy behind TLS-terminating reverse proxy
|
||||
- [ ] Configure `FLASKPASTE_PROXY_SECRET` if using auth headers
|
||||
- [ ] Enable proof-of-work if public-facing
|
||||
- [ ] Set appropriate size limits
|
||||
- [ ] Configure paste expiry
|
||||
|
||||
### Ongoing
|
||||
|
||||
- [ ] Monitor logs for suspicious activity
|
||||
- [ ] Keep dependencies updated
|
||||
- [ ] Review access patterns
|
||||
- [ ] Rotate secrets periodically
|
||||
- [ ] Test backup/restore procedures
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **No rate limiting per IP** - Delegated to reverse proxy
|
||||
2. **No user accounts** - PKI handles identity
|
||||
3. **No audit log** - Standard request logging only
|
||||
4. **Single-node only** - SQLite limits horizontal scaling
|
||||
|
||||
## Reporting Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability:
|
||||
|
||||
1. **Do not** open a public issue
|
||||
2. Email security concerns to the repository maintainer
|
||||
3. Include:
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Suggested fix (if any)
|
||||
|
||||
We aim to acknowledge reports within 48 hours and provide a fix timeline within 7 days.
|
||||
|
||||
## Security Updates
|
||||
|
||||
Security fixes are released as soon as possible. Subscribe to repository releases for notifications.
|
||||
|
||||
## Threat Model
|
||||
|
||||
### In Scope
|
||||
|
||||
- Unauthorized access to pastes
|
||||
- Content injection attacks
|
||||
- Authentication bypass
|
||||
- Information disclosure
|
||||
- Denial of service (application-level)
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Physical access to server
|
||||
- Compromise of reverse proxy
|
||||
- Client-side vulnerabilities
|
||||
- Network-level DoS
|
||||
- Social engineering
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Security Changes |
|
||||
|---------|------------------|
|
||||
| 1.2.0 | Password protection with PBKDF2, code modernization |
|
||||
| 1.1.0 | E2E encryption, entropy enforcement, burn-after-read |
|
||||
| 1.0.0 | Initial release with core security features |
|
||||
Reference in New Issue
Block a user