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]

Reply via email to