← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~enriqueesanchz/launchpad:soss-import-parsing into launchpad:master

 

Enrique Sánchez has proposed merging ~enriqueesanchz/launchpad:soss-import-parsing into launchpad:master.

Commit message:
Add soss-cve-tracker import parsing layer
    
The parsing layer also validates the data

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~enriqueesanchz/launchpad/+git/launchpad/+merge/487018
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~enriqueesanchz/launchpad:soss-import-parsing into launchpad:master.
diff --git a/lib/lp/bugs/scripts/soss/__init__.py b/lib/lp/bugs/scripts/soss/__init__.py
new file mode 100644
index 0000000..13dc61a
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/__init__.py
@@ -0,0 +1,4 @@
+#  Copyright 2025 Canonical Ltd.  This software is licensed under the
+#  GNU Affero General Public License version 3 (see the file LICENSE).
+
+from lp.bugs.scripts.soss.sossrecord import SOSSRecord  # noqa: F401
diff --git a/lib/lp/bugs/scripts/soss/sossrecord.py b/lib/lp/bugs/scripts/soss/sossrecord.py
new file mode 100644
index 0000000..466606b
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/sossrecord.py
@@ -0,0 +1,139 @@
+#  Copyright 2025 Canonical Ltd.  This software is licensed under the
+#  GNU Affero General Public License version 3 (see the file LICENSE).
+import re
+from dataclasses import dataclass
+from datetime import datetime
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+import yaml
+
+# From `soss-cve-tracker/git-hooks/check-cve-syntax`
+VALID_CHANNEL_REGEX = re.compile(r"^(focal|jammy|noble):[^/]+/stable$")
+
+
+@dataclass
+class SOSSRecord:
+
+    class PriorityEnum(Enum):
+        NEEDS_TRIAGE = "Needs-triage"
+        NEGLIGIBLE = "Negligible"
+        LOW = "Low"
+        MEDIUM = "Medium"
+        HIGH = "High"
+        CRITICAL = "Critical"
+
+    class PackageStatusEnum(Enum):
+        IGNORED = "ignored"
+        NEEDS_TRIAGE = "needs-triage"
+        RELEASED = "released"
+        NOT_AFFECTED = "not-affected"
+        DEFERRED = "deferred"
+        NEEDED = "needed"
+
+    class PackageTypeEnum(Enum):
+        CONDA = "conda"
+        PYTHON = "python"
+        UNPACKAGED = "unpackaged"
+        MAVEN = "maven"
+        RUST = "rust"
+
+    @dataclass
+    class Channel:
+        value: str
+
+        def __post_init__(self):
+            if not VALID_CHANNEL_REGEX.match(self.value):
+                raise ValueError(f"Invalid channel format: {self.value}")
+
+    @dataclass
+    class CVSS:
+        source: str
+        vector: str
+        base_score: float
+        base_severity: float
+
+        BASE_SEVERITIES = {"LOW", "MEDIUM", "HIGH", "CRITICAL"}
+
+        def __post_init__(self):
+            if not (0.0 <= self.base_score <= 10.0):
+                raise ValueError(f"Invalid base score: {self.base_score}")
+            if self.base_severity not in self.BASE_SEVERITIES:
+                raise ValueError(
+                    f"Invalid base severity: {self.base_severity}"
+                )
+
+    @dataclass
+    class Package:
+        name: str
+        channel: "SOSSRecord.Channel"
+        repositories: List[str]
+        status: "SOSSRecord.PackageStatusEnum"
+        note: str
+
+    references: List[str]
+    notes: List[str]
+    priority: PriorityEnum
+    priority_reason: str
+    assigned_to: str
+    packages: Dict[PackageTypeEnum, List[Package]]
+    candidate: Optional[str] = None
+    description: Optional[str] = None
+    cvss: Optional[List[CVSS]] = None
+    public_date: Optional[datetime] = None
+
+    @classmethod
+    def from_yaml(cls, yaml_str: str) -> "SOSSRecord":
+        raw: Dict[str, Any] = yaml.safe_load(yaml_str)
+        return cls.from_dict(raw)
+
+    @classmethod
+    def from_dict(cls, raw: Dict[str, Any]) -> "SOSSRecord":
+        priority = SOSSRecord.PriorityEnum(raw["Priority"])
+
+        packages = {}
+        for enum_key, pkgs in raw.get("Packages", {}).items():
+            package_type = SOSSRecord.PackageTypeEnum(enum_key.lower())
+            package_list = [
+                SOSSRecord.Package(
+                    name=package["Name"],
+                    channel=SOSSRecord.Channel(package["Channel"]),
+                    repositories=package["Repositories"],
+                    status=SOSSRecord.PackageStatusEnum(
+                        package["Status"].lower()
+                    ),
+                    note=package["Note"],
+                )
+                for package in pkgs
+            ]
+            packages[package_type] = package_list
+
+        cvss_list = [
+            SOSSRecord.CVSS(
+                cvss["source"],
+                cvss["vector"],
+                cvss["baseScore"],
+                cvss["baseSeverity"],
+            )
+            for cvss in raw.get("CVSS", [])
+        ]
+
+        public_date_str = raw.get("PublicDate")
+        public_date = (
+            datetime.fromisoformat(public_date_str)
+            if public_date_str
+            else None
+        )
+
+        return cls(
+            references=raw.get("References", []),
+            notes=raw.get("Notes", []),
+            priority=priority,
+            priority_reason=raw.get("Priority-Reason", ""),
+            assigned_to=raw.get("Assigned-To", ""),
+            packages=packages,
+            candidate=raw.get("Candidate"),
+            description=raw.get("Description"),
+            cvss=cvss_list,
+            public_date=public_date,
+        )
diff --git a/lib/lp/bugs/scripts/soss/tests/__init__.py b/lib/lp/bugs/scripts/soss/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/tests/__init__.py
diff --git a/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2025-1979-full b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2025-1979-full
new file mode 100644
index 0000000..c8d8b2c
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2025-1979-full
@@ -0,0 +1,78 @@
+References:
+- https://github.com/ray-project/ray/commit/64a2e4010522d60b90c389634f24df77b603d85d
+- https://github.com/ray-project/ray/issues/50266
+- https://github.com/ray-project/ray/pull/50409
+- https://security.snyk.io/vuln/SNYK-PYTHON-RAY-8745212
+- https://ubuntu.com/security/notices/SSN-148-1.json?show_hidden=true
+Notes:
+- This is a sample soss cve with all the fields filled for testing
+- sample note 2
+Priority: Low
+Priority-Reason: 'Unrealistic exploitation scenario. Logs are stored locally and not
+  transferred between agents, so local log access is the only conceivable method to
+  view the password for the redis instance (i.e., no possibility of MitM to access
+  the logs). Given the requirement for priviledged system access to access log files
+  the real "danger" posed by the vulnerability is quite low, and that is reflected
+  in this priority assignment. '
+Assigned-To: octagalland
+Packages:
+  unpackaged:
+  - Name: vllm
+    Channel: noble:0.7.3/stable
+    Repositories:
+    - soss-src-stable-local
+    Status: needed
+    Note: ''
+  python:
+  - Name: ray
+    Channel: jammy:2.22.0/stable
+    Repositories:
+    - nvidia-pb3-python-stable-local
+    Status: released
+    Note: 2.22.0+soss.1
+  - Name: pyyaml
+    Channel: jammy:2.22.0/stable
+    Repositories:
+    - nvidia-pb3-python-stable-local
+    Status: not-affected
+    Note: ''
+  maven:
+  - Name: vllm
+    Channel: noble:0.7.3/stable
+    Repositories:
+    - soss-src-stable-local
+    Status: needs-triage
+    Note: ''
+  conda:
+  - Name: ray
+    Channel: jammy:1.17.0/stable
+    Repositories:
+    - nvidia-pb3-python-stable-local
+    Status: not-affected
+    Note: 2.22.0+soss.1
+  rust:
+  - Name: ray
+    Channel: focal:0.27.0/stable
+    Repositories:
+    - nvidia-pb3-python-stable-local
+    Status: deferred
+    Note: 2.22.0+soss.1
+Candidate: CVE-2025-1979
+Description: "Versions of the package ray before 2.43.0 are vulnerable to Insertion\
+  \ of Sensitive Information into Log File where the redis password is being logged\
+  \ in the standard logging. If the redis password is passed as an argument, it will\
+  \ be logged and could potentially leak the password.\r\rThis is only exploitable\
+  \ if:\r\r1) Logging is enabled;\r\r2) Redis is using password authentication;\r\r\
+  3) Those logs are accessible to an attacker, who can reach that redis instance.\r\
+  \r**Note:**\r\rIt is recommended that anyone who is running in this configuration\
+  \ should update to the latest version of Ray, then rotate their redis password."
+CVSS:
+- source: report@xxxxxxx
+  vector: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
+  baseScore: 6.4
+  baseSeverity: MEDIUM
+- source: security-advisories@xxxxxxxxxx
+  vector: CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
+  baseScore: 9.0
+  baseSeverity: CRITICAL
+PublicDate: '2025-03-06T05:15:16.213'
diff --git a/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py b/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py
new file mode 100644
index 0000000..a26cad7
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py
@@ -0,0 +1,275 @@
+#  Copyright 2025 Canonical Ltd.  This software is licensed under the
+#  GNU Affero General Public License version 3 (see the file LICENSE).
+from datetime import datetime
+from pathlib import Path
+
+from lp.bugs.scripts.soss import SOSSRecord
+from lp.testing import TestCase
+
+
+class TestSOSSRecord(TestCase):
+    maxDiff = None
+
+    def setUp(self, *args, **kwargs):
+        super().setUp(*args, **kwargs)
+
+        # This is a synthetic SOSSRecord using all the features
+        self.soss_record = SOSSRecord(
+            references=[
+                "https://github.com/ray-project/ray/commit/";
+                "64a2e4010522d60b90c389634f24df77b603d85d",
+                "https://github.com/ray-project/ray/issues/50266";,
+                "https://github.com/ray-project/ray/pull/50409";,
+                "https://security.snyk.io/vuln/SNYK-PYTHON-RAY-8745212";,
+                "https://ubuntu.com/security/notices/SSN-148-1.json?";
+                "show_hidden=true",
+            ],
+            notes=[
+                "This is a sample soss cve with all the fields filled for "
+                "testing",
+                "sample note 2",
+            ],
+            priority=SOSSRecord.PriorityEnum.LOW,
+            priority_reason=(
+                "Unrealistic exploitation scenario. Logs are stored locally "
+                "and not transferred between agents, so local log access is "
+                "the only conceivable method to view the password for the "
+                "redis instance (i.e., no possibility of MitM to access the "
+                "logs). Given the requirement for priviledged system access "
+                'to access log files the real "danger" posed by the '
+                "vulnerability is quite low, and that is reflected in this "
+                "priority assignment. "
+            ),
+            assigned_to="octagalland",
+            packages={
+                SOSSRecord.PackageTypeEnum.UNPACKAGED: [
+                    SOSSRecord.Package(
+                        name="vllm",
+                        channel=SOSSRecord.Channel("noble:0.7.3/stable"),
+                        repositories=["soss-src-stable-local"],
+                        status=SOSSRecord.PackageStatusEnum.NEEDED,
+                        note="",
+                    )
+                ],
+                SOSSRecord.PackageTypeEnum.PYTHON: [
+                    SOSSRecord.Package(
+                        name="ray",
+                        channel=SOSSRecord.Channel("jammy:2.22.0/stable"),
+                        repositories=["nvidia-pb3-python-stable-local"],
+                        status=SOSSRecord.PackageStatusEnum.RELEASED,
+                        note="2.22.0+soss.1",
+                    ),
+                    SOSSRecord.Package(
+                        name="pyyaml",
+                        channel=SOSSRecord.Channel("jammy:2.22.0/stable"),
+                        repositories=["nvidia-pb3-python-stable-local"],
+                        status=SOSSRecord.PackageStatusEnum.NOT_AFFECTED,
+                        note="",
+                    ),
+                ],
+                SOSSRecord.PackageTypeEnum.MAVEN: [
+                    SOSSRecord.Package(
+                        name="vllm",
+                        channel=SOSSRecord.Channel("noble:0.7.3/stable"),
+                        repositories=["soss-src-stable-local"],
+                        status=SOSSRecord.PackageStatusEnum.NEEDS_TRIAGE,
+                        note="",
+                    )
+                ],
+                SOSSRecord.PackageTypeEnum.CONDA: [
+                    SOSSRecord.Package(
+                        name="ray",
+                        channel=SOSSRecord.Channel("jammy:1.17.0/stable"),
+                        repositories=["nvidia-pb3-python-stable-local"],
+                        status=SOSSRecord.PackageStatusEnum.NOT_AFFECTED,
+                        note="2.22.0+soss.1",
+                    )
+                ],
+                SOSSRecord.PackageTypeEnum.RUST: [
+                    SOSSRecord.Package(
+                        name="ray",
+                        channel=SOSSRecord.Channel("focal:0.27.0/stable"),
+                        repositories=["nvidia-pb3-python-stable-local"],
+                        status=SOSSRecord.PackageStatusEnum.DEFERRED,
+                        note="2.22.0+soss.1",
+                    )
+                ],
+            },
+            candidate="CVE-2025-1979",
+            description=(
+                "Versions of the package ray before 2.43.0 are vulnerable to "
+                "Insertion of Sensitive Information into Log File where the "
+                "redis password is being logged in the standard logging. If "
+                "the redis password is passed as an argument, it will be "
+                "logged and could potentially leak the password.\r\rThis is "
+                "only exploitable if:\r\r1) Logging is enabled;\r\r2) Redis "
+                "is using password authentication;\r\r3) Those logs are "
+                "accessible to an attacker, who can reach that redis instance."
+                "\r\r**Note:**\r\rIt is recommended that anyone who is "
+                "running in this configuration should update to the latest "
+                "version of Ray, then rotate their redis password."
+            ),
+            cvss=[
+                SOSSRecord.CVSS(
+                    source="report@xxxxxxx",
+                    vector=("CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N"),
+                    base_score=6.4,
+                    base_severity="MEDIUM",
+                ),
+                SOSSRecord.CVSS(
+                    source="security-advisories@xxxxxxxxxx",
+                    vector=("CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"),
+                    base_score=9.0,
+                    base_severity="CRITICAL",
+                ),
+            ],
+            public_date=datetime.fromisoformat("2025-03-06T05:15:16.213"),
+        )
+
+        self.soss_record_dict = {
+            "References": [
+                "https://github.com/ray-project/ray/commit/";
+                "64a2e4010522d60b90c389634f24df77b603d85d",
+                "https://github.com/ray-project/ray/issues/50266";,
+                "https://github.com/ray-project/ray/pull/50409";,
+                "https://security.snyk.io/vuln/SNYK-PYTHON-RAY-8745212";,
+                "https://ubuntu.com/security/notices/SSN-148-1.json?";
+                "show_hidden=true",
+            ],
+            "Notes": [
+                "This is a sample soss cve with all the fields filled for "
+                "testing",
+                "sample note 2",
+            ],
+            "Priority": "Low",
+            "Priority-Reason": (
+                "Unrealistic exploitation scenario. Logs are stored locally "
+                "and not transferred between agents, so local log access is "
+                "the only conceivable method to view the password for the "
+                "redis instance (i.e., no possibility of MitM to access the "
+                "logs). Given the requirement for priviledged system access "
+                'to access log files the real "danger" posed by the '
+                "vulnerability is quite low, and that is reflected in this "
+                "priority assignment. "
+            ),
+            "Assigned-To": "octagalland",
+            "Packages": {
+                "unpackaged": [
+                    {
+                        "Name": "vllm",
+                        "Channel": "noble:0.7.3/stable",
+                        "Repositories": ["soss-src-stable-local"],
+                        "Status": "needed",
+                        "Note": "",
+                    }
+                ],
+                "python": [
+                    {
+                        "Name": "ray",
+                        "Channel": "jammy:2.22.0/stable",
+                        "Repositories": ["nvidia-pb3-python-stable-local"],
+                        "Status": "released",
+                        "Note": "2.22.0+soss.1",
+                    },
+                    {
+                        "Name": "pyyaml",
+                        "Channel": "jammy:2.22.0/stable",
+                        "Repositories": ["nvidia-pb3-python-stable-local"],
+                        "Status": "not-affected",
+                        "Note": "",
+                    },
+                ],
+                "maven": [
+                    {
+                        "Name": "vllm",
+                        "Channel": "noble:0.7.3/stable",
+                        "Repositories": ["soss-src-stable-local"],
+                        "Status": "needs-triage",
+                        "Note": "",
+                    }
+                ],
+                "conda": [
+                    {
+                        "Name": "ray",
+                        "Channel": "jammy:1.17.0/stable",
+                        "Repositories": ["nvidia-pb3-python-stable-local"],
+                        "Status": "not-affected",
+                        "Note": "2.22.0+soss.1",
+                    }
+                ],
+                "rust": [
+                    {
+                        "Name": "ray",
+                        "Channel": "focal:0.27.0/stable",
+                        "Repositories": ["nvidia-pb3-python-stable-local"],
+                        "Status": "deferred",
+                        "Note": "2.22.0+soss.1",
+                    }
+                ],
+            },
+            "Candidate": "CVE-2025-1979",
+            "Description": (
+                "Versions of the package ray before 2.43.0 are vulnerable to "
+                "Insertion of Sensitive Information into Log File where the "
+                "redis password is being logged in the standard logging. If "
+                "the redis password is passed as an argument, it will be "
+                "logged and could potentially leak the password.\r\rThis is "
+                "only exploitable if:\r\r1) Logging is enabled;\r\r2) Redis "
+                "is using password authentication;\r\r3) Those logs are "
+                "accessible to an attacker, who can reach that redis instance."
+                "\r\r**Note:**\r\rIt is recommended that anyone who is "
+                "running in this configuration should update to the latest "
+                "version of Ray, then rotate their redis password."
+            ),
+            "CVSS": [
+                {
+                    "source": "report@xxxxxxx",
+                    "vector": ("CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N"),
+                    "baseScore": 6.4,
+                    "baseSeverity": "MEDIUM",
+                },
+                {
+                    "source": "security-advisories@xxxxxxxxxx",
+                    "vector": ("CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"),
+                    "baseScore": 9.0,
+                    "baseSeverity": "CRITICAL",
+                },
+            ],
+            "PublicDate": "2025-03-06T05:15:16.213",
+        }
+
+    def test_from_dict(self):
+        soss_record = SOSSRecord.from_dict(self.soss_record_dict)
+        self.assertEqual(self.soss_record, soss_record)
+
+    def test_from_dict_bad_cvss(self):
+        self.soss_record_dict["CVSS"][0]["baseScore"] = -1.5
+        self.assertRaises(
+            ValueError, SOSSRecord.from_dict, self.soss_record_dict
+        )
+
+        self.soss_record_dict["CVSS"][0]["baseScore"] = 10.1
+        self.assertRaises(
+            ValueError, SOSSRecord.from_dict, self.soss_record_dict
+        )
+
+        self.soss_record_dict["CVSS"][0]["baseScore"] = 5
+        self.soss_record_dict["CVSS"][0]["baseSeverity"] = "foo"
+        self.assertRaises(
+            ValueError, SOSSRecord.from_dict, self.soss_record_dict
+        )
+
+    def test_from_dict_bad_channel(self):
+        self.soss_record_dict["Packages"]["unpackaged"][0]["Channel"] = "bar"
+        self.assertRaises(
+            ValueError, SOSSRecord.from_dict, self.soss_record_dict
+        )
+
+    def test_from_yaml(self):
+        load_from = Path(__file__).parent / "sampledata" / "CVE-2025-1979-full"
+
+        soss_record = None
+        with open(load_from) as f:
+            soss_record = SOSSRecord.from_yaml(f)
+
+        self.assertEqual(self.soss_record, soss_record)

Follow ups