mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-17 06:26:31 +03:00
133 lines
5.8 KiB
Markdown
133 lines
5.8 KiB
Markdown
# CSP / SPA Debugging — Entrypoint Header Overwrite
|
|
|
|
Debugging guide for the failure mode where an entrypoint-level `headers` middleware
|
|
silently breaks cross-origin SPAs by overwriting router-level
|
|
Content-Security-Policy headers.
|
|
|
|
## The Mechanism
|
|
|
|
Traefik middleware execution order on the response path is the **reverse** of the
|
|
request path:
|
|
|
|
```
|
|
Request: entrypoint middlewares → router middlewares → service middlewares → backend
|
|
Response: backend → service middlewares → router middlewares → entrypoint middlewares
|
|
```
|
|
|
|
Entrypoint-level middlewares run **last on the response path**. The `headers`
|
|
middleware documentation states:
|
|
|
|
> "Custom headers will overwrite existing headers if they have identical names."
|
|
> — [Traefik Headers middleware docs](https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/headers/)
|
|
|
|
Therefore, when an entrypoint applies a chain containing `contentSecurityPolicy`
|
|
(e.g., a `default@file` security-headers chain on `websecure`), it overwrites any
|
|
router-level CSP on the response. **Router-level CSP overrides are impossible**
|
|
when the entrypoint also sets CSP.
|
|
|
|
## The Failure
|
|
|
|
If the entrypoint CSP is generic — typically `default-src 'self'` with no
|
|
`connect-src` directive — the browser enforces it and blocks every cross-origin
|
|
`fetch`/`XHR` the SPA makes. The SPA's own page and static assets load normally
|
|
(they are same-origin), but all API calls to a different origin silently fail.
|
|
|
|
**The backend logs show zero requests from the SPA.** This is the key tell that
|
|
distinguishes this failure from backend issues, network problems, or CORS
|
|
misconfiguration on the backend.
|
|
|
|
## Diagnostic Flow
|
|
|
|
```
|
|
SPA page loads (200) but API calls fail silently
|
|
│
|
|
├─ 1. Check CSP on the SPA page:
|
|
│ curl -D- -o /dev/null https://your-spa.example.com/ | grep -i content-security-policy
|
|
│
|
|
│ If you see `default-src 'self'` with no `connect-src` → this is the problem.
|
|
│ The browser blocks all cross-origin requests.
|
|
│
|
|
├─ 2. Confirm zero requests reach the backend:
|
|
│ Check backend logs for the complete absence of requests from the SPA.
|
|
│ (Not 403s, not CORS errors — nothing at all.)
|
|
│
|
|
├─ 3. Identify the source of the CSP:
|
|
│ Check whether the CSP comes from the backend or from Traefik.
|
|
│ curl -D- -o /dev/null https://your-backend-api.example.com/ | grep -i content-security-policy
|
|
│
|
|
│ If the backend emits its own (different) CSP but the SPA page shows a
|
|
│ generic one, the entrypoint middleware is overwriting it.
|
|
│
|
|
└─ 4. Check the entrypoint middleware chain:
|
|
Look at the entrypoint's `http.middlewares` list in static config,
|
|
then trace the chain to find `contentSecurityPolicy` in a headers middleware.
|
|
```
|
|
|
|
## The Fix
|
|
|
|
Remove `contentSecurityPolicy` from the entrypoint-level default middleware chain.
|
|
Let each service emit its own tailored CSP. Many applications (GoToSocial,
|
|
Mastodon, Nextcloud, etc.) ship their own CSP headers that are specific to their
|
|
needs.
|
|
|
|
A proxy-wide `default-src 'self'` is actively harmful for any SPA that
|
|
communicates with a different origin. The proxy should not impose a CSP that
|
|
overrides what the application itself intends.
|
|
|
|
Other security headers (HSTS, X-Content-Type-Options, X-Frame-Options,
|
|
Referrer-Policy) are safe to keep in the entrypoint chain — they do not interfere
|
|
with cross-origin API calls.
|
|
|
|
## CORS Preflight Interception
|
|
|
|
The `headers` middleware also intercepts CORS preflight requests when CORS headers
|
|
are configured:
|
|
|
|
> "If CORS headers are set, then the middleware does not pass preflight requests
|
|
> to any service, instead the response will be generated and sent back to the
|
|
> client directly."
|
|
> — [Traefik Headers middleware docs](https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/headers/)
|
|
|
|
This is useful when a backend does not handle `OPTIONS` preflight requests (returns
|
|
405). Adding a `headers` middleware with `accessControlAllowMethods` and
|
|
`accessControlAllowOriginList` to the router handles preflights at the proxy level.
|
|
|
|
Example:
|
|
|
|
```yaml
|
|
http:
|
|
middlewares:
|
|
cors-preflight:
|
|
headers:
|
|
accessControlAllowMethods:
|
|
- GET
|
|
- POST
|
|
- PUT
|
|
- DELETE
|
|
- PATCH
|
|
- OPTIONS
|
|
accessControlAllowOriginList:
|
|
- "*"
|
|
accessControlAllowHeaders:
|
|
- "*"
|
|
accessControlMaxAge: 120
|
|
addVaryHeader: true
|
|
```
|
|
|
|
## Related but Distinct Failures
|
|
|
|
| Failure | Symptom | Cause |
|
|
|---------|---------|-------|
|
|
| **CSP overwrite (this guide)** | SPA loads, zero API requests reach backend | Entrypoint `headers` middleware overwrites router CSP |
|
|
| **Rate limiting** | SPA loads, 429 errors on asset/API requests | Entrypoint rate limit too low for SPA burst |
|
|
| **Double middleware execution** | Doubled rate-limit counts, duplicate compression | Same middleware declared at both entrypoint and router level |
|
|
|
|
## Sources
|
|
|
|
- [Traefik Headers middleware](https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/headers/) — header overwrite behavior, CORS preflight interception
|
|
- [Traefik Middleware overview](https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/overview/) — router vs. service middleware execution order
|
|
- [Traefik Entrypoints](https://doc.traefik.io/traefik/reference/install-configuration/entrypoints/) — entrypoint-level `http.middlewares` configuration
|
|
- [MDN Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) — `default-src`, `connect-src`, browser enforcement
|
|
- [MDN CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) — preflight request mechanics
|
|
- [unrolled/secure](https://github.com/unrolled/secure#available-options) — the library Traefik uses for security headers
|