GitHub user rennf93 edited a discussion: Idea: documented opt-in 
application-layer security (attack screening, automatic bans, per-route 
policies) for internet-exposed Superset (via FLASK_APP_MUTATOR)

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. That 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 
would 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 integration 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/`).
- Honeypot traps: a hidden field in the login form that no person can see. A 
client that fills it is a bot working through the instance, which is a sign of 
an intrusion attempt, so it is banned rather than just turned away.
- Behavior rules, for example banning clients that keep hitting 404s (scanners) 
or that repeat suspicious request patterns.
- Country allow or block lists, blocking cloud-provider IP ranges, and 
user-agent filtering.
- Emergency mode, which locks the instance down to an allowlist during an 
incident, and a passive (log-only) mode to try everything before enforcing it.
- Optionally, rules changed at runtime without a restart (bans, country blocks, 
budgets, an emergency 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, with the 
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 
is not a trusted proxy is ignored. 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. CSRF behavior 
was unchanged.
- Finding: with the default settings the rison `q` search parameter gives false 
positives on text such as "union select", which is why the snippet excludes `q` 
and `form_data`. Also, the library 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 change 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 did not 
run a full Docusaurus build.

### Dependency impact

None for Superset. It is 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 does not replace the edge and the edge does not 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 
will open a PR referencing this discussion if the idea is welcome. If you would 
rather not point at a third-party package in the official docs, I understand 
and will drop it.

I used AI assistance to prepare this and reviewed and verified everything 
myself.

**Project background:** Guard Core's first library is 
[fastapi-guard](https://github.com/Guard-Core/fastapi-guard), public since 
August 2024 (first PyPI release, 0.1.0, on 2024-08-26). It has 825 GitHub 
stars, 65 releases and about 62,000 PyPI downloads in the last month 
(pypistats.org). The framework-agnostic engines and the adapters for other 
frameworks and languages came later and run the same detection rules, checked 
in CI against shared conformance fixtures.

GitHub link: https://github.com/apache/superset/discussions/45372

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: 
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to