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]

Reply via email to