refactor: centralize constants and reorganize documentation
- Create internal/constants package with all hardcoded values (environment, cookies, themes, headers, routes, cache) - Create internal/httputil package for HTTP helper functions - Update all handlers and middleware to use centralized constants - Reorganize documentation with numbered prefixes (00-26) - Remove duplicate docs from validation folder and docs/ - Delete handlers/constants.go (moved to internal/constants)
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This project is actively maintained. Security updates will be provided for the latest release on the `main` branch.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| main | :white_check_mark: |
|
||||
| < main | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of this CV site seriously. If you discover a security vulnerability, please help us protect users by following responsible disclosure practices.
|
||||
|
||||
### How to Report
|
||||
|
||||
**DO NOT** open a public issue for security vulnerabilities.
|
||||
|
||||
Instead, please report security vulnerabilities by:
|
||||
|
||||
1. **GitHub Security Advisories** (Preferred):
|
||||
- Go to the repository's Security tab
|
||||
- Click "Report a vulnerability"
|
||||
- Fill out the form with details
|
||||
|
||||
2. **Direct Contact**:
|
||||
- Open a private issue or contact the maintainer directly
|
||||
- Use encrypted communication if the vulnerability is severe
|
||||
|
||||
### What to Include
|
||||
|
||||
Please provide the following information in your report:
|
||||
|
||||
- **Description** of the vulnerability
|
||||
- **Steps to reproduce** the issue
|
||||
- **Potential impact** (e.g., data exposure, XSS, CSRF)
|
||||
- **Affected versions** (if known)
|
||||
- **Suggested fix** (if you have one)
|
||||
- **Your contact information** for follow-up questions
|
||||
|
||||
### Response Timeline
|
||||
|
||||
- **Initial Response**: Within 48 hours
|
||||
- **Status Update**: Within 7 days
|
||||
- **Fix Timeline**: Depends on severity
|
||||
- Critical: Within 7 days
|
||||
- High: Within 14 days
|
||||
- Medium: Within 30 days
|
||||
- Low: Next release cycle
|
||||
|
||||
### Disclosure Policy
|
||||
|
||||
- We ask that you give us reasonable time to fix the vulnerability before public disclosure
|
||||
- We will credit you in the security advisory (unless you prefer to remain anonymous)
|
||||
- Once the fix is deployed, we will publish a security advisory with details
|
||||
|
||||
## Security Considerations for Deployments
|
||||
|
||||
If you're deploying this CV site, please be aware of these security considerations:
|
||||
|
||||
### 1. PDF Generation Security
|
||||
|
||||
The server uses headless Chrome (via chromedp) to generate PDFs:
|
||||
|
||||
- **Risk**: Chromedp executes JavaScript and renders HTML, which could be exploited if user input is not sanitized
|
||||
- **Mitigation**:
|
||||
- The CV data comes from trusted JSON files, not user input
|
||||
- If you modify the application to accept user input, ensure proper sanitization
|
||||
- Consider running chromedp in a sandboxed environment
|
||||
|
||||
### 2. Content Security Policy
|
||||
|
||||
The application includes CSP headers:
|
||||
|
||||
```go
|
||||
Content-Security-Policy: default-src 'self';
|
||||
script-src 'self' 'unsafe-inline' https://unpkg.com;
|
||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
||||
font-src 'self' https://fonts.gstatic.com;
|
||||
img-src 'self' data:
|
||||
```
|
||||
|
||||
- Review and adjust CSP headers based on your deployment needs
|
||||
- Remove `'unsafe-inline'` if possible by moving inline scripts/styles to separate files
|
||||
|
||||
### 3. Environment Variables
|
||||
|
||||
Sensitive configuration is managed via environment variables:
|
||||
|
||||
- **Never commit** `.env` file to version control
|
||||
- Use `.env.example` as a template
|
||||
- In production, use secure secret management (e.g., HashiCorp Vault, AWS Secrets Manager)
|
||||
|
||||
### 4. HTTPS in Production
|
||||
|
||||
- **Always use HTTPS** in production
|
||||
- Configure TLS certificates (Let's Encrypt recommended)
|
||||
- Consider using a reverse proxy (nginx, Caddy) for TLS termination
|
||||
|
||||
### 5. Rate Limiting & Origin Checking
|
||||
|
||||
**Status:** ✅ **Implemented**
|
||||
|
||||
The application includes built-in rate limiting and origin checking for resource-intensive endpoints:
|
||||
|
||||
**Protected Endpoints:**
|
||||
- `/export/pdf` - Rate limited to 3 requests per minute per IP
|
||||
- Origin checking prevents external hotlinking
|
||||
|
||||
**Configuration:**
|
||||
```bash
|
||||
# Set allowed domains in production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- IP-based rate limiting (3 PDF/min per IP)
|
||||
- Origin/Referer header validation
|
||||
- Works with reverse proxies (Nginx, CloudFlare)
|
||||
- Automatic IP detection from X-Forwarded-For headers
|
||||
|
||||
For detailed configuration, see [API Protection Features](#api-protection-features) below.
|
||||
|
||||
### 6. Input Validation
|
||||
|
||||
While this application primarily serves static CV data:
|
||||
|
||||
- If you extend it to accept user input, implement strict validation
|
||||
- Sanitize all inputs before rendering in templates
|
||||
- Use Go's `html/template` package (which auto-escapes) for HTML rendering
|
||||
|
||||
### 7. Dependency Management
|
||||
|
||||
Keep dependencies up to date:
|
||||
|
||||
```bash
|
||||
# Check for outdated dependencies
|
||||
go list -u -m all
|
||||
|
||||
# Update dependencies
|
||||
go get -u ./...
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
### 8. Security Headers
|
||||
|
||||
The application sets security headers:
|
||||
|
||||
- `X-Content-Type-Options: nosniff`
|
||||
- `X-Frame-Options: DENY`
|
||||
- `X-XSS-Protection: 1; mode=block`
|
||||
- `Content-Security-Policy: ...`
|
||||
|
||||
Review and enhance these headers based on your deployment needs.
|
||||
|
||||
### 9. Logging and Monitoring
|
||||
|
||||
- Enable structured logging in production
|
||||
- Monitor for unusual patterns (e.g., excessive PDF generation requests)
|
||||
- Set up alerts for errors and anomalies
|
||||
|
||||
### 10. Docker Security
|
||||
|
||||
If deploying via Docker:
|
||||
|
||||
- Use official, minimal base images
|
||||
- Run container as non-root user
|
||||
- Scan images for vulnerabilities (e.g., `docker scan`, Trivy)
|
||||
- Keep base images updated
|
||||
|
||||
## API Protection Features
|
||||
|
||||
The application implements multiple layers of protection to prevent external access and DDoS attacks on resource-intensive endpoints.
|
||||
|
||||
### Origin Checking
|
||||
|
||||
**Purpose:** Prevent external sites from hotlinking to resource-intensive endpoints like PDF generation.
|
||||
|
||||
**How It Works:**
|
||||
1. Checks `Origin` header (CORS requests)
|
||||
2. Falls back to `Referer` header (navigation requests)
|
||||
3. Validates against whitelist of allowed domains
|
||||
4. Blocks requests from external domains
|
||||
|
||||
**Configuration via Environment Variable:**
|
||||
|
||||
```bash
|
||||
# Development (default - allows localhost)
|
||||
ALLOWED_ORIGINS=
|
||||
|
||||
# Production (specify your domains)
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
|
||||
# Multiple domains
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com,staging.yourdomain.com
|
||||
```
|
||||
|
||||
**Behavior by Environment:**
|
||||
|
||||
| Environment | No Header | Localhost | Your Domain | External Domain |
|
||||
|-------------|-----------|-----------|-------------|-----------------|
|
||||
| **Development** | ✅ Allowed | ✅ Allowed | ✅ Allowed | ❌ Blocked |
|
||||
| **Production** | ❌ Blocked (PDF) | ✅ Allowed | ✅ Allowed | ❌ Blocked |
|
||||
|
||||
**Example Requests:**
|
||||
|
||||
```bash
|
||||
# ✅ Allowed (localhost in development)
|
||||
curl http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ✅ Allowed (valid referer)
|
||||
curl -H "Referer: https://yourdomain.com/" \
|
||||
https://yourdomain.com/export/pdf?lang=en
|
||||
|
||||
# ❌ Blocked (external referer)
|
||||
curl -H "Referer: https://evil.com/" \
|
||||
https://yourdomain.com/export/pdf?lang=en
|
||||
# Response: 403 Forbidden - External access not allowed
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
**Purpose:** Prevent abuse even from allowed origins by limiting request frequency.
|
||||
|
||||
**Current Configuration:**
|
||||
- **Endpoint:** `/export/pdf`
|
||||
- **Limit:** 3 requests per minute per IP address
|
||||
- **Window:** 1 minute (rolling)
|
||||
- **Response:** 429 Too Many Requests when exceeded
|
||||
|
||||
**Implementation:**
|
||||
```go
|
||||
// Applied in main.go
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
| Requests | Status | Response |
|
||||
|----------|--------|----------|
|
||||
| 1st-3rd requests | ✅ 200 OK | PDF generated |
|
||||
| 4th+ request (within 1 min) | ❌ 429 Too Many Requests | Rate limit exceeded |
|
||||
| After 1 minute | ✅ 200 OK | Counter reset |
|
||||
|
||||
**IP Detection:**
|
||||
- Checks `X-Forwarded-For` header (proxy/CDN scenarios)
|
||||
- Falls back to `X-Real-IP` header (alternative proxy header)
|
||||
- Uses `RemoteAddr` field (direct connections)
|
||||
- Works correctly behind Nginx, CloudFlare, and other reverse proxies
|
||||
|
||||
**Rate Limit Response:**
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Retry-After: 60
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
```
|
||||
|
||||
### Combined Protection
|
||||
|
||||
The PDF endpoint has **both** origin checking and rate limiting applied in sequence:
|
||||
|
||||
```
|
||||
Request Flow:
|
||||
User Request → Origin Checker → Rate Limiter → PDF Handler
|
||||
↓ ↓
|
||||
403 if external 429 if exceeded
|
||||
```
|
||||
|
||||
**Protection Scenarios:**
|
||||
|
||||
1. **Scenario: External hotlinking attempt**
|
||||
- External site links directly to your PDF endpoint
|
||||
- **Mitigation:** Origin checker blocks (403 Forbidden)
|
||||
- **Result:** Request never reaches rate limiter
|
||||
|
||||
2. **Scenario: Rapid PDF generation from your site**
|
||||
- Legitimate user on your domain requests many PDFs quickly
|
||||
- **Mitigation:** Rate limiter blocks after 3 requests/min (429)
|
||||
- **Result:** Prevents resource exhaustion
|
||||
|
||||
3. **Scenario: Distributed attack with header spoofing**
|
||||
- Botnet spoofs Referer headers to bypass origin check
|
||||
- **Mitigation:** HTTPS prevents header modification + Rate limiter per IP
|
||||
- **Result:** Each IP limited to 3 requests/min
|
||||
|
||||
### Configuration Examples
|
||||
|
||||
**Development Environment:**
|
||||
```bash
|
||||
# .env file
|
||||
PORT=1999
|
||||
HOST=localhost
|
||||
GO_ENV=development
|
||||
ALLOWED_ORIGINS= # Empty allows localhost
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Allows localhost and 127.0.0.1
|
||||
- Allows requests without headers
|
||||
- Rate limit: 3 PDF/min per IP
|
||||
|
||||
**Production Environment:**
|
||||
```bash
|
||||
# .env file
|
||||
PORT=1999
|
||||
HOST=0.0.0.0
|
||||
GO_ENV=production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Only allows specified domains
|
||||
- Requires Origin or Referer header for PDF endpoint
|
||||
- Blocks direct URL access
|
||||
- Rate limit: 3 PDF/min per IP
|
||||
|
||||
### Testing Protection
|
||||
|
||||
**Test Origin Checking:**
|
||||
```bash
|
||||
# ✅ Should succeed (localhost in development)
|
||||
curl http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ✅ Should succeed (valid referer)
|
||||
curl -H "Referer: http://localhost:1999/" \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ❌ Should fail (external referer)
|
||||
curl -H "Referer: https://evil.com/" \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
# Expected: 403 Forbidden
|
||||
```
|
||||
|
||||
**Test Rate Limiting:**
|
||||
```bash
|
||||
# Generate 4 PDFs quickly to trigger rate limit
|
||||
for i in {1..4}; do
|
||||
echo "Request $i:"
|
||||
curl -w "Status: %{http_code}\n" -o /dev/null -s \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Expected output:
|
||||
# Request 1: Status: 200
|
||||
# Request 2: Status: 200
|
||||
# Request 3: Status: 200
|
||||
# Request 4: Status: 429
|
||||
```
|
||||
|
||||
### Customizing Protection
|
||||
|
||||
**Adjust Rate Limits:**
|
||||
|
||||
Edit `main.go` to change limits:
|
||||
|
||||
```go
|
||||
// Current: 3 requests per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
|
||||
// More restrictive: 5 per hour
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)
|
||||
|
||||
// Less restrictive: 10 per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(10, 1*time.Minute)
|
||||
```
|
||||
|
||||
**Apply Protection to Other Endpoints:**
|
||||
|
||||
```go
|
||||
// Protect /cv endpoint
|
||||
protectedCVHandler := middleware.OriginChecker(
|
||||
http.HandlerFunc(cvHandler.CVContent),
|
||||
)
|
||||
mux.Handle("/cv", protectedCVHandler)
|
||||
```
|
||||
|
||||
### Production Deployment Checklist
|
||||
|
||||
Before deploying to production:
|
||||
|
||||
- [ ] Set `GO_ENV=production` in environment
|
||||
- [ ] Configure `ALLOWED_ORIGINS` with your domain(s)
|
||||
- [ ] Test origin checking with external domain
|
||||
- [ ] Test rate limiting with rapid requests
|
||||
- [ ] Verify HTTPS is enabled (prevents header spoofing)
|
||||
- [ ] Set up monitoring for 403/429 responses
|
||||
- [ ] Configure log retention for security analysis
|
||||
- [ ] Test PDF generation under load
|
||||
- [ ] Verify reverse proxy headers (X-Forwarded-For)
|
||||
- [ ] Document allowed origins in runbook
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Problem: Legitimate users getting 403 Forbidden**
|
||||
|
||||
**Cause:** `ALLOWED_ORIGINS` not configured correctly
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Ensure all your domains are listed (comma-separated, no spaces)
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
|
||||
# Check for typos (domain matching is case-insensitive but exact)
|
||||
# Verify in logs which domain is being rejected
|
||||
```
|
||||
|
||||
**Problem: Rate limit too restrictive**
|
||||
|
||||
**Cause:** Legitimate users hitting the 3 requests/min limit
|
||||
|
||||
**Solution:**
|
||||
```go
|
||||
// Increase limit in main.go
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Minute) // or higher
|
||||
```
|
||||
|
||||
**Problem: Behind reverse proxy, rate limiting not working per IP**
|
||||
|
||||
**Cause:** IP detection failing, all requests seen as same IP
|
||||
|
||||
**Solution:**
|
||||
```nginx
|
||||
# Ensure Nginx passes correct headers
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
```
|
||||
|
||||
**Problem: Origin header not being sent**
|
||||
|
||||
**Cause:** Browser doesn't send Origin for same-origin navigation requests
|
||||
|
||||
**Solution:** This is normal browser behavior. The middleware checks `Referer` header as fallback, which browsers do send for navigation.
|
||||
|
||||
For technical API details, see [3-API.md](3-API.md#security-protection).
|
||||
|
||||
---
|
||||
|
||||
## Known Security Considerations
|
||||
|
||||
### PDF Generation Resource Usage
|
||||
|
||||
- **Issue**: PDF generation uses headless Chrome, which consumes significant CPU/memory
|
||||
- **Impact**: Potential DoS via excessive PDF generation requests
|
||||
- **Mitigation**:
|
||||
- Implement rate limiting on `/export/pdf` endpoint
|
||||
- Consider caching generated PDFs
|
||||
- Monitor resource usage
|
||||
|
||||
### Third-Party Dependencies
|
||||
|
||||
External dependencies loaded from CDNs:
|
||||
|
||||
- HTMX from `https://unpkg.com`
|
||||
- Google Fonts from `https://fonts.googleapis.com`
|
||||
|
||||
**Recommendation**: For production, consider:
|
||||
- Self-hosting these dependencies for better control
|
||||
- Using Subresource Integrity (SRI) hashes
|
||||
- Implementing a Content Security Policy
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
If you're forking this project for your own CV:
|
||||
|
||||
1. **Review all code** before deploying
|
||||
2. **Update personal information** in JSON files
|
||||
3. **Configure security headers** appropriate for your use case
|
||||
4. **Enable HTTPS** with valid certificates
|
||||
5. **Keep dependencies updated** regularly
|
||||
6. **Monitor application logs** for suspicious activity
|
||||
7. **Backup your data** regularly
|
||||
8. **Test security** before going live
|
||||
|
||||
## Security Updates
|
||||
|
||||
Security updates will be announced via:
|
||||
|
||||
- GitHub Security Advisories
|
||||
- Release notes on GitHub
|
||||
- Git commit messages tagged with `[SECURITY]`
|
||||
|
||||
## Contact
|
||||
|
||||
For security concerns, please contact the project maintainer via GitHub.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
We appreciate the security research community's efforts in responsibly disclosing vulnerabilities. Thank you for helping keep this project secure!
|
||||
We appreciate the security research community's efforts in responsibly disclosing vulnerabilities. Thank you for helping keep this project secure!
|
||||
Reference in New Issue
Block a user