| name | security-headers-check |
| description | Verify artstroy.net HTTP security headers against OWASP best practices. Checks CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, and Referrer-Policy. Creates issues for missing or misconfigured headers. |
Security Headers Check
ArtStroy publishes articles about OWASP and web security. Our own security headers must be exemplary — they are effectively our credibility at the HTTP layer.
When to Run
- Monthly (alongside dependency audit)
- After any nginx configuration change
- After a new deploy or server migration
- When
security-digest flags a new headers-related attack vector
Headers to Check
Required Headers
| Header | Required Value | Purpose |
|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | Forces HTTPS; preload eligible |
X-Content-Type-Options | nosniff | Prevents MIME sniffing attacks |
X-Frame-Options | DENY or SAMEORIGIN | Prevents clickjacking |
Referrer-Policy | strict-origin-when-cross-origin | Controls referrer information |
Permissions-Policy | geolocation=(), microphone=(), camera=() (minimum) | Restricts browser feature access |
Content-Security-Policy | See CSP section below | XSS mitigation |
CSP Policy for ArtStroy
ArtStroy is an Astro SSG site with no server-side code injection. A strict CSP is achievable:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{nonce}';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://plausible.io;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
Note on unsafe-inline for styles: Tailwind inlines some styles; if this can be eliminated, do it. Otherwise it's an acceptable trade-off documented in decisions.md.
Plausible analytics: requires connect-src https://plausible.io — already in the policy above.
How to Check
curl -I https://artstroy.net 2>/dev/null | grep -i -E "strict-transport|x-content|x-frame|referrer|permissions|content-security"
curl -D - -o /dev/null https://artstroy.net 2>/dev/null
Or use a security header grader:
curl "https://http-observatory.security.mozilla.org/api/v1/analyze?host=artstroy.net"
Target score: A+ on Mozilla Observatory and securityheaders.com.
Findings Format
For each missing or misconfigured header:
### {Header-Name} — {MISSING / MISCONFIGURED}
- **Current value**: {value or "not present"}
- **Required value**: {what it should be}
- **Risk**: {1 sentence on what attack this enables}
- **Fix**: update nginx.conf with:
```nginx
add_header {Header-Name} "{value}" always;
- Reference: {OWASP or MDN link}
## nginx.conf Security Headers Block
The correct location for all security headers in nginx:
```nginx
server {
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
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 "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; ..." always;
}
The always directive ensures headers are sent on all responses including error pages.
Reporting
After each check, create a comment on the monthly security audit issue:
## Security Headers Check — {YYYY-MM-DD}
**Observatory Score**: {A+ / A / B / C / F}
**securityheaders.com**: {link to report}
| Header | Status | Notes |
|---|---|---|
| HSTS | ✅ / ❌ | |
| X-Content-Type-Options | ✅ / ❌ | |
| X-Frame-Options | ✅ / ❌ | |
| Referrer-Policy | ✅ / ❌ | |
| Permissions-Policy | ✅ / ❌ | |
| CSP | ✅ / ⚠️ / ❌ | {any unsafe-inline or unsafe-eval present} |
**Issues created**: {count} (links)