diff --git a/application/tests/fixtures/owasp_mappings/owasp_kubernetes_top10_2025.json b/application/tests/fixtures/owasp_mappings/owasp_kubernetes_top10_2025.json index c55afb059..d6546e26f 100644 --- a/application/tests/fixtures/owasp_mappings/owasp_kubernetes_top10_2025.json +++ b/application/tests/fixtures/owasp_mappings/owasp_kubernetes_top10_2025.json @@ -2,70 +2,68 @@ { "section_id": "K01", "section": "Insecure Workload Configurations", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", - "cre_ids": ["233-748", "486-813"], - "fallback_section_ids": ["K01"] + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K01-Insecure-Workload-Configurations.html", + "cre_ids": ["233-748", "486-813"] }, { "section_id": "K02", "section": "Overly Permissive Authorization Configurations", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K02-Overly-Permissive-Authorization-Configurations.html", "cre_ids": ["128-128", "724-770"], "fallback_section_ids": ["K03"] }, { "section_id": "K03", "section": "Secrets Management Failures", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K03-Secrets-Management-Failures.html", "cre_ids": ["340-375", "774-888", "813-610"], "fallback_section_ids": ["K08"] }, { "section_id": "K04", "section": "Lack Of Cluster Level Policy Enforcement", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", - "cre_ids": ["117-371"], - "fallback_section_ids": ["K04"] + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K04-Lack-Of-Cluster-Level-Policy-Enforcement.html", + "cre_ids": ["117-371"] }, { "section_id": "K05", "section": "Missing Network Segmentation Controls", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K05-Missing-Network-Segmentation-Controls.html", "cre_ids": ["132-146", "467-784", "515-021"], "fallback_section_ids": ["K07"] }, { "section_id": "K06", "section": "Overly Exposed Kubernetes Components", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K06-Overly-Exposed-Kubernetes-Components.html", "cre_ids": ["152-725", "640-364"], "fallback_section_ids": ["K09"] }, { "section_id": "K07", "section": "Misconfigured And Vulnerable Cluster Components", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K07-Misconfigured-And-Vulnerable-Cluster-Components.html", "cre_ids": ["053-751", "233-748", "486-813", "715-334"], "fallback_section_ids": ["K09", "K10"] }, { "section_id": "K08", "section": "Cluster To Cloud Lateral Movement", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K08-Cluster-To-Cloud-Lateral-Movement.html", "cre_ids": ["132-146", "640-364", "724-770"], "fallback_section_ids": ["K03", "K07"] }, { "section_id": "K09", "section": "Broken Authentication Mechanisms", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K09-Broken-Authentication-Mechanisms.html", "cre_ids": ["177-260", "586-842", "633-428"], "fallback_section_ids": ["K06"] }, { "section_id": "K10", "section": "Inadequate Logging And Monitoring", - "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K10-Inadequate-Logging-And-Monitoring.html", "cre_ids": ["058-083", "148-420", "402-706", "843-841"], "fallback_section_ids": ["K05"] } diff --git a/application/tests/owasp_kubernetes_top10_2022_parser_test.py b/application/tests/owasp_kubernetes_top10_2022_parser_test.py new file mode 100644 index 000000000..a0c657c14 --- /dev/null +++ b/application/tests/owasp_kubernetes_top10_2022_parser_test.py @@ -0,0 +1,45 @@ +import unittest + +from application import create_app, sqla # type: ignore +from application.database import db +from application.defs import cre_defs as defs +from application.prompt_client import prompt_client +from application.utils.external_project_parsers.parsers import ( + owasp_kubernetes_top10_2022, +) + + +class TestOwaspKubernetesTop10_2022Parser(unittest.TestCase): + def tearDown(self) -> None: + sqla.session.remove() + sqla.drop_all() + self.app_context.pop() + + def setUp(self) -> None: + self.app = create_app(mode="test") + self.app_context = self.app.app_context() + self.app_context.push() + sqla.create_all() + self.collection = db.Node_collection() + + def test_parse(self) -> None: + for cre_id, name in [ + ("233-748", "Configuration hardening"), + ("486-813", "Configuration"), + ("053-751", "Force build pipeline to check outdated/insecure components"), + ]: + self.collection.add_cre(defs.CRE(id=cre_id, name=name, description="")) + + result = owasp_kubernetes_top10_2022.OwaspKubernetesTop10_2022().parse( + self.collection, prompt_client.PromptHandler(database=self.collection) + ) + + entries = result.results["OWASP Kubernetes Top Ten 2022"] + self.assertEqual(10, len(entries)) + self.assertEqual("K01", entries[0].sectionID) + self.assertEqual("Insecure Workload Configurations", entries[0].section) + self.assertEqual( + ["233-748", "486-813"], [link.document.id for link in entries[0].links] + ) + self.assertEqual("K10", entries[-1].sectionID) + self.assertEqual(["053-751"], [link.document.id for link in entries[-1].links]) diff --git a/application/tests/owasp_kubernetes_top10_2025_parser_test.py b/application/tests/owasp_kubernetes_top10_2025_parser_test.py new file mode 100644 index 000000000..d0a46c26b --- /dev/null +++ b/application/tests/owasp_kubernetes_top10_2025_parser_test.py @@ -0,0 +1,102 @@ +import unittest +import tempfile +from pathlib import Path + +from application import create_app, sqla # type: ignore +from application.database import db +from application.defs import cre_defs as defs +from application.prompt_client import prompt_client +from application.utils.external_project_parsers.parsers import ( + owasp_kubernetes_top10_2025, +) + + +class TestOwaspKubernetesTop10_2025Parser(unittest.TestCase): + def tearDown(self) -> None: + sqla.session.remove() + sqla.drop_all() + self.app_context.pop() + + def setUp(self) -> None: + self.app = create_app(mode="test") + self.app_context = self.app.app_context() + self.app_context.push() + sqla.create_all() + self.collection = db.Node_collection() + + def test_parse(self) -> None: + for cre_id, name in [ + ("233-748", "Configuration hardening"), + ("486-813", "Configuration"), + ("148-420", "Log integrity"), + ("402-706", "Log relevant"), + ("843-841", "Log discretely"), + ]: + self.collection.add_cre(defs.CRE(id=cre_id, name=name, description="")) + + result = owasp_kubernetes_top10_2025.OwaspKubernetesTop10_2025().parse( + self.collection, prompt_client.PromptHandler(database=self.collection) + ) + + entries = result.results["OWASP Kubernetes Top Ten 2025 (Draft)"] + self.assertEqual(10, len(entries)) + self.assertEqual("K01", entries[0].sectionID) + self.assertEqual("Insecure Workload Configurations", entries[0].section) + self.assertEqual( + ["233-748", "486-813"], [link.document.id for link in entries[0].links] + ) + self.assertEqual("K10", entries[-1].sectionID) + self.assertEqual( + ["148-420", "402-706", "843-841"], + [link.document.id for link in entries[-1].links], + ) + + def test_parse_falls_back_to_2022_mapping_when_2025_links_missing(self) -> None: + self.collection.add_cre( + defs.CRE(id="148-420", name="Log integrity", description="") + ) + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + current_file = tmp_path / "k8s_2025.json" + fallback_file = tmp_path / "k8s_2022.json" + current_file.write_text( + """ +[ + { + "section_id": "K10", + "section": "Inadequate Logging And Monitoring", + "hyperlink": "https://example.com/k10", + "cre_ids": ["999-999"], + "fallback_section_ids": ["K05"] + } +] + """.strip(), + encoding="utf-8", + ) + fallback_file.write_text( + """ +[ + { + "section_id": "K05", + "section": "Inadequate Logging and Monitoring", + "hyperlink": "https://example.com/k05", + "cre_ids": ["148-420"] + } +] + """.strip(), + encoding="utf-8", + ) + + parser = owasp_kubernetes_top10_2025.OwaspKubernetesTop10_2025() + parser.data_file = current_file + parser.fallback_data_file = fallback_file + + result = parser.parse( + self.collection, + prompt_client.PromptHandler(database=self.collection), + ) + + entries = result.results["OWASP Kubernetes Top Ten 2025 (Draft)"] + self.assertEqual(1, len(entries)) + self.assertEqual(["148-420"], [link.document.id for link in entries[0].links]) diff --git a/application/tests/owasp_mapping_fixtures_test.py b/application/tests/owasp_mapping_fixtures_test.py index ee9a38238..11fa4692c 100644 --- a/application/tests/owasp_mapping_fixtures_test.py +++ b/application/tests/owasp_mapping_fixtures_test.py @@ -15,9 +15,77 @@ "owasp_top10_2025.json", } CRE_ID_PATTERN = re.compile(r"^\d{3}-\d{3}$") +EXPECTED_SECTION_COUNTS = { + "owasp_aisvs_1_0.json": 14, + "owasp_api_top10_2023.json": 10, + "owasp_cheatsheets_supplement.json": 9, + "owasp_kubernetes_top10_2022.json": 10, + "owasp_kubernetes_top10_2025.json": 10, + "owasp_llm_top10_2025.json": 10, + "owasp_top10_2025.json": 10, +} +EXPECTED_LINK_PREFIXES = { + "owasp_aisvs_1_0.json": ("https://github.com/OWASP/AISVS/tree/main/1.0/en/",), + "owasp_api_top10_2023.json": ("https://owasp.org/API-Security/editions/2023/en/",), + "owasp_cheatsheets_supplement.json": ( + "https://cheatsheetseries.owasp.org/cheatsheets/", + ), + "owasp_kubernetes_top10_2022.json": ( + "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/", + ), + "owasp_kubernetes_top10_2025.json": ( + "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/", + ), + "owasp_llm_top10_2025.json": ("https://genai.owasp.org/llmrisk/",), + "owasp_top10_2025.json": ("https://owasp.org/Top10/2025/",), +} +EXPECTED_GOLDEN_MAPPINGS = { + ("owasp_api_top10_2023.json", "API7"): { + "section": "Server Side Request Forgery", + "cre_ids": ["028-728", "657-084"], + }, + ("owasp_kubernetes_top10_2025.json", "K01"): { + "section": "Insecure Workload Configurations", + "cre_ids": ["233-748", "486-813"], + "fallback_section_ids": None, + }, + ("owasp_kubernetes_top10_2025.json", "K04"): { + "section": "Lack Of Cluster Level Policy Enforcement", + "cre_ids": ["117-371"], + "fallback_section_ids": None, + }, + ("owasp_kubernetes_top10_2025.json", "K07"): { + "section": "Misconfigured And Vulnerable Cluster Components", + "cre_ids": ["053-751", "233-748", "486-813", "715-334"], + "fallback_section_ids": ["K09", "K10"], + }, + ("owasp_llm_top10_2025.json", "LLM01"): { + "section": "Prompt Injection", + "cre_ids": ["161-451", "760-764"], + }, + ("owasp_top10_2025.json", "A05"): { + "section": "Injection", + "cre_ids": ["031-447", "064-808", "760-764"], + }, +} +EXPECTED_CROSS_FIXTURE_LINKS = { + ("owasp_cheatsheets_supplement.json", "Kubernetes Security Cheat Sheet"): { + "cre_ids": ["467-784", "233-748", "486-813"], + }, + ("owasp_aisvs_1_0.json", "AISVS10"): { + "cre_ids": ["307-507", "715-223"], + }, +} class TestOwaspMappingFixtures(unittest.TestCase): + @staticmethod + def _load_fixture(path: Path) -> list[dict[str, object]]: + payload = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(payload, list): + raise AssertionError(f"{path.name} must contain a top-level list") + return payload + def test_fixture_set_is_complete(self) -> None: actual = {path.name for path in FIXTURE_DIR.glob("*.json")} self.assertEqual(actual, EXPECTED_FIXTURES) @@ -25,10 +93,9 @@ def test_fixture_set_is_complete(self) -> None: def test_fixtures_have_expected_mapping_shape(self) -> None: for path in sorted(FIXTURE_DIR.glob("*.json")): with self.subTest(fixture=path.name): - payload = json.loads(path.read_text(encoding="utf-8")) - - self.assertIsInstance(payload, list) + payload = self._load_fixture(path) self.assertGreater(len(payload), 0) + self.assertEqual(EXPECTED_SECTION_COUNTS[path.name], len(payload)) known_section_ids = { entry["section_id"] @@ -43,6 +110,15 @@ def test_fixtures_have_expected_mapping_shape(self) -> None: self.assertTrue(entry["section"].strip()) self.assertIsInstance(entry.get("hyperlink"), str) self.assertTrue(entry["hyperlink"].strip()) + self.assertTrue( + entry["hyperlink"].startswith( + EXPECTED_LINK_PREFIXES[path.name] + ), + msg=( + f"Unexpected hyperlink prefix for {path.name}: " + f"{entry['hyperlink']}" + ), + ) self.assertIn("cre_ids", entry) self.assertIsInstance(entry["cre_ids"], list) self.assertGreater(len(entry["cre_ids"]), 0) @@ -76,3 +152,46 @@ def test_fixtures_have_expected_mapping_shape(self) -> None: self.assertIsInstance(cre_id, str) self.assertTrue(cre_id.strip()) self.assertRegex(cre_id, CRE_ID_PATTERN) + + def test_expected_golden_mappings_remain_stable(self) -> None: + for (fixture_name, section_key), expected in EXPECTED_GOLDEN_MAPPINGS.items(): + path = FIXTURE_DIR / fixture_name + payload = self._load_fixture(path) + entry = next( + entry for entry in payload if entry.get("section_id") == section_key + ) + with self.subTest(fixture=fixture_name, section_id=section_key): + self.assertEqual(expected["section"], entry["section"]) + self.assertEqual(expected["cre_ids"], entry["cre_ids"]) + if "fallback_section_ids" in expected: + self.assertEqual( + expected["fallback_section_ids"], + entry.get("fallback_section_ids"), + ) + + def test_cross_fixture_reference_rows_stay_reviewable(self) -> None: + for (fixture_name, key), expected in EXPECTED_CROSS_FIXTURE_LINKS.items(): + path = FIXTURE_DIR / fixture_name + payload = self._load_fixture(path) + if key.startswith("AISVS"): + entry = next( + entry for entry in payload if entry.get("section_id") == key + ) + else: + entry = next(entry for entry in payload if entry.get("section") == key) + with self.subTest(fixture=fixture_name, key=key): + self.assertEqual(expected["cre_ids"], entry["cre_ids"]) + + def test_known_ambiguous_kubernetes_mapping_is_explicit(self) -> None: + payload = self._load_fixture(FIXTURE_DIR / "owasp_kubernetes_top10_2022.json") + entries_by_section_id = { + entry["section_id"]: entry + for entry in payload + if isinstance(entry.get("section_id"), str) + } + # K01/K09 intentionally share configuration-focused CREs until a better + # K09-specific mapping is validated from upstream source material. + self.assertEqual( + entries_by_section_id["K01"]["cre_ids"], + entries_by_section_id["K09"]["cre_ids"], + ) diff --git a/application/utils/external_project_parsers/data/owasp_mappings/owasp_kubernetes_top10_2022.json b/application/utils/external_project_parsers/data/owasp_mappings/owasp_kubernetes_top10_2022.json new file mode 100644 index 000000000..c4eb3d6fd --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_mappings/owasp_kubernetes_top10_2022.json @@ -0,0 +1,62 @@ +[ + { + "section_id": "K01", + "section": "Insecure Workload Configurations", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K01-insecure-workload-configurations", + "cre_ids": ["233-748", "486-813"] + }, + { + "section_id": "K02", + "section": "Supply Chain Vulnerabilities", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K02-supply-chain-vulnerabilities", + "cre_ids": ["613-285", "613-287"] + }, + { + "section_id": "K03", + "section": "Overly Permissive RBAC Configurations", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K03-overly-permissive-rbac-configurations", + "cre_ids": ["128-128", "724-770"] + }, + { + "section_id": "K04", + "section": "Lack of Centralized Policy Enforcement", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K04-lack-of-centralized-policy-enforcement", + "cre_ids": ["117-371"] + }, + { + "section_id": "K05", + "section": "Inadequate Logging and Monitoring", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K05-inadequate-logging-and-monitoring", + "cre_ids": ["058-083", "148-420", "402-706", "843-841"] + }, + { + "section_id": "K06", + "section": "Broken Authentication Mechanisms", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K06-broken-authentication-mechanisms", + "cre_ids": ["177-260", "586-842", "633-428"] + }, + { + "section_id": "K07", + "section": "Missing Network Segmentation Controls", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K07-missing-network-segmentation-controls", + "cre_ids": ["132-146", "467-784", "515-021"] + }, + { + "section_id": "K08", + "section": "Secrets Management Failures", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K08-secrets-management-failures", + "cre_ids": ["340-375", "774-888", "813-610"] + }, + { + "section_id": "K09", + "section": "Misconfigured Cluster Components", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K09-misconfigured-cluster-components", + "cre_ids": ["233-748", "486-813"] + }, + { + "section_id": "K10", + "section": "Outdated and Vulnerable Kubernetes Components", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K10-outdated-and-vulnerable-kubernetes-components", + "cre_ids": ["053-751", "715-334", "863-521"] + } +] diff --git a/application/utils/external_project_parsers/data/owasp_mappings/owasp_kubernetes_top10_2025.json b/application/utils/external_project_parsers/data/owasp_mappings/owasp_kubernetes_top10_2025.json new file mode 100644 index 000000000..d6546e26f --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_mappings/owasp_kubernetes_top10_2025.json @@ -0,0 +1,70 @@ +[ + { + "section_id": "K01", + "section": "Insecure Workload Configurations", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K01-Insecure-Workload-Configurations.html", + "cre_ids": ["233-748", "486-813"] + }, + { + "section_id": "K02", + "section": "Overly Permissive Authorization Configurations", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K02-Overly-Permissive-Authorization-Configurations.html", + "cre_ids": ["128-128", "724-770"], + "fallback_section_ids": ["K03"] + }, + { + "section_id": "K03", + "section": "Secrets Management Failures", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K03-Secrets-Management-Failures.html", + "cre_ids": ["340-375", "774-888", "813-610"], + "fallback_section_ids": ["K08"] + }, + { + "section_id": "K04", + "section": "Lack Of Cluster Level Policy Enforcement", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K04-Lack-Of-Cluster-Level-Policy-Enforcement.html", + "cre_ids": ["117-371"] + }, + { + "section_id": "K05", + "section": "Missing Network Segmentation Controls", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K05-Missing-Network-Segmentation-Controls.html", + "cre_ids": ["132-146", "467-784", "515-021"], + "fallback_section_ids": ["K07"] + }, + { + "section_id": "K06", + "section": "Overly Exposed Kubernetes Components", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K06-Overly-Exposed-Kubernetes-Components.html", + "cre_ids": ["152-725", "640-364"], + "fallback_section_ids": ["K09"] + }, + { + "section_id": "K07", + "section": "Misconfigured And Vulnerable Cluster Components", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K07-Misconfigured-And-Vulnerable-Cluster-Components.html", + "cre_ids": ["053-751", "233-748", "486-813", "715-334"], + "fallback_section_ids": ["K09", "K10"] + }, + { + "section_id": "K08", + "section": "Cluster To Cloud Lateral Movement", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K08-Cluster-To-Cloud-Lateral-Movement.html", + "cre_ids": ["132-146", "640-364", "724-770"], + "fallback_section_ids": ["K03", "K07"] + }, + { + "section_id": "K09", + "section": "Broken Authentication Mechanisms", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K09-Broken-Authentication-Mechanisms.html", + "cre_ids": ["177-260", "586-842", "633-428"], + "fallback_section_ids": ["K06"] + }, + { + "section_id": "K10", + "section": "Inadequate Logging And Monitoring", + "hyperlink": "https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K10-Inadequate-Logging-And-Monitoring.html", + "cre_ids": ["058-083", "148-420", "402-706", "843-841"], + "fallback_section_ids": ["K05"] + } +] diff --git a/application/utils/external_project_parsers/parsers/owasp_kubernetes_top10_2022.py b/application/utils/external_project_parsers/parsers/owasp_kubernetes_top10_2022.py new file mode 100644 index 000000000..612ab75dd --- /dev/null +++ b/application/utils/external_project_parsers/parsers/owasp_kubernetes_top10_2022.py @@ -0,0 +1,50 @@ +import json +from pathlib import Path + +from application.database import db +from application.defs import cre_defs as defs +from application.prompt_client import prompt_client +from application.utils.external_project_parsers.base_parser_defs import ( + ParseResult, + ParserInterface, +) + + +class OwaspKubernetesTop10_2022(ParserInterface): + name = "OWASP Kubernetes Top Ten 2022" + data_file = ( + Path(__file__).resolve().parents[1] + / "data" + / "owasp_mappings" + / "owasp_kubernetes_top10_2022.json" + ) + + def parse(self, cache: db.Node_collection, ph: prompt_client.PromptHandler): + with self.data_file.open("r", encoding="utf-8") as handle: + raw_entries = json.load(handle) + + entries = [] + for entry in raw_entries: + standard = defs.Standard( + name=self.name, + sectionID=entry["section_id"], + section=entry["section"], + hyperlink=entry["hyperlink"], + ) + for cre_id in entry.get("cre_ids", []): + cres = cache.get_CREs(external_id=cre_id) + if not cres: + continue + standard.add_link( + defs.Link( + ltype=defs.LinkTypes.LinkedTo, + document=cres[0].shallow_copy(), + ) + ) + entries.append(standard) + + return ParseResult( + results={self.name: entries}, + calculate_gap_analysis=False, + calculate_embeddings=False, + ) diff --git a/application/utils/external_project_parsers/parsers/owasp_kubernetes_top10_2025.py b/application/utils/external_project_parsers/parsers/owasp_kubernetes_top10_2025.py new file mode 100644 index 000000000..d67151668 --- /dev/null +++ b/application/utils/external_project_parsers/parsers/owasp_kubernetes_top10_2025.py @@ -0,0 +1,80 @@ +import json +from pathlib import Path + +from application.database import db +from application.defs import cre_defs as defs +from application.prompt_client import prompt_client +from application.utils.external_project_parsers.base_parser_defs import ( + ParseResult, + ParserInterface, +) + + +class OwaspKubernetesTop10_2025(ParserInterface): + name = "OWASP Kubernetes Top Ten 2025 (Draft)" + data_file = ( + Path(__file__).resolve().parents[1] + / "data" + / "owasp_mappings" + / "owasp_kubernetes_top10_2025.json" + ) + fallback_data_file = ( + Path(__file__).resolve().parents[1] + / "data" + / "owasp_mappings" + / "owasp_kubernetes_top10_2022.json" + ) + + def parse(self, cache: db.Node_collection, ph: prompt_client.PromptHandler): + with self.data_file.open("r", encoding="utf-8") as handle: + raw_entries = json.load(handle) + with self.fallback_data_file.open("r", encoding="utf-8") as handle: + fallback_entries = { + entry["section_id"]: entry for entry in json.load(handle) + } + + entries = [] + for entry in raw_entries: + standard = defs.Standard( + name=self.name, + sectionID=entry["section_id"], + section=entry["section"], + hyperlink=entry["hyperlink"], + ) + linked_cre_ids = [] + for cre_id in entry.get("cre_ids", []): + cres = cache.get_CREs(external_id=cre_id) + if not cres: + continue + linked_cre_ids.append(cre_id) + standard.add_link( + defs.Link( + ltype=defs.LinkTypes.LinkedTo, + document=cres[0].shallow_copy(), + ) + ) + if not linked_cre_ids: + for section_id in entry.get("fallback_section_ids", []): + fallback_entry = fallback_entries.get(section_id) + if not fallback_entry: + continue + for cre_id in fallback_entry.get("cre_ids", []): + if cre_id in linked_cre_ids: + continue + cres = cache.get_CREs(external_id=cre_id) + if not cres: + continue + linked_cre_ids.append(cre_id) + standard.add_link( + defs.Link( + ltype=defs.LinkTypes.LinkedTo, + document=cres[0].shallow_copy(), + ) + ) + entries.append(standard) + + return ParseResult( + results={self.name: entries}, + calculate_gap_analysis=False, + calculate_embeddings=False, + ) diff --git a/docs/owasp_mapping_fixtures.md b/docs/owasp_mapping_fixtures.md new file mode 100644 index 000000000..b48cf9ba4 --- /dev/null +++ b/docs/owasp_mapping_fixtures.md @@ -0,0 +1,45 @@ +# OWASP Mapping Fixtures + +These JSON files are validation fixtures for OWASP-to-CRE mapping work. + +They are intentionally curated and reviewable. They are not the production +OWASP-to-CRE linking path. Production linking is expected to move through the +ETL/librarian pipeline once that path is ready. + +## Source snapshots + +- `owasp_top10_2025.json`: `https://owasp.org/Top10/2025/` +- `owasp_api_top10_2023.json`: `https://owasp.org/API-Security/editions/2023/en/` +- `owasp_llm_top10_2025.json`: `https://genai.owasp.org/llmrisk/` +- `owasp_aisvs_1_0.json`: `https://github.com/OWASP/AISVS/tree/main/1.0/en` +- `owasp_kubernetes_top10_2022.json`: `https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/` +- `owasp_kubernetes_top10_2025.json`: `https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/` +- `owasp_cheatsheets_supplement.json`: `https://cheatsheetseries.owasp.org/cheatsheets/` + +## Dataset notes + +- Kubernetes 2025 uses `fallback_section_ids` where 2025 sections consolidate or + rename 2022 coverage and the fixture needs an explicit review trail. +- `owasp_cheatsheets_supplement.json` is supplemental material for validation and + does not represent the full OWASP Cheat Sheet Series. +- AISVS links and section names should be treated as provisional review data and + revalidated against upstream if the project publishes a different canonical + structure later. + +## Known ambiguous mappings + +- `owasp_kubernetes_top10_2022.json` currently maps both `K01` and `K09` to the + same configuration-focused CRE set. This is intentional in the fixture until a + better `K09`-specific mapping is validated from upstream source material. + +## Intended handoff to ETL/librarian work + +These fixtures are meant to provide: + +- stable section metadata for evaluation +- explicit expected `cre_ids` for selected OWASP resources +- fallback relationships that can be compared against ETL output +- a reviewable record of provisional or ambiguous mappings + +Future ETL/librarian integration should treat these files as validation inputs, +not as an importer contract.