Deduplicate HTTP security headers between nginx and Flask-Talisman - #169
Merged
Conversation
nginx and Superset's own Flask-Talisman middleware both independently added Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, and Referrer-Policy to every response. nginx's add_header doesn't replace upstream headers, it appends, so responses carried duplicates with different values - per spec/browser handling this silently dropped nginx's preload flag on HSTS (RFC 6797: only the first STS header is processed), risked invalidating X-Frame-Options entirely on some browsers when comma-joined, and silently downgraded Referrer-Policy to nginx's weaker legacy default (UAs take the last policy across combined headers). Only nginx's HSTS line carried `always`, so its other four headers vanished on non-2xx/3xx responses (404, 500), relying entirely on Talisman as an unconditional fallback. Use proxy_hide_header to strip Talisman's upstream copies and let nginx's own add_header lines (all now with `always`) be the sole source of these five headers. Corrects Referrer-Policy to strict-origin-when-cross-origin. Left Talisman's CSP and all other config untouched - reconfiguring Talisman instead would have required replicating Superset's internal default CSP dict verbatim to safely override TALISMAN_CONFIG. Add a functional test (status_headers) that curls both a normal path and a nonexistent one, asserting each header appears exactly once with the expected value on both.
3 tasks
szachovy
added a commit
that referenced
this pull request
Jul 16, 2026
Fixes the two root causes behind the header-dedup fix (#169) never reaching production: stale local image cache in pull_or_build_image() and a publish race with no concurrency guard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
services/superset/nginx.conf) and Superset's own Flask-Talisman middleware both independently addStrict-Transport-Security,X-Content-Type-Options,X-Frame-Options,X-XSS-Protection, andReferrer-Policyto every response.add_headerdoesn't replace upstream headers, it appends — so responses carried duplicates with different values, and per spec/browser handling this wasn't "defense in depth," it was silent misconfiguration:preload) came first, so nginx'spreloadcopy was spec-mandated to be ignored entirely.SAMEORIGINvsDENYrisked comma-joining into an invalid value on some browsers (documented: Safari 6.0.5), which can drop clickjacking protection entirely.no-referrer-when-downgrade) silently overrode Superset's stricterstrict-origin-when-cross-origin.always, so its other four headers vanished on non-2xx/3xx responses (404, 500) — the response was only still hardened because Talisman's headers are unconditional.Fix
proxy_hide_headerfor all five headers so Talisman's upstream copies never reach the client, and made nginx's ownadd_headerlines (all now withalways) the sole source of truth — headers now survive on error pages too.Referrer-Policytostrict-origin-when-cross-origin. KeptX-Frame-Options: DENY(nginx's existing, stricter value — no dashboard-embedding feature exists anywhere in this repo, so nothing depends on same-origin framing).TALISMAN_CONFIG/CSP entirely untouched. Reconfiguring Talisman instead would have required replicating Superset's internal default CSP dict verbatim to safely override the monolithicTALISMAN_CONFIG, without ground-truth on the exact defaults shipped inapache/superset:4.0.2— not worth the risk of silently weakening CSP when the nginx-side fix is fully sufficient.CHANGELOG.mdentry under### Fixed.Test plan
status_headers()tofunctional_superset.py, following the existingstatus_database/status_swarmpattern (auto-runs via theOverlaymetaclass on instantiation, nofunctional.ymlchanges needed). Curls both/and a nonexistent path, asserts each of the 5 headers appears exactly once with the exact expected value on both — directly encodes this PR's acceptance criteria as a regression test.python3 -m py_compile,flake8,pylintall pass on the modified test file (two pylint import-error warnings are pre-existing and confirmed unrelated by diffing againstmaster).curl -Ioutput.nginx -t) against an actual deployment before merge.