Security headers are dropped on non-2xx responses for custom apps
-
Hello,
I am running a custom app on Cloudron 10.0.5 and noticed that three security headers are missing from any response that is not a 2xx or 3xx. I believe the reverse proxy strips them and re-adds its own only on successful responses.
What I measured
Running the app locally, outside Cloudron, it emits twelve security headers, identical on every response regardless of status code. Here is a successful response and an authentication failure, both from the same app:
GET /api/health -> 200 OK GET /api/me -> 401 Unauthorized both carry, byte for byte: cross-origin-opener-policy: same-origin cross-origin-resource-policy: same-origin origin-agent-cluster: ?1 permissions-policy: camera=(), microphone=(), geolocation=() referrer-policy: no-referrer strict-transport-security: max-age=15552000; includeSubDomains x-content-type-options: nosniff x-dns-prefetch-control: off x-download-options: noopen x-frame-options: DENY x-permitted-cross-domain-policies: none x-xss-protection: 0Behind Cloudron, the same two endpoints give:
200 OK -> nine of the twelve, plus Strict-Transport-Security: max-age=63072000 X-Content-Type-Options: nosniff Referrer-Policy: no-referrer 401 -> the same nine, and nothing elseThe nine that survive are exactly the ones Cloudron does not manage. The three that disappear are exactly the ones it does. On the 200 they come back with Cloudron's own values and in a different header casing, so they are the proxy's, not the app's.
Two consequences
Error responses reach the browser without HSTS, nosniff or a referrer policy, and an app cannot fix this on its own: whatever it emits is removed the same way.
The HSTS value Cloudron sets is also weaker than the one the app sent. The app sends
max-age=15552000; includeSubDomains, and what reaches the client ismax-age=63072000with noincludeSubDomains, so subdomain coverage is silently lost.What I think would fix it
Adding
alwaysto theadd_headerdirectives in the app nginx configuration, so the headers are emitted on every response and not only on 2xx and 3xx. Preserving the upstream value when the app already sets one would also avoid the downgrade, though thealwayspart is the one that matters to me.I did not try to work around this by editing the nginx config, since app configs are rewritten on restart and upgrade.
Happy to provide more details or test a fix on my side.
Thanks
-
G girish has marked this topic as solved
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login