Security headers in Skapp
Overview
Skapp sets its security headers at the Cloudflare edge (zone skapp.com), so they apply to every *.skapp.com host — web app, APIs, and all tenant subdomains — with no application changes. Seven headers are deployed at the edge, Content-Type comes from the application, two headers are intentionally skipped, and CSP is parked as future work. External grade: A.
What Skapp deploys
| Header | Value | Notes | |
|---|---|---|---|
| Strict-Transport-Security | (via HSTS toggle) | See Cloudflare config below | |
| X-Frame-Options | DENY | ||
| X-Content-Type-Options | nosniff | ||
| Referrer-Policy | strict-origin-when-cross-origin | ||
| Permissions-Policy | camera=(), microphone=(), geolocation=() | ||
| Cross-Origin-Opener-Policy | same-origin | ||
| Cross-Origin-Resource-Policy | same-site | Chosen over same-origin because frontend (app.skapp.com) and API (app-api.skapp.com) are different subdomains | |
| Content-Type | application/json; charset=utf-8 on JSON | Already set by the application (Spring Boot) — no edge change | ## What Skapp deliberately does not set- Content-Security-Policy (CSP) — parked (future work). Skapp loads scripts from many third parties (Google sign-in, Firebase, Hotjar, Sentry, reCAPTCHA, Google Analytics, and more), so a strict script-src allowlist would need updating per new integration. Plan when picked up: deploy Content-Security-Policy-Report-Only first, collect violations from real traffic, build the allowlist, then enforce. |
- Cross-Origin-Embedder-Policy (COEP) — skipped by design. Skapp doesn't use cross-origin isolation features;
require-corpwould break Google Fonts, S3/CloudFront assets, and third-party scripts for no benefit. - Clear-Site-Data — skipped by design. The
"cookies"directive clears cookies for the wholeskapp.comdomain and all subdomains. Skapp intentionally lets one user hold concurrent sessions across multiple tenant subdomains, and logout from one tenant must not end the others. Session teardown is handled by tenant-scoped server-side invalidation instead.
Cloudflare configuration
All in the skapp.com zone. Two places:
1. The six static headers — one Transform Rule
Rules → Transform Rules → Modify Response Header → rule Security Headers – Tier 1.
- Matches: All incoming requests — covers every
*.skapp.comhost including future tenant subdomains, nothing to maintain. - Operation: Set static per header (Set, not Add — avoids duplicates).
- Headers: the six edge headers from the table above (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP, CORP).
- To change a value: edit the rule, update, Deploy (instant).
- To exclude a subdomain (e.g. an embeddable surface that must not be
X-Frame-Options: DENY): change the match to(http.host wildcard "*.skapp.com") and not (http.host eq "<host>").

2. HSTS — separate toggle
SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) → Enabled.
- Not part of the Transform Rule — it has its own control.
- The
preloaddirective is set, but the domain is NOT submitted to hstspreload.org. Don't submit until every*.skapp.comhost is confirmed permanently HTTPS (removal is slow).
Not configured in Cloudflare
- CSP — when added, use a separate rule.
- COEP — not added (intentional).
- Clear-Site-Data — not added (intentional). If ever revisited, it must be a path-scoped rule on the logout endpoint only — never "All incoming requests."
Verify
securityheaders.com or MDN Observatory on app.skapp.com → expect grade A. Or PowerShell:
powershell
$resp = Invoke-WebRequest -Uri "https://app.skapp.com/" -Method Head -UseBasicParsing
"x-frame-options","x-content-type-options","referrer-policy","permissions-policy",
"cross-origin-opener-policy","cross-origin-resource-policy","strict-transport-security" |
ForEach-Object {
if ($resp.Headers.Keys -contains $_) { Write-Host "PRESENT $_" -ForegroundColor Green }
else { Write-Host "MISSING $_" -ForegroundColor Red }
}
Run against app-api.skapp.com and a sample tenant subdomain too, to confirm zone-wide coverage.