You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Idea: documented opt-in application-layer security (attack screening, automatic bans, per-route policies) for internet-exposed Superset via FLASK_APP_MUTATOR
#45372
Disclosure: I maintain flaskapi-guard, the extension discussed below.
Problem
Superset instances exposed to the internet get hit by login and API abuse and by generic scanners. Today the built-in answer is Flask-Limiter: RATELIMIT_ENABLED defaults to os.environ.get("SUPERSET_ENV") == "production" and RATELIMIT_APPLICATION = "50 per second" (config.py). It throttles, but it has no IP allow or block lists, no temporary bans for repeat offenders and no screening of requests for attack patterns. The security guide recommends a WAF at the edge, which covers a different layer: inside the application, Superset is the one that knows its routes, its real clients after trusted-proxy resolution, and who keeps sending attack strings.
Proposal
Documentation only, no code change in Superset. FLASK_APP_MUTATOR already hands administrators the Flask app after initialization, and flaskapi-guard (PyPI, MIT) is a plain Flask extension that registers before_request and after_request hooks, so it can be enabled from superset_config.py today. I'd add one opt-in bullet and a tested snippet under "Additional Security Layers" in docs/admin_docs/security/securing_superset.mdx, covering:
Trusted proxies and how that relates to ENABLE_PROXY_FIX (use one mechanism, because ProxyFix trusts X-Forwarded-For from any peer).
No request-body scanning (SQL Lab and chart queries legitimately carry SQL), and excluding the rison q and form_data query parameters, which carry free-text search and SQL.
Leaving Talisman and CORS to Superset (guard security headers and CORS off), and leaving rate limiting to Flask-Limiter.
Leaving the guard's SSRF category out, because of the Referer finding below.
A passive (log-only) mode to trial it first.
What else the engine offers
I kept the branch to a small, safe subset. The rest is already in flaskapi-guard and I can wire whatever you find useful:
Per-route policies: a strict budget with bans on the login view (/login/), an IP allowlist only on database connection management (/api/v1/database/), and per-client budgets on SQL Lab execution (/api/v1/sqllab/execute/).
Behavior rules, for example banning clients that keep hitting 404s (scanners) or repeat suspicious request patterns.
Country allow and block lists, blocking cloud-provider IP ranges, user-agent filtering.
Emergency mode, which locks the instance down to an allowlist during an incident, and the passive mode above.
Optionally, rules changed at runtime without a restart (bans, country blocks, budgets, a lockdown) and security events for monitoring, through a separate agent that is off by default and not part of this proposal.
What I tested
I ran the real Superset app (current master, SQLite metadata DB, create_app() plus superset run) with the exact snippet from the docs change, guard on and off:
Normal pages and authenticated API calls pass from scripted clients, including the JSON login and JWT flow.
Attack patterns in the query string get 400, repeated attacks escalate to a temporary ban (403), other clients are unaffected.
A static block list returns 403. A spoofed X-Forwarded-For from a peer that isn't a trusted proxy is ignored, and the same header from a trusted proxy is honoured.
With the guard off, behavior is identical to stock Superset. Response headers (Talisman CSP, CORS) were identical with the guard on and off, and CSRF behavior was unchanged.
Finding: with default settings the rison q search parameter gives false positives on text like "union select", which is why the snippet excludes q and form_data. The library also defaults to enable_redis=True against redis://localhost:6379, so the snippet sets it explicitly.
Finding (added after posting): browsers send the page address as Referer, and guard-core 4.4.0 flags localhost and LAN addresses in that header as SSRF. With the first version of the snippet every API call from a browser opened on localhost or a LAN IP got 400. My scripted probes sent no Referer, which is why I missed it. The snippet now leaves the SSRF category out (enabled_detection_categories). With that, those requests pass and a Referer carrying a script tag still gets 400 (checked with flaskapi-guard 4.4.0 and the snippet's exact settings, not re-run on a full Superset). I reported the engine bug upstream.
The docs link linter (node scripts/lint-docs-links.mjs) passes. I didn't run a full Docusaurus build.
Dependency impact
None for Superset. It's documentation, and the extension is installed by the administrator into their own environment.
Alternatives
A WAF or reverse-proxy policy (Cloudflare, AWS WAF, nginx with ModSecurity, fail2ban) protects the edge and stays recommended. This is the application layer, so it doesn't replace the edge and the edge doesn't replace it. The two complement each other, and the guard works behind a WAF (through trusted proxies) as well as without one.
A tested branch with the docs change is ready (https://github.com/rennf93/superset/tree/feat/flaskapi-guard-security), and I'll open a PR referencing this discussion if the idea is welcome. If you'd rather not point at a third-party package in the official docs, I understand and I'll drop it.
I used AI assistance to prepare this and reviewed and verified everything myself.
Who I am: I'm rennf93. I wrote fastapi-guard, public since August 2024 (first PyPI release, 0.1.0, on 2024-08-26), now at 825 GitHub stars, 65 releases and about 62,000 PyPI downloads last month (pypistats.org). The engines and adapters for other languages and frameworks came later and run the same detection rules, checked in CI against shared conformance fixtures.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Disclosure: I maintain flaskapi-guard, the extension discussed below.
Problem
Superset instances exposed to the internet get hit by login and API abuse and by generic scanners. Today the built-in answer is Flask-Limiter:
RATELIMIT_ENABLEDdefaults toos.environ.get("SUPERSET_ENV") == "production"andRATELIMIT_APPLICATION = "50 per second"(config.py). It throttles, but it has no IP allow or block lists, no temporary bans for repeat offenders and no screening of requests for attack patterns. The security guide recommends a WAF at the edge, which covers a different layer: inside the application, Superset is the one that knows its routes, its real clients after trusted-proxy resolution, and who keeps sending attack strings.Proposal
Documentation only, no code change in Superset.
FLASK_APP_MUTATORalready hands administrators the Flask app after initialization, andflaskapi-guard(PyPI, MIT) is a plain Flask extension that registersbefore_requestandafter_requesthooks, so it can be enabled fromsuperset_config.pytoday. I'd add one opt-in bullet and a tested snippet under "Additional Security Layers" indocs/admin_docs/security/securing_superset.mdx, covering:ENABLE_PROXY_FIX(use one mechanism, becauseProxyFixtrustsX-Forwarded-Forfrom any peer).qandform_dataquery parameters, which carry free-text search and SQL.Refererfinding below.What else the engine offers
I kept the branch to a small, safe subset. The rest is already in flaskapi-guard and I can wire whatever you find useful:
/login/), an IP allowlist only on database connection management (/api/v1/database/), and per-client budgets on SQL Lab execution (/api/v1/sqllab/execute/).What I tested
I ran the real Superset app (current master, SQLite metadata DB,
create_app()plussuperset run) with the exact snippet from the docs change, guard on and off:X-Forwarded-Forfrom a peer that isn't a trusted proxy is ignored, and the same header from a trusted proxy is honoured.qsearch parameter gives false positives on text like "union select", which is why the snippet excludesqandform_data. The library also defaults toenable_redis=Trueagainstredis://localhost:6379, so the snippet sets it explicitly.Referer, and guard-core 4.4.0 flagslocalhostand LAN addresses in that header as SSRF. With the first version of the snippet every API call from a browser opened on localhost or a LAN IP got 400. My scripted probes sent noReferer, which is why I missed it. The snippet now leaves the SSRF category out (enabled_detection_categories). With that, those requests pass and aReferercarrying a script tag still gets 400 (checked with flaskapi-guard 4.4.0 and the snippet's exact settings, not re-run on a full Superset). I reported the engine bug upstream.node scripts/lint-docs-links.mjs) passes. I didn't run a full Docusaurus build.Dependency impact
None for Superset. It's documentation, and the extension is installed by the administrator into their own environment.
Alternatives
A WAF or reverse-proxy policy (Cloudflare, AWS WAF, nginx with ModSecurity, fail2ban) protects the edge and stays recommended. This is the application layer, so it doesn't replace the edge and the edge doesn't replace it. The two complement each other, and the guard works behind a WAF (through trusted proxies) as well as without one.
A tested branch with the docs change is ready (https://github.com/rennf93/superset/tree/feat/flaskapi-guard-security), and I'll open a PR referencing this discussion if the idea is welcome. If you'd rather not point at a third-party package in the official docs, I understand and I'll drop it.
I used AI assistance to prepare this and reviewed and verified everything myself.
Who I am: I'm rennf93. I wrote fastapi-guard, public since August 2024 (first PyPI release, 0.1.0, on 2024-08-26), now at 825 GitHub stars, 65 releases and about 62,000 PyPI downloads last month (pypistats.org). The engines and adapters for other languages and frameworks came later and run the same detection rules, checked in CI against shared conformance fixtures.
All reactions