devin-ai-integration[bot] opened a new issue, #42215:
URL: https://github.com/apache/superset/issues/42215
## Security scan findings — 2026-07-19
Nightly Security Sweep of this fork, grounded in
[`SECURITY.md`](../blob/master/SECURITY.md) (the canonical security model) plus
an independent white-hat pass. This run scanned the fork's divergence from
`apache/superset` (`superset/constants.py`,
`superset/initialization/__init__.py`, `superset/views/internal.py`) and the
surrounding config/auth surface.
**1 new confirmed finding** (no existing open issue or PR tracks it).
| Severity | File | Line | Rule violated | Description |
|---|---|---|---|---|
| **Critical** `white-hat` | `superset/initialization/__init__.py` (and
`superset/constants.py`) | `__init__.py` 702–709; `constants.py` 30 | Concrete
exploit path (session/token forgery → auth bypass). Also SECURITY.md *In
Scope*: "An Apache Superset default fails open without an accompanying
documented hardening requirement" and "A user bypasses authentication … reaches
an authenticated endpoint without logging in." | The production startup guard
for an insecure `SECRET_KEY` was downgraded from fail-closed (`sys.exit(1)`) to
a mere `logger.warning`, and the default sentinel was changed to the short
literal `thisISaSECRET_1234`. A production deployment that does not override
`SUPERSET_SECRET_KEY` now boots instead of refusing to start. See exploit path
below. |
### Finding 1 — Insecure default `SECRET_KEY` permitted in production
(authentication bypass)
**Principal assumed:** Public (anonymous) → escalated to Admin.
**Matrix row:** `Public (anonymous)` has *no* read/write/execute/manage
capability. This finding lets an anonymous attacker act as any principal, up to
Admin.
**Root cause (fork commits `36407a39e1`, `d87a935e79`):**
- `superset/constants.py`: `CHANGE_ME_SECRET_KEY` changed from the upstream
placeholder `"CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET"` to `"thisISaSECRET_1234"`.
- `superset/initialization/__init__.py::check_secret_key`: in non-debug /
non-testing (i.e. production) mode, upstream logs an error and calls
`sys.exit(1)` when `SECRET_KEY` is empty or equal to the built-in default. The
fork replaced that with `logger.warning(...)` and returns, so startup proceeds.
`superset/config.py` defines `SECRET_KEY =
os.environ.get("SUPERSET_SECRET_KEY") or CHANGE_ME_SECRET_KEY`, so a deployment
that forgets to set the env var / config runs with the hardcoded,
publicly-known key.
**Concrete exploit path:**
1. Operator deploys the fork without overriding `SUPERSET_SECRET_KEY`.
Upstream Superset would refuse to boot (fail-closed); this fork boots with
`SECRET_KEY = "thisISaSECRET_1234"`.
2. Flask signs the session cookie (and Flask-Login "remember me" token) with
`app.config["SECRET_KEY"]`. The value is public in this open-source repo.
3. An unauthenticated attacker mints/signs a session cookie carrying an
arbitrary `user_id` (e.g. an Admin's) using the known key and sends it to any
authenticated endpoint.
4. The server validates the signature with the same key → the attacker is
authenticated as that user. Full authentication bypass and privilege escalation
to Admin, with no credentials.
This is exactly the fail-open-default class `SECURITY.md` puts *in scope*:
the codebase is responsible for shipping fail-closed defaults, and upstream
does (`sys.exit(1)`). Removing that guard ships an undocumented fail-open
default.
**Validation of the regression:** the repo already contains
`tests/unit_tests/test_check_secret_key.py`, whose
`test_check_secret_key_refuses_to_start_when_insecure` asserts `SystemExit` in
production. Those 3 parametrized cases currently **fail** on the fork's code,
confirming the contract was broken.
**Fix:** restore the fail-closed `sys.exit(1)` guard and revert the default
sentinel to the upstream placeholder. Tracked by a linked PR.
---
### Notes on items considered and discarded
- `superset/views/internal.py` (`/internal/data_export`) — reviewed. It is
gated by `@has_access_api` (`can_data_export`, Admin-only by default) and
builds queries via the SQLAlchemy expression language with table/column
validation against reflected names, so it neither bypasses the matrix nor
injects SQL. Its earlier SQL-injection / access issues were already fixed in
merged PR #30. No residual in-scope finding.
- Other secrets in `constants.py` (`CHANGE_ME_GUEST_TOKEN_JWT_SECRET`,
`CHANGE_ME_GLOBAL_ASYNC_QUERIES_JWT_SECRET`) and their `check_*` guards are
unchanged and still fail-closed (`sys.exit(1)`).
*Filed by the automated Nightly Security Sweep.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]