sadpandajoe commented on code in PR #45186:
URL: https://github.com/apache/superset/pull/45186#discussion_r4241277278
##########
superset/utils/core.py:
##########
@@ -1658,7 +1658,11 @@ def parse_ssl_cert(certificate: str) -> Certificate:
:raises CertificateException: If certificate is not valid/unparseable
"""
try:
- return load_pem_x509_certificate(certificate.encode("utf-8"),
default_backend())
+ data = certificate.encode("utf-8")
+ try:
+ return load_pem_x509_certificate(data)
Review Comment:
The new `TypeError` retry with `default_backend()` isn't exercised by any
test in this PR (the added cryptography/Snowflake tests never call
`parse_ssl_cert`), so breaking or removing the fallback would still pass CI.
Could a unit test patch `load_pem_x509_certificate` to raise `TypeError` on the
one-argument call, assert the retry returns the certificate, and assert a
`ValueError` from the retry still becomes `CertificateException`?
##########
scripts/check_security_dependencies.py:
##########
@@ -0,0 +1,208 @@
+#!/usr/bin/env python3
+# Licensed to the 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.
+"""
+CI/CD verification script: audit security dependency constraints.
+Ensures that pyproject.toml, superset-core/pyproject.toml, and
requirements/base.in
+satisfy CVE remediation floors and reject known vulnerable versions (issue
#45087).
+"""
+
+import sys
+from pathlib import Path
+
+from packaging.requirements import Requirement
+from packaging.version import Version
+
+ROOT_DIR = Path(__file__).resolve().parents[1]
+
+# Known vulnerable versions from Trivy/Snyk scans that must NOT be accepted
+VULNERABLE_TEST_MATRIX = {
+ "cryptography": [
+ ("46.0.7", "CVE-2026-69247 / CVE-2026-69249 / GHSA-537c-gmf6-5ccf"),
+ ("47.0.0", "CVE-2026-69247 / CVE-2026-69249"),
+ ("48.0.0", "CVE-2026-69247 / GHSA-537c-gmf6-5ccf"),
+ ],
+ "msgpack": [
+ ("1.0.8", "GHSA-6v7p-g79w-8964"),
+ ("1.1.0", "GHSA-6v7p-g79w-8964"),
+ ("1.2.0", "GHSA-6v7p-g79w-8964"),
+ ],
+ "pyarrow": [
+ ("16.1.0", "CVE-2026-25087 / CVE-2024-52338"),
+ ("18.0.0", "CVE-2026-25087"),
+ ("22.0.0", "CVE-2026-25087"),
+ ],
+}
+
+# Remediation versions that MUST be accepted
+REMEDIATED_TEST_MATRIX = {
+ "cryptography": ["48.0.1", "49.0.0", "50.0.0", "50.0.2"],
+ "msgpack": ["1.2.1", "1.2.3"],
+ "pyarrow": ["25.0.1"],
+}
+
+
+def parse_pyproject_deps(path: Path) -> dict[str, Requirement]:
+ """Extract dependencies from pyproject.toml."""
+ content = path.read_text(encoding="utf-8")
+ reqs: dict[str, Requirement] = {}
+ in_deps = False
+
+ for line in content.splitlines():
+ stripped = line.strip()
+ if stripped == "dependencies = [":
+ in_deps = True
+ continue
+ if in_deps:
+ if stripped == "]":
+ break
+ if "#" in stripped:
+ stripped = stripped.split("#", 1)[0].strip()
+ stripped = stripped.rstrip(",")
+ if (stripped.startswith('"') and stripped.endswith('"')) or (
+ stripped.startswith("'") and stripped.endswith("'")
+ ):
+ stripped = stripped[1:-1].strip()
+ if stripped:
+ req = Requirement(stripped)
+ reqs[req.name.lower()] = req
+
+ return reqs
+
+
+def _audit_root_package(
+ root_deps: dict[str, Requirement],
+ errors: list[str],
+) -> None:
+ """Audit root pyproject.toml dependencies against vulnerable/remediated
matrix."""
+ for pkg_name, vulnerable_cases in VULNERABLE_TEST_MATRIX.items():
+ if pkg_name not in root_deps:
+ errors.append(f"Missing '{pkg_name}' in root pyproject.toml")
+ continue
+
+ spec = root_deps[pkg_name].specifier
+ print(f" - {pkg_name}: declared constraint '{spec}'")
+
+ for ver_str, cve_info in vulnerable_cases:
+ v = Version(ver_str)
+ if v in spec:
+ msg = (
+ f"Root permits vulnerable {pkg_name}=={ver_str} "
+ f"({cve_info}) under '{spec}'"
+ )
+ errors.append(msg)
+ else:
+ print(f" [OK] Rejects vulnerable {pkg_name} {ver_str}")
+
+ for ver_str in REMEDIATED_TEST_MATRIX[pkg_name]:
+ v = Version(ver_str)
+ if v not in spec:
+ msg = (
+ f"Root erroneously blocks remediated {pkg_name}=={ver_str}
"
+ f"under '{spec}'"
+ )
+ errors.append(msg)
+ else:
+ print(f" [OK] Accepts remediated {pkg_name} {ver_str}")
+
+
+def _audit_core_subpackage(
+ core_deps: dict[str, Requirement],
+ errors: list[str],
+) -> None:
+ """Audit superset-core/pyproject.toml pyarrow dependency."""
+ if "pyarrow" not in core_deps:
+ errors.append("Missing 'pyarrow' in superset-core/pyproject.toml")
+ return
+
+ core_spec = core_deps["pyarrow"].specifier
+ print(f" - pyarrow: declared constraint '{core_spec}'")
+ for ver_str, cve_info in VULNERABLE_TEST_MATRIX["pyarrow"]:
+ v = Version(ver_str)
+ if v in core_spec:
+ msg = (
+ f"superset-core permits vulnerable pyarrow=={ver_str} "
+ f"({cve_info}) under '{core_spec}'"
+ )
+ errors.append(msg)
+ else:
+ print(f" [OK] Rejects vulnerable pyarrow {ver_str}
({cve_info})")
+
+
+def _audit_base_requirements(
+ base_in_content: str,
+ base_in_path: Path,
+ errors: list[str],
+) -> None:
+ """Audit requirements/base.in for required annotations and floors."""
+ required_markers = [
+ ("msgpack", "msgpack declaration"),
+ ("GHSA-6v7p-g79w-8964", "msgpack CVE annotation"),
+ ("CVE-2026-69247", "cryptography CVE annotation"),
+ ("CVE-2026-25087", "pyarrow CVE annotation"),
+ ]
+
+ for marker, description in required_markers:
Review Comment:
This audit only checks that advisory strings and `msgpack` appear somewhere
in `base.in`, comments included. It would still print `AUDIT PASSED` with
`msgpack==1.0.8`, `cryptography==46.0.7` or `pyarrow==16.1.0` as long as those
comments remain, so it doesn't verify the floors it claims to. The same
substring check is mirrored in `test_dependency_security_constraints.py`. Could
this parse the active requirement lines and test their specifiers against the
vulnerable/remediated matrices, as the pyproject checks above do?
##########
tests/unit_tests/security/test_dependency_conflicts_reproduction.py:
##########
@@ -0,0 +1,85 @@
+# Licensed to the 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.
+"""
+Regression test for issue #45087 reproduction:
+Verifies that container security scanner recommendations (cryptography 50.0.2,
+msgpack 1.2.3, pyarrow 25.0.1) can be satisfied simultaneously by Superset
+without dependency resolver conflicts.
+"""
+
+from pathlib import Path
+
+from packaging.requirements import Requirement
+from packaging.version import Version
+
+ROOT_DIR = Path(__file__).resolve().parents[3]
+
+
+def test_trivy_remediation_versions_resolve_without_conflict() -> None:
+ """
+ Simulate pip dependency resolution for the scanner-reported remediation
versions.
+ Validates that:
+ 1. cryptography 50.0.2 satisfies root constraints.
+ 2. msgpack 1.2.3 satisfies root constraints.
+ 3. pyarrow 25.0.1 satisfies both root and superset-core constraints.
+ """
+ root_pyproject = (ROOT_DIR / "pyproject.toml").read_text(encoding="utf-8")
+ core_pyproject_path = ROOT_DIR / "superset-core" / "pyproject.toml"
+ core_pyproject = core_pyproject_path.read_text(encoding="utf-8")
+
+ def get_req(content: str, name: str) -> Requirement:
+ for line in content.splitlines():
+ stripped = line.strip()
+ if "#" in stripped:
+ stripped = stripped.split("#", 1)[0].strip()
+ stripped = stripped.rstrip(",")
+ if (stripped.startswith('"') and stripped.endswith('"')) or (
+ stripped.startswith("'") and stripped.endswith("'")
+ ):
+ stripped = stripped[1:-1].strip()
+ if stripped.lower().startswith(name.lower()):
+ return Requirement(stripped)
+ raise ValueError(f"Requirement '{name}' not found")
+
+ root_crypto = get_req(root_pyproject, "cryptography")
+ root_msgpack = get_req(root_pyproject, "msgpack")
+ root_pyarrow = get_req(root_pyproject, "pyarrow")
+ core_pyarrow = get_req(core_pyproject, "pyarrow")
+
+ # Target versions recommended by Trivy scanner
+ trivy_crypto_version = Version("50.0.2")
+ trivy_msgpack_version = Version("1.2.3")
+ trivy_pyarrow_version = Version("25.0.1")
+
+ # Assert all target versions satisfy the respective specifiers
+ assert trivy_crypto_version in root_crypto.specifier, (
Review Comment:
These assertions (cryptography 50.0.2, msgpack 1.2.3, pyarrow 25.0.1)
already hold against the old specifiers on master, so the test stays green with
every dependency change reverted and doesn't reproduce or guard the conflict
this PR describes. If it is meant to guard the range change, which version does
it assert that the old ranges rejected?
##########
pyproject.toml:
##########
@@ -82,7 +82,7 @@ dependencies = [
# https://github.com/apache/superset/issues/33162
"marshmallow>=3.0, <5",
"marshmallow-union>=0.1.15.post1",
- "msgpack>=1.2.3, <1.3",
+ "msgpack>=1.2.1, <1.3",
Review Comment:
Lowering the msgpack floor to 1.2.1 also admits 1.2.2, which is affected by
GHSA-j586-36cw-2gc2 (unbounded memory leak on strict_map_key validation
failure, fixed in 1.2.3). A package install can now resolve to 1.2.2 even
though master required >=1.2.3, and the new checker/test matrices don't list
1.2.2 as vulnerable. Should the floor stay at 1.2.3, or exclude 1.2.2?
##########
docs/admin_docs/security/dependency-vulnerability-remediation.mdx:
##########
@@ -0,0 +1,77 @@
+---
+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 | Remediation Floor | Superset Code Path &
Exploitability Analysis |
+| :--- | :--- | :--- | :--- | :--- |
+| **cryptography** | CVE-2026-69247, CVE-2026-69249, GHSA-537c-gmf6-5ccf,
CVE-2026-69248 | HIGH / MEDIUM | `>=48.0.1` (resolved in `50.0.2`) | Used
primarily for encrypting database connection passwords at rest and parsing
operator-supplied SSL certificates. Unauthenticated external input does not
traverse PKCS12 parsing routines. Exploitability in standard deployments is
considered low. |
+| **msgpack** | GHSA-6v7p-g79w-8964 | HIGH | `>=1.2.1` (resolved in `1.2.3`) |
Utilized internally for SQL Lab asynchronous query result serialization
(`RESULTS_BACKEND_USE_MSGPACK`). Raw msgpack byte streams are generated and
consumed only within the trusted backend cache boundary. Exploitability
requires full cache store compromise. |
+| **pyarrow** | CVE-2026-25087 | HIGH | `>=23.0.1` (resolved in `25.0.1`) |
Used for columnar tabular transformations and CSV/Parquet ingestion by
authenticated users. Ingestion pathways are governed by role-based permissions
and file size limits. Exploitability is bounded to authorized data uploads. |
+
+## 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`: `>=48.0.1, <51.0.0` (accepts all remediated releases from
48.0.1 through 50.x while barring vulnerable <48.0.1 versions).
+ - `msgpack`: `>=1.2.1, <1.3` (accepts all remediated releases from 1.2.1
through 1.2.x while barring vulnerable <1.2.1 versions).
+ - `pyarrow`: `>=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
+
+Create a customized Dockerfile that layers the fixed libraries on top of the
base image:
+
+```dockerfile
+FROM apache/superset:latest
+
+USER root
+RUN pip install --no-cache-dir \
Review Comment:
The official image installs Superset into an unseeded `uv venv /app/.venv`,
so a bare `pip install` here can land in the system site-packages and leave the
libraries Superset actually imports unchanged. An operator copying this
Dockerfile would think they remediated while the scanner and runtime still see
the old versions. Should this use `uv pip install --python
/app/.venv/bin/python ...` instead?
##########
pyproject.toml:
##########
@@ -50,7 +50,7 @@ dependencies = [
"flask-cors>=6.0.5, <7.0",
"croniter>=6.2.4",
"cron-descriptor",
- "cryptography>=50.0.2, <51.0.0",
+ "cryptography>=48.0.1, <51.0.0",
Review Comment:
Agreed—50.0.0 is the first release that fixes CVE-2026-69247
(GHSA-g6cj-pr64-35w5), so `>=48.0.1` still admits affected 49.x for anyone
installing from the package metadata, and the new checker and tests treat
49.0.0 as remediated. `requirements/base.in` doesn't protect those installs.
Should the floor stay at 50.0.2, or at least 50.0.0?
--
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]