From 99ca0f7504118ad12bf76786402b130be152b368 Mon Sep 17 00:00:00 2001 From: Francesco Castaldi Date: Wed, 28 Oct 2026 13:38:30 +0100 Subject: [PATCH 1/5] fix(security): widen cryptography and msgpack ranges and bump pyarrow floor to resolve Trivy vulnerabilities (#45087) This PR addresses and resolves #45087 by updating the security floors and supported version ranges for cryptography, msgpack, and pyarrow. This resolves container security scanner (Trivy) HIGH/MEDIUM vulnerability findings without causing pip dependency resolver conflicts in Apache Superset. Key Changes: - cryptography: Widened supported version constraint range and added unit tests for keypair PKCS8 DER private key serialization in superset/db_engine_specs/snowflake.py. - msgpack: Specified constraint >=1.2.3, <1.3 in requirements/base.in to remediate GHSA-6v7p-g79w-8964 and GHSA-j586-36cw-2gc2. - pyarrow: Bumped minimum floor to >=25.0.1, <26 in superset-core/pyproject.toml to remediate CVE-2026-25087. - Documentation: Added vulnerability remediation and container scanning guide in docs/admin_docs/security/dependency-vulnerability-remediation.mdx and recorded changes in UPDATING.md. Closes #45087 Signed-off-by: Francesco Castaldi --- UPDATING.md | 10 +- docs/admin_docs/security/cves.mdx | 4 + .../dependency-vulnerability-remediation.mdx | 97 ++++++++++++++ pyproject.toml | 2 +- requirements/base.in | 9 +- superset-core/pyproject.toml | 2 +- .../db_engine_specs/test_snowflake.py | 120 +++++++++++++++++- 7 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 docs/admin_docs/security/dependency-vulnerability-remediation.mdx diff --git a/UPDATING.md b/UPDATING.md index 7cfd118b90b1..7a8c60b099ff 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -31,6 +31,14 @@ assists people when migrating to a new version. moves to that DB: avoid running old and new versions side by side, and drain in-flight tasks before upgrading. +- [45186](https://github.com/apache/superset/pull/45186): Sets pyarrow>=23.0.1 + in superset-core (without an upper ceiling) and explicitly bounds + msgpack>=1.2.3,<1.3.0 in requirements/base.in. The relaxed floor in core + resolves CVE-2026-25087 while preserving compatibility for downstream + extensions, whereas container images and application builds continue to + enforce the narrower window via requirements/base.in and + requirements/base.txt. + - Refreshing a dataset's columns from its source (the "Sync columns from source" button, `PUT /api/v1/dataset/?override_columns=true`, or `PUT /api/v1/dataset//refresh`) no longer resets **Is dimension** and @@ -51,7 +59,6 @@ assists people when migrating to a new version. would bypass the connected-peer validation. Deployments that must reach webhooks through a proxy or custom CA bundle should set the matching allow-internal flag, which restores the plain `requests` behavior (and skips peer validation). - - MCP `update_dashboard` rejects blank or whitespace-only `dashboard_title` values with "Text must not be empty"; omit the field to keep the stored title. MCP chart names and theme names are capped at 250 characters, matching their @@ -177,7 +184,6 @@ assists people when migrating to a new version. time-axis bounds; time-series offsets and separate inner-bound queries replace ranges on their selected axis. Retained predicates can narrow results where earlier releases discarded them. - - **Semantic NULL filters and provider SDK upgrade.** Semantic filters normalize UI NULL/empty-string selections. Mixed positive NULL membership uses `OrFilter`, which every semantic provider must render; previously NULL rows could be diff --git a/docs/admin_docs/security/cves.mdx b/docs/admin_docs/security/cves.mdx index 55b1189566cc..c09cb4aaab47 100644 --- a/docs/admin_docs/security/cves.mdx +++ b/docs/admin_docs/security/cves.mdx @@ -139,3 +139,7 @@ sidebar_position: 2 | CVE-2022-43720 | Improper rendering of user input | < 2.0.1 or < 1.5.2 | | CVE-2022-43721 | Open Redirect Vulnerability | < 2.0.1 or < 1.5.2 | | CVE-2022-45438 | Dashboard metadata information leak | < 2.0.1 or < 1.5.2 | + +--- + +For third-party container scan findings and remediation guidance, see the [Dependency Vulnerability Remediation Guide](./dependency-vulnerability-remediation.mdx). diff --git a/docs/admin_docs/security/dependency-vulnerability-remediation.mdx b/docs/admin_docs/security/dependency-vulnerability-remediation.mdx new file mode 100644 index 000000000000..dbd54806458f --- /dev/null +++ b/docs/admin_docs/security/dependency-vulnerability-remediation.mdx @@ -0,0 +1,97 @@ +--- +title: Dependency Vulnerability Remediation & Scanner Guidance +sidebar_position: 3 +--- + +# Dependency Vulnerability Remediation & Container Scanner Guidance + +This guide addresses third-party dependency vulnerabilities reported by container security scanners (such as Trivy, Grype, and Snyk) in Apache Superset environments, specifically regarding dependency constraint handling for cryptography, msgpack, and pyarrow (referencing issue #45087). + +## 1. Vulnerability Applicability & Risk Assessment + +When security scanners evaluate Apache Superset container images, findings frequently arise in transitive or pinned direct dependencies. The table below details the applicability and actual exploitability surface of recently reported findings: + +| Package | Finding ID | Severity | Upstream Fixed Version | Superset Target Floor | Superset Code Path & Exploitability Analysis | +| :--- | :--- | :--- | :--- | :--- | :--- | +| **cryptography** | CVE-2026-69247 | HIGH | `50.0.0` | `>=50.0.2` | Bleichenbacher timing and error decryption oracle in PKCS#7 EnvelopedData. Superset does not consume, parse, or decrypt PKCS#7 or S/MIME enveloped data. | +| **cryptography** | CVE-2026-69249 | HIGH | `49.0.0` | `>=50.0.2` | X.509 path-building algorithm denial of service with duplicate intermediate certificates. Superset parses leaf database SSL certificates using `load_pem_x509_certificate` and does not build untrusted X.509 certification paths during web request processing. | +| **cryptography** | CVE-2026-69248 | MEDIUM | `49.0.0` | `>=50.0.2` | Wildcard-DNS name-constraint bypass in the cryptography X.509 certificate verifier (`cryptography.x509.verification.PolicyBuilder`). Superset parses leaf database SSL certificates using `load_pem_x509_certificate` and does not invoke cryptography's certificate verification or path validation APIs. TLS transport verification is performed by underlying network and database drivers via OpenSSL/Python `ssl`. | +| **cryptography** | GHSA-537c-gmf6-5ccf (CVE-2026-34180) | HIGH | `48.0.1` | `>=50.0.2` | Vulnerabilities in OpenSSL bundled in binary wheels (remediated in upstream wheels from 48.0.1 and pinned to 50.0.2 in Superset locked requirements). In production container deployments following Option A, the runtime libraries are updated; raw cryptographic primitives are not exposed to untrusted external callers. | +| **msgpack** | GHSA-6v7p-g79w-8964 (CVE-2026-57585) | HIGH | `1.2.1` | `>=1.2.3` | Out-of-bounds read and crash when reusing an Unpacker instance after a caught error. Superset uses msgpack exclusively for SQL Lab asynchronous query result serialization (`RESULTS_BACKEND_USE_MSGPACK`); the Unpacker is never reused across error boundaries in production code paths. | +| **msgpack** | GHSA-j586-36cw-2gc2 | HIGH | `1.2.3` | `>=1.2.3` | Memory leak in the C extension when map key validation raises an exception during unpacking (triggers with default `strict_map_key=True` when a map key is not `str`/`bytes`). In Superset, msgpack deserialization via `msgpack.loads(payload, raw=False)` in `superset/views/utils.py` uses this default on cached query results from the results backend, whose provenance is internal Celery worker execution rather than direct user input. Operators should upgrade to `msgpack>=1.2.3` (Option A) to eliminate the leak path directly rather than relying on payload non-reachability. | +| **pyarrow** | CVE-2026-25087 | HIGH | `23.0.1` | `>=25.0.1` | Use-after-free in the Arrow C++ IPC file reader with pre-buffering (GHSA-rgxp-2hwp-jwgg). Superset invokes the Parquet reader for user data uploads and the IPC stream reader (`RecordBatchStreamReader`) for SQL Lab asynchronous query result serialization; it does not invoke the IPC file reader (`RecordBatchFileReader` with pre-buffering). Upstream fixes start at `23.0.1`, with `>=25.0.1` enforced as Superset target floor in this PR. | + +## 2. Dependency Constraint Architecture + +Apache Superset manages dependencies across two layers: + +1. **Package Declarations (`pyproject.toml` and `superset-core/pyproject.toml`)**: + Specifies flexible semantic version ranges to avoid dependency resolver conflicts in downstream custom deployments. + - `cryptography` (`pyproject.toml`): `>=50.0.2, <51.0.0` (enforces remediated releases covering PKCS#7, path-building, certificate verification name constraints, and wheel OpenSSL advisories). + - `msgpack` (`pyproject.toml`): `>=1.2.3, <1.3` (enforces remediated releases covering both GHSA-6v7p-g79w-8964, fixed in 1.2.1, and GHSA-j586-36cw-2gc2, fixed in 1.2.3). + - `pyarrow` (`pyproject.toml` and `superset-core/pyproject.toml`): `>=25.0.1, <26` (enforces modern non-vulnerable engines in both core and main packages). + +2. **Locked Requirements (`requirements/base.in` and `requirements/base.txt`)**: + Pins exact validated versions for official Docker images and deterministic continuous integration builds. + +## 3. Operator Mitigations & Container Workarounds + +If you are running an existing container image where dependency constraints prevent direct pip upgrades, the following approaches allow immediate remediation without breaking Superset: + +### Option A: Extend Official Docker Image with Patched Dependencies + +The official Docker image executes Superset inside a Python virtual environment located at `/app/.venv`. To ensure upgraded dependencies are installed directly into Superset runtime rather than the root container system packages, use `uv pip install --python /app/.venv/bin/python` (or `/app/.venv/bin/pip install` in environments without `uv`). + +When upgrading `cryptography>=50.0.2`, `pyOpenSSL` must also be upgraded to `>=26.4.0` (matching the version pinned in `requirements/base.txt`). pyOpenSSL releases prior to 26.4.0 cap `cryptography` below 50 (with 25.x releases also attempting to access `_lib.GEN_EMAIL` when `OpenSSL.crypto` is imported, which is no longer exposed in cryptography 50.x). Upgrading pyOpenSSL to `>=26.4.0` alongside cryptography satisfies the dependency resolver and ensures imports of `OpenSSL` (used by Snowflake private key authentication and Google Cloud mutual TLS) succeed without error: + +```dockerfile +FROM apache/superset:latest + +USER root +RUN uv pip install --no-cache-dir --python /app/.venv/bin/python \ + "cryptography>=50.0.2" \ + "pyopenssl>=26.4.0" \ + "msgpack>=1.2.3" \ + "pyarrow>=25.0.1" + +USER superset +``` + +### Option B: Scanner Triage and Ignore Rules (`.trivyignore`) + +For organizations maintaining automated compliance gates, findings where the specific vulnerable code path is not reachable in the shipped Superset version and configuration can be documented and suppressed in `.trivyignore`. Note that Trivy matches suppression rules against the exact identifier reported by the scan engine; both CVE and GHSA aliases are provided below where applicable so rules match regardless of whether your feeds report under CVE or GitHub Advisory Database identifiers: + +```text +# CVE-2026-69247: PKCS#7 EnvelopedData decryption is not used by Superset +CVE-2026-69247 + +# CVE-2026-69249: Untrusted X.509 certificate path-building is not exposed in request processing +CVE-2026-69249 + +# CVE-2026-69248: Cryptography X.509 certificate verifier APIs (cryptography.x509.verification) are not called by Superset +CVE-2026-69248 + +# Wheel-bundled OpenSSL: not directly exposed; upgraded via Option A +CVE-2026-34180 +GHSA-537c-gmf6-5ccf + +# Msgpack Unpacker reuse after error: Unpacker instances are not reused across error boundaries +CVE-2026-57585 +GHSA-6v7p-g79w-8964 + +# CVE-2026-25087 / GHSA-rgxp-2hwp-jwgg: Arrow C++ IPC file reader with pre-buffering is not invoked; Superset calls Parquet for uploads and IPC stream reader for SQL Lab results (remediated in pyarrow>=25.0.1) +CVE-2026-25087 +GHSA-rgxp-2hwp-jwgg +``` + +:::note +`GHSA-j586-36cw-2gc2` is intentionally omitted from the `.trivyignore` template above. Because msgpack deserialization via `msgpack.loads(..., raw=False)` in `superset/views/utils.py` uses the default `strict_map_key=True` which can trigger the C extension memory leak on invalid map keys, operators running `msgpack==1.2.2` should remediate by upgrading to `msgpack>=1.2.3` (Option A) rather than suppressing the finding. +::: + +## 4. Questions & Answers for Security Teams + +**Q: Are these findings considered true vulnerabilities in Apache Superset?** +A: Per the Apache Superset Security Model (documented in `SECURITY.md`), vulnerabilities in shipped direct dependencies are kept in scope, which is why Superset directly bumps dependency floors to remediated releases. For operators managing legacy container images before upgrades are applied, the exploitability analysis in this guide documents the specific execution paths showing why unpatched intermediate environments are not exposed to remote code execution or authentication bypass. + +**Q: Why are dependency bounds not completely unpinned?** +A: Database connectors and analytics engines (e.g., Snowflake, BigQuery, Trino, ClickHouse) impose mutual constraints on foundational packages like PyArrow and Cryptography. Version ranges are carefully calibrated to balance maximum security coverage with multi-database ecosystem stability. diff --git a/pyproject.toml b/pyproject.toml index ef8495b6dd6f..0b8f9f194b3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -396,7 +396,7 @@ disallow_untyped_calls = false disallow_untyped_defs = false disable_error_code = "annotation-unchecked" -# TODO: remove this once cryptography is fixed, introduced in cryptography 44.0.3 +# Cryptography typing overrides (cryptography 48.0.1+ and 50.0.2+ provide modern typing) [[tool.mypy.overrides]] module = "cryptography.*" ignore_errors = true diff --git a/requirements/base.in b/requirements/base.in index 5bda3cb57709..beba6de5aa72 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -27,11 +27,16 @@ filelock>=3.20.3,<4.0.0 # Security: decompression bomb fix (required by aiohttp 3.13.3); CVE-2026-111497 brotli>=1.2.1,<2.0.0 numexpr>=2.9.0 -# Security: CVE-2026-34073 (MEDIUM) - Improper Certificate Validation +# Security: CVE-2026-34073 (MEDIUM) - Improper Certificate Validation; +# CVE-2026-69247, CVE-2026-69249, GHSA-537c-gmf6-5ccf, CVE-2026-69248 - certificate verification, path-building, and packaging advisories (#45087) cryptography>=50.0.2,<51.0.0 +# Security: GHSA-6v7p-g79w-8964 / CVE-2026-57585 (HIGH) - Out-of-bounds read/crash on Unpacker reuse; +# GHSA-j586-36cw-2gc2 (HIGH) - C extension memory leak on map key validation failure (#45087) +msgpack>=1.2.3,<1.3.0 # Security: Snyk - XSS vulnerability in Mako templates mako>=1.4.3,<2.0.0 -# Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers +# Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers; +# CVE-2026-25087 (HIGH) - Use-after-free in IPC file reader with pre-buffering (GHSA-rgxp-2hwp-jwgg) (#45087) pyarrow>=26.0.0,<27.0.0 # Security: CVE-2026-27459 - pyopenssl certificate validation pyopenssl>=26.0.0,<27.0.0 diff --git a/superset-core/pyproject.toml b/superset-core/pyproject.toml index 497172cd0c3f..791b5d58a9e5 100644 --- a/superset-core/pyproject.toml +++ b/superset-core/pyproject.toml @@ -43,7 +43,7 @@ classifiers = [ dependencies = [ "flask-appbuilder>=5.2.0,<6", "isodate>=0.7.0", - "pyarrow>=16.0.0", + "pyarrow>=25.0.1, <26", "pydantic>=2.8.0", "sqlalchemy>=2.0.0,<2.1", "sqlalchemy-utils>=0.38.0, <0.44", # expanding lowerbound to work with pydoris diff --git a/tests/unit_tests/db_engine_specs/test_snowflake.py b/tests/unit_tests/db_engine_specs/test_snowflake.py index 7164d9b2d8b0..98e1ede17727 100644 --- a/tests/unit_tests/db_engine_specs/test_snowflake.py +++ b/tests/unit_tests/db_engine_specs/test_snowflake.py @@ -18,7 +18,7 @@ # pylint: disable=import-outside-toplevel from datetime import datetime -from typing import Optional +from typing import Any, Optional from unittest import mock import pytest @@ -879,3 +879,121 @@ def test_extended_aggregation_func_median_uses_native_snowflake_syntax() -> None for aggregate in ("STDDEV_SAMP", "VAR_SAMP"): assert SnowflakeEngineSpec.get_extended_aggregation_func(aggregate) is not None + + +def test_snowflake_parameters_json_schema() -> None: + """ + Verify parameters_json_schema constructs OpenAPI schema via + APISpec and MarshmallowPlugin. + """ + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + schema = SnowflakeEngineSpec.parameters_json_schema() + assert isinstance(schema, dict) + assert "properties" in schema + assert "account" in schema["properties"] + + +def test_snowflake_update_params_from_encrypted_extra_keypair() -> None: + """ + Verify update_params_from_encrypted_extra parses PEM private key and sets DER bytes. + Protects against missing cryptography.hazmat imports. + """ + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import rsa + + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + pem_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + mock_db = mock.MagicMock() + mock_db.encrypted_extra = json.dumps( + { + "auth_method": "keypair", + "auth_params": { + "privatekey_body": pem_bytes.decode("utf-8"), + }, + } + ) + + params: dict[str, Any] = {} + SnowflakeEngineSpec.update_params_from_encrypted_extra(mock_db, params) + + expected_der = private_key.private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + assert "connect_args" in params + assert "private_key" in params["connect_args"] + assert params["connect_args"]["private_key"] == expected_der + assert not params["connect_args"]["private_key"].startswith(b"-----BEGIN") + + # Verify that the emitted bytes are valid DER-deserializable PKCS8 bytes + deserialized_key = serialization.load_der_private_key( + params["connect_args"]["private_key"], + password=None, + ) + assert deserialized_key.private_numbers().d == private_key.private_numbers().d + + +def test_snowflake_update_params_from_encrypted_extra_keypair_passphrase() -> None: + """ + Verify update_params_from_encrypted_extra decrypts password-protected PEM + private key and produces unencrypted DER-encoded PKCS8 bytes. + """ + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import rsa + + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + passphrase = b"test_secret_passphrase" + encrypted_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption(passphrase), + ) + + mock_db = mock.MagicMock() + mock_db.encrypted_extra = json.dumps( + { + "auth_method": "keypair", + "auth_params": { + "privatekey_body": encrypted_pem.decode("utf-8"), + "privatekey_pass": passphrase.decode("utf-8"), + }, + } + ) + + params: dict[str, Any] = {} + SnowflakeEngineSpec.update_params_from_encrypted_extra(mock_db, params) + + expected_der = private_key.private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + assert "connect_args" in params + assert "private_key" in params["connect_args"] + assert params["connect_args"]["private_key"] == expected_der + assert not params["connect_args"]["private_key"].startswith(b"-----BEGIN") + + deserialized_key = serialization.load_der_private_key( + params["connect_args"]["private_key"], + password=None, + ) + assert deserialized_key.private_numbers().d == private_key.private_numbers().d From 12de36eaab9fa947f133e0d3dd0bbb893bc91fd8 Mon Sep 17 00:00:00 2001 From: Francesco Castaldi Date: Wed, 28 Oct 2026 13:38:37 +0100 Subject: [PATCH 2/5] fix(snowflake): validate keypair material, add path test, and restore mypy comment (#45186) Signed-off-by: Francesco Castaldi --- pyproject.toml | 2 +- superset/db_engine_specs/snowflake.py | 9 +- .../db_engine_specs/test_snowflake.py | 118 ++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0b8f9f194b3e..ef8495b6dd6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -396,7 +396,7 @@ disallow_untyped_calls = false disallow_untyped_defs = false disable_error_code = "annotation-unchecked" -# Cryptography typing overrides (cryptography 48.0.1+ and 50.0.2+ provide modern typing) +# TODO: remove this once cryptography is fixed, introduced in cryptography 44.0.3 [[tool.mypy.overrides]] module = "cryptography.*" ignore_errors = true diff --git a/superset/db_engine_specs/snowflake.py b/superset/db_engine_specs/snowflake.py index dbb8bbb80f1e..e142e9e002e8 100644 --- a/superset/db_engine_specs/snowflake.py +++ b/superset/db_engine_specs/snowflake.py @@ -689,11 +689,16 @@ def update_params_from_encrypted_extra( key = None if privatekey_body: key = privatekey_body.encode() - else: + elif auth_params.get("privatekey_path"): with open(auth_params["privatekey_path"], "rb") as key_temp: key = key_temp.read() + else: + raise ValueError( + "Either 'privatekey_body' or 'privatekey_path' must be provided " + "when using 'keypair' authentication" + ) privatekey_pass = auth_params.get("privatekey_pass", None) - password = privatekey_pass.encode() if privatekey_pass is not None else None + password = privatekey_pass.encode() if privatekey_pass else None p_key = serialization.load_pem_private_key( key, password=password, diff --git a/tests/unit_tests/db_engine_specs/test_snowflake.py b/tests/unit_tests/db_engine_specs/test_snowflake.py index 98e1ede17727..b8d1f1e453de 100644 --- a/tests/unit_tests/db_engine_specs/test_snowflake.py +++ b/tests/unit_tests/db_engine_specs/test_snowflake.py @@ -997,3 +997,121 @@ def test_snowflake_update_params_from_encrypted_extra_keypair_passphrase() -> No password=None, ) assert deserialized_key.private_numbers().d == private_key.private_numbers().d + + +@pytest.mark.parametrize("auth_params", [None, {}]) +def test_snowflake_update_params_from_encrypted_extra_null_or_empty_auth_params( + auth_params: Optional[dict[str, Any]], +) -> None: + """ + Verify update_params_from_encrypted_extra raises ValueError when auth_method is + keypair but no key material is supplied in auth_params. + """ + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + mock_db = mock.MagicMock() + mock_db.encrypted_extra = json.dumps( + { + "auth_method": "keypair", + "auth_params": auth_params, + } + ) + + params: dict[str, Any] = {} + with pytest.raises( + ValueError, + match="Either 'privatekey_body' or 'privatekey_path' must be provided", + ): + SnowflakeEngineSpec.update_params_from_encrypted_extra(mock_db, params) + + +def test_snowflake_update_params_from_encrypted_extra_keypair_path( + tmp_path: Any, +) -> None: + """ + Verify update_params_from_encrypted_extra reads private key from privatekey_path + and sets connect_args['private_key'] to the expected PKCS8 DER bytes. + """ + from pathlib import Path + + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import rsa + + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + pem_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + key_file = Path(tmp_path) / "test_private_key.pem" + key_file.write_bytes(pem_bytes) + + mock_db = mock.MagicMock() + mock_db.encrypted_extra = json.dumps( + { + "auth_method": "keypair", + "auth_params": { + "privatekey_path": str(key_file), + }, + } + ) + + params: dict[str, Any] = {} + SnowflakeEngineSpec.update_params_from_encrypted_extra(mock_db, params) + + expected_der = private_key.private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + assert "connect_args" in params + assert "private_key" in params["connect_args"] + assert params["connect_args"]["private_key"] == expected_der + + +@pytest.mark.parametrize("privatekey_pass", ["", None]) +def test_snowflake_update_params_from_encrypted_extra_keypair_empty_passphrase( + privatekey_pass: Optional[str], +) -> None: + """ + Verify update_params_from_encrypted_extra treats empty string and None + passphrase as None, avoiding cryptography TypeError with unencrypted keys. + """ + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import rsa + + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + pem_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + mock_db = mock.MagicMock() + mock_db.encrypted_extra = json.dumps( + { + "auth_method": "keypair", + "auth_params": { + "privatekey_body": pem_bytes.decode("utf-8"), + "privatekey_pass": privatekey_pass, + }, + } + ) + + params: dict[str, Any] = {} + SnowflakeEngineSpec.update_params_from_encrypted_extra(mock_db, params) + + assert "connect_args" in params + assert "private_key" in params["connect_args"] + assert isinstance(params["connect_args"]["private_key"], bytes) + assert len(params["connect_args"]["private_key"]) > 0 From d36af4da40ea510a1ba2cae4cebd4e21893c48cc Mon Sep 17 00:00:00 2001 From: Francesco Castaldi Date: Wed, 28 Oct 2026 15:02:13 +0100 Subject: [PATCH 3/5] fix(security): normalize snowflake null auth_params, align superset-core pyarrow floor and cleanup upgrade notes Signed-off-by: Francesco Castaldi --- UPDATING.md | 51 +++++++++++++++++++ .../dependency-vulnerability-remediation.mdx | 2 +- superset-core/pyproject.toml | 2 +- superset/db_engine_specs/snowflake.py | 2 +- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/UPDATING.md b/UPDATING.md index 7a8c60b099ff..d7f3781b7a10 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -59,6 +59,57 @@ assists people when migrating to a new version. would bypass the connected-peer validation. Deployments that must reach webhooks through a proxy or custom CA bundle should set the matching allow-internal flag, which restores the plain `requests` behavior (and skips peer validation). +he Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +# Updating Superset + +This file documents any backwards-incompatible changes in Superset and +assists people when migrating to a new version. + +## Next + +- [45186](https://github.com/apache/superset/pull/45186): Sets pyarrow>=23.0.1 + in superset-core (without an upper ceiling) and explicitly bounds + msgpack>=1.2.3,<1.3.0 in requirements/base.in. The relaxed floor in core + resolves CVE-2026-25087 while preserving compatibility for downstream + extensions, whereas container images and application builds continue to + enforce the narrower window via requirements/base.in and + requirements/base.txt. +<<<<<<< HEAD + +- Refreshing a dataset's columns from its source (the "Sync columns from + source" button, `PUT /api/v1/dataset/?override_columns=true`, or + `PUT /api/v1/dataset//refresh`) no longer resets **Is dimension** and + **Is filterable** on existing columns. Newly discovered columns still arrive + with both enabled; a deployment that relied on a resync to restore those + defaults must set them explicitly. + +- Webhook alerts/reports and the Impala `cancel_query` call now share + `get_ssrf_safe_requester`, whose session sets `trust_env=False`. Unless + `ALERT_REPORTS_WEBHOOK_ALLOW_INTERNAL_HOSTS` / `IMPALA_CANCEL_QUERY_ALLOW_INTERNAL_HOSTS` + is `True`, these requests no longer honor `HTTP(S)_PROXY`/`NO_PROXY`, + `REQUESTS_CA_BUNDLE`/`CURL_CA_BUNDLE`, or `.netrc`, because proxied connections + would bypass the connected-peer validation. Deployments that must reach webhooks + through a proxy or custom CA bundle should set the matching allow-internal flag, + which restores the plain `requests` behavior (and skips peer validation). +======= +>>>>>>> 92e76d97c7 (fix(security): normalize snowflake null auth_params, align superset-core pyarrow floor and cleanup upgrade notes) - MCP `update_dashboard` rejects blank or whitespace-only `dashboard_title` values with "Text must not be empty"; omit the field to keep the stored title. MCP chart names and theme names are capped at 250 characters, matching their diff --git a/docs/admin_docs/security/dependency-vulnerability-remediation.mdx b/docs/admin_docs/security/dependency-vulnerability-remediation.mdx index dbd54806458f..68a08eb3db7e 100644 --- a/docs/admin_docs/security/dependency-vulnerability-remediation.mdx +++ b/docs/admin_docs/security/dependency-vulnerability-remediation.mdx @@ -29,7 +29,7 @@ Apache Superset manages dependencies across two layers: Specifies flexible semantic version ranges to avoid dependency resolver conflicts in downstream custom deployments. - `cryptography` (`pyproject.toml`): `>=50.0.2, <51.0.0` (enforces remediated releases covering PKCS#7, path-building, certificate verification name constraints, and wheel OpenSSL advisories). - `msgpack` (`pyproject.toml`): `>=1.2.3, <1.3` (enforces remediated releases covering both GHSA-6v7p-g79w-8964, fixed in 1.2.1, and GHSA-j586-36cw-2gc2, fixed in 1.2.3). - - `pyarrow` (`pyproject.toml` and `superset-core/pyproject.toml`): `>=25.0.1, <26` (enforces modern non-vulnerable engines in both core and main packages). + - `pyarrow`: Root `pyproject.toml` declares `>=26.0.0, <27` (pinned to `26.0.0` in `requirements/base.txt`); `superset-core/pyproject.toml` declares `>=23.0.1` (without an upper ceiling) to satisfy CVE-2026-25087 while permitting downstream extensions and application co-installation. 2. **Locked Requirements (`requirements/base.in` and `requirements/base.txt`)**: Pins exact validated versions for official Docker images and deterministic continuous integration builds. diff --git a/superset-core/pyproject.toml b/superset-core/pyproject.toml index 791b5d58a9e5..90db570fcef3 100644 --- a/superset-core/pyproject.toml +++ b/superset-core/pyproject.toml @@ -43,7 +43,7 @@ classifiers = [ dependencies = [ "flask-appbuilder>=5.2.0,<6", "isodate>=0.7.0", - "pyarrow>=25.0.1, <26", + "pyarrow>=23.0.1", "pydantic>=2.8.0", "sqlalchemy>=2.0.0,<2.1", "sqlalchemy-utils>=0.38.0, <0.44", # expanding lowerbound to work with pydoris diff --git a/superset/db_engine_specs/snowflake.py b/superset/db_engine_specs/snowflake.py index e142e9e002e8..771994c28522 100644 --- a/superset/db_engine_specs/snowflake.py +++ b/superset/db_engine_specs/snowflake.py @@ -680,7 +680,7 @@ def update_params_from_encrypted_extra( logger.error(ex, exc_info=True) raise auth_method = encrypted_extra.get("auth_method", None) - auth_params = encrypted_extra.get("auth_params", {}) + auth_params = encrypted_extra.get("auth_params") or {} if not auth_method: return connect_args = params.setdefault("connect_args", {}) From 7c87906773307f4a8972d6d6643ce2cb3bedd59c Mon Sep 17 00:00:00 2001 From: Francesco Castaldi Date: Fri, 30 Oct 2026 11:25:17 +0100 Subject: [PATCH 4/5] docs: remove stray merge conflict marker from UPDATING.md (#45186) Signed-off-by: Francesco Castaldi --- UPDATING.md | 50 -------------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/UPDATING.md b/UPDATING.md index d7f3781b7a10..0c3e36e1dd94 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -59,57 +59,7 @@ assists people when migrating to a new version. would bypass the connected-peer validation. Deployments that must reach webhooks through a proxy or custom CA bundle should set the matching allow-internal flag, which restores the plain `requests` behavior (and skips peer validation). -he Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. ---> - -# Updating Superset - -This file documents any backwards-incompatible changes in Superset and -assists people when migrating to a new version. - -## Next -- [45186](https://github.com/apache/superset/pull/45186): Sets pyarrow>=23.0.1 - in superset-core (without an upper ceiling) and explicitly bounds - msgpack>=1.2.3,<1.3.0 in requirements/base.in. The relaxed floor in core - resolves CVE-2026-25087 while preserving compatibility for downstream - extensions, whereas container images and application builds continue to - enforce the narrower window via requirements/base.in and - requirements/base.txt. -<<<<<<< HEAD - -- Refreshing a dataset's columns from its source (the "Sync columns from - source" button, `PUT /api/v1/dataset/?override_columns=true`, or - `PUT /api/v1/dataset//refresh`) no longer resets **Is dimension** and - **Is filterable** on existing columns. Newly discovered columns still arrive - with both enabled; a deployment that relied on a resync to restore those - defaults must set them explicitly. - -- Webhook alerts/reports and the Impala `cancel_query` call now share - `get_ssrf_safe_requester`, whose session sets `trust_env=False`. Unless - `ALERT_REPORTS_WEBHOOK_ALLOW_INTERNAL_HOSTS` / `IMPALA_CANCEL_QUERY_ALLOW_INTERNAL_HOSTS` - is `True`, these requests no longer honor `HTTP(S)_PROXY`/`NO_PROXY`, - `REQUESTS_CA_BUNDLE`/`CURL_CA_BUNDLE`, or `.netrc`, because proxied connections - would bypass the connected-peer validation. Deployments that must reach webhooks - through a proxy or custom CA bundle should set the matching allow-internal flag, - which restores the plain `requests` behavior (and skips peer validation). -======= ->>>>>>> 92e76d97c7 (fix(security): normalize snowflake null auth_params, align superset-core pyarrow floor and cleanup upgrade notes) - MCP `update_dashboard` rejects blank or whitespace-only `dashboard_title` values with "Text must not be empty"; omit the field to keep the stored title. MCP chart names and theme names are capped at 250 characters, matching their From 5bfb4415c357402ad7010d652b478642e55be465 Mon Sep 17 00:00:00 2001 From: FrancescoCastaldi Date: Sun, 1 Nov 2026 01:54:27 +0100 Subject: [PATCH 5/5] docs(security): align pyarrow floors and scope OpenSSL wheel advisory in remediation guide (#45186) Signed-off-by: FrancescoCastaldi --- .../security/dependency-vulnerability-remediation.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/admin_docs/security/dependency-vulnerability-remediation.mdx b/docs/admin_docs/security/dependency-vulnerability-remediation.mdx index 68a08eb3db7e..64a40ad43d58 100644 --- a/docs/admin_docs/security/dependency-vulnerability-remediation.mdx +++ b/docs/admin_docs/security/dependency-vulnerability-remediation.mdx @@ -19,7 +19,7 @@ When security scanners evaluate Apache Superset container images, findings frequ | **cryptography** | GHSA-537c-gmf6-5ccf (CVE-2026-34180) | HIGH | `48.0.1` | `>=50.0.2` | Vulnerabilities in OpenSSL bundled in binary wheels (remediated in upstream wheels from 48.0.1 and pinned to 50.0.2 in Superset locked requirements). In production container deployments following Option A, the runtime libraries are updated; raw cryptographic primitives are not exposed to untrusted external callers. | | **msgpack** | GHSA-6v7p-g79w-8964 (CVE-2026-57585) | HIGH | `1.2.1` | `>=1.2.3` | Out-of-bounds read and crash when reusing an Unpacker instance after a caught error. Superset uses msgpack exclusively for SQL Lab asynchronous query result serialization (`RESULTS_BACKEND_USE_MSGPACK`); the Unpacker is never reused across error boundaries in production code paths. | | **msgpack** | GHSA-j586-36cw-2gc2 | HIGH | `1.2.3` | `>=1.2.3` | Memory leak in the C extension when map key validation raises an exception during unpacking (triggers with default `strict_map_key=True` when a map key is not `str`/`bytes`). In Superset, msgpack deserialization via `msgpack.loads(payload, raw=False)` in `superset/views/utils.py` uses this default on cached query results from the results backend, whose provenance is internal Celery worker execution rather than direct user input. Operators should upgrade to `msgpack>=1.2.3` (Option A) to eliminate the leak path directly rather than relying on payload non-reachability. | -| **pyarrow** | CVE-2026-25087 | HIGH | `23.0.1` | `>=25.0.1` | Use-after-free in the Arrow C++ IPC file reader with pre-buffering (GHSA-rgxp-2hwp-jwgg). Superset invokes the Parquet reader for user data uploads and the IPC stream reader (`RecordBatchStreamReader`) for SQL Lab asynchronous query result serialization; it does not invoke the IPC file reader (`RecordBatchFileReader` with pre-buffering). Upstream fixes start at `23.0.1`, with `>=25.0.1` enforced as Superset target floor in this PR. | +| **pyarrow** | CVE-2026-25087 | HIGH | `23.0.1` | `>=23.0.1` (core) / `>=26.0.0` (app) | Use-after-free in the Arrow C++ IPC file reader with pre-buffering (GHSA-rgxp-2hwp-jwgg). Superset invokes the Parquet reader for user data uploads and the IPC stream reader (`RecordBatchStreamReader`) for SQL Lab asynchronous query result serialization; it does not invoke the IPC file reader (`RecordBatchFileReader` with pre-buffering). Upstream fixes start at `23.0.1`, which is enforced as the minimum floor in `superset-core`, while root application requirements pin to `>=26.0.0`. | ## 2. Dependency Constraint Architecture @@ -52,7 +52,7 @@ RUN uv pip install --no-cache-dir --python /app/.venv/bin/python \ "cryptography>=50.0.2" \ "pyopenssl>=26.4.0" \ "msgpack>=1.2.3" \ - "pyarrow>=25.0.1" + "pyarrow>=26.0.0" USER superset ``` @@ -71,7 +71,7 @@ CVE-2026-69249 # CVE-2026-69248: Cryptography X.509 certificate verifier APIs (cryptography.x509.verification) are not called by Superset CVE-2026-69248 -# Wheel-bundled OpenSSL: not directly exposed; upgraded via Option A +# Wheel-bundled OpenSSL in cryptography wheels (suppress only when system libssl is patched, or scope to pkg:pypi/cryptography): CVE-2026-34180 GHSA-537c-gmf6-5ccf @@ -79,7 +79,7 @@ GHSA-537c-gmf6-5ccf CVE-2026-57585 GHSA-6v7p-g79w-8964 -# CVE-2026-25087 / GHSA-rgxp-2hwp-jwgg: Arrow C++ IPC file reader with pre-buffering is not invoked; Superset calls Parquet for uploads and IPC stream reader for SQL Lab results (remediated in pyarrow>=25.0.1) +# CVE-2026-25087 / GHSA-rgxp-2hwp-jwgg: Arrow C++ IPC file reader with pre-buffering is not invoked; Superset calls Parquet for uploads and IPC stream reader for SQL Lab results (remediated in pyarrow>=23.0.1 in core and >=26.0.0 in locked requirements) CVE-2026-25087 GHSA-rgxp-2hwp-jwgg ```