security: implement CSP headers (API middleware + Nginx configs + dev meta tags)
API middleware: - SecurityHeaders now sets Content-Security-Policy from config/security.php - Default API policy: "default-src 'none'; frame-ancestors 'none'" - Supports report-only mode via CSP_REPORT_ONLY env var - Policy value configurable via CSP_POLICY env var Nginx deployment configs (deploy/nginx/): - security-headers.conf: shared headers for all server blocks - csp-api.conf: restrictive JSON-only policy for api.crewli.app - csp-spa.conf: SPA policy for app/admin (self + unsafe-inline styles) - csp-portal.conf: portal policy matching SPA Development: - CSP meta tags added to all three index.html files - Includes 'unsafe-inline' + 'unsafe-eval' for Vite HMR/loader script - Each app allows its own ws:// port for HMR websocket Resolves security finding A13-9. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
deploy/README.md
Normal file
69
deploy/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Crewli Deployment — Security Configuration
|
||||
|
||||
## Nginx Security Headers
|
||||
|
||||
Copy the configuration snippets to your Nginx server:
|
||||
|
||||
### API (api.crewli.app)
|
||||
```nginx
|
||||
server {
|
||||
server_name api.crewli.app;
|
||||
|
||||
include /path/to/deploy/nginx/security-headers.conf;
|
||||
include /path/to/deploy/nginx/csp-api.conf;
|
||||
|
||||
# ... rest of config
|
||||
}
|
||||
```
|
||||
|
||||
### Organizer App (app.crewli.app)
|
||||
```nginx
|
||||
server {
|
||||
server_name app.crewli.app;
|
||||
|
||||
include /path/to/deploy/nginx/security-headers.conf;
|
||||
include /path/to/deploy/nginx/csp-spa.conf;
|
||||
|
||||
# ... rest of config
|
||||
}
|
||||
```
|
||||
|
||||
### Admin (admin.crewli.app)
|
||||
```nginx
|
||||
server {
|
||||
server_name admin.crewli.app;
|
||||
|
||||
include /path/to/deploy/nginx/security-headers.conf;
|
||||
include /path/to/deploy/nginx/csp-spa.conf;
|
||||
|
||||
# ... rest of config
|
||||
}
|
||||
```
|
||||
|
||||
### Portal (portal.crewli.app)
|
||||
```nginx
|
||||
server {
|
||||
server_name portal.crewli.app;
|
||||
|
||||
include /path/to/deploy/nginx/security-headers.conf;
|
||||
include /path/to/deploy/nginx/csp-portal.conf;
|
||||
|
||||
# ... rest of config
|
||||
}
|
||||
```
|
||||
|
||||
## CSP Rollout Process
|
||||
|
||||
1. Start with `Content-Security-Policy-Report-Only` (uncomment in `csp-spa.conf`)
|
||||
2. Monitor browser console for CSP violations for 1-2 weeks
|
||||
3. Add any missing sources to the policy
|
||||
4. Switch to enforcing `Content-Security-Policy`
|
||||
5. Monitor for false positives after enforcement
|
||||
|
||||
## DirectAdmin Integration
|
||||
|
||||
If using DirectAdmin with Nginx:
|
||||
1. Place the `.conf` files in `/usr/local/directadmin/data/users/USERNAME/nginx.conf`
|
||||
or use DirectAdmin's custom Nginx configuration feature
|
||||
2. Reload Nginx: `service nginx reload`
|
||||
3. Verify headers: `curl -I https://app.crewli.app | grep -i security`
|
||||
3
deploy/nginx/csp-api.conf
Normal file
3
deploy/nginx/csp-api.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
# CSP for api.crewli.app
|
||||
# The API serves JSON only — no scripts, styles, or images needed.
|
||||
add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none'" always;
|
||||
4
deploy/nginx/csp-portal.conf
Normal file
4
deploy/nginx/csp-portal.conf
Normal file
@@ -0,0 +1,4 @@
|
||||
# CSP for portal.crewli.app
|
||||
# Same policy as SPA but with stricter connect-src since portal
|
||||
# should only talk to the API.
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.crewli.app; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
|
||||
15
deploy/nginx/csp-spa.conf
Normal file
15
deploy/nginx/csp-spa.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
# CSP for app.crewli.app and admin.crewli.app
|
||||
# Vite bundles all JS/CSS into same-origin files.
|
||||
# 'unsafe-inline' for style-src is required by Vuetify (inline styles for theming).
|
||||
# img-src https: allows organisation logos loaded from external URLs.
|
||||
# connect-src must include the API domain for XHR/fetch calls.
|
||||
#
|
||||
# IMPORTANT: Start with Content-Security-Policy-Report-Only to catch
|
||||
# false positives. Switch to Content-Security-Policy after 1-2 weeks
|
||||
# of clean logs.
|
||||
|
||||
# Report-only mode (start with this):
|
||||
# add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.crewli.app; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
|
||||
|
||||
# Enforce mode (switch to this after testing):
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.crewli.app; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
|
||||
19
deploy/nginx/security-headers.conf
Normal file
19
deploy/nginx/security-headers.conf
Normal file
@@ -0,0 +1,19 @@
|
||||
# ============================================================
|
||||
# Crewli Security Headers — Nginx Configuration
|
||||
# ============================================================
|
||||
# Include this file in each server block:
|
||||
# include /path/to/deploy/nginx/security-headers.conf;
|
||||
#
|
||||
# Three separate CSP policies for API vs SPA vs Portal.
|
||||
# Adjust connect-src if the API domain changes.
|
||||
# ============================================================
|
||||
|
||||
# --- Shared headers (all server blocks) ---
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
|
||||
# HSTS: enable after confirming HTTPS works correctly
|
||||
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
Reference in New Issue
Block a user