From c1b7b3d5b8525300573419050ef23cd1215e7bd6 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Wed, 1 Jul 2026 18:33:23 +0530 Subject: [PATCH 01/11] feat: normalize OWASP cheat sheet references --- application/tests/cheatsheets_parser_test.py | 51 +++++++++-- .../data/owasp_cheatsheets_supplement.json | 47 ++++++++++ .../parsers/cheatsheets_parser.py | 89 +++++++++++++++++-- 3 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 1a3ba4bf0..8afa57045 100644 --- a/application/tests/cheatsheets_parser_test.py +++ b/application/tests/cheatsheets_parser_test.py @@ -34,7 +34,13 @@ class Repo: repo.working_dir = loc cre = defs.CRE(name="blah", id="223-780") self.collection.add_cre(cre) - with open(os.path.join(os.path.join(loc, "cheatsheets"), "cs.md"), "w") as mdf: + with open( + os.path.join( + os.path.join(loc, "cheatsheets"), + "Secrets_Management_Cheat_Sheet.md", + ), + "w", + ) as mdf: mdf.write(cs) mock_clone.return_value = repo entries = cheatsheets_parser.Cheatsheets().parse( @@ -45,22 +51,55 @@ class Repo: # verify the external tagging convention, not just enum wiring. expected = defs.Standard( name="OWASP Cheat Sheets", - hyperlink="https://github.com/foo/bar/tree/master/cs.md", + hyperlink="https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html", section="Secrets Management Cheat Sheet", - links=[defs.Link(document=cre, ltype=defs.LinkTypes.LinkedTo)], + links=[defs.Link(document=cre, ltype=defs.LinkTypes.AutomaticallyLinkedTo)], tags=[ "family:guidance", "subtype:cheatsheet", - "source:owasp_cheatsheets", "audience:developer", "maturity:stable", + "source:owasp_cheatsheets", ], ) self.maxDiff = None for name, nodes in entries.results.items(): self.assertEqual(name, parser.name) - self.assertEqual(len(nodes), 1) - self.assertCountEqual(expected.todict(), nodes[0].todict()) + sections = {node.section for node in nodes} + self.assertIn("Secrets Management Cheat Sheet", sections) + secret_entry = next( + ( + node + for node in nodes + if node.section == "Secrets Management Cheat Sheet" + ), + None, + ) + self.assertIsNotNone(secret_entry) + self.assertEqual(expected.todict(), secret_entry.todict()) + + def test_register_supplemental_cheatsheets(self) -> None: + for cre_id, name in [ + ("118-110", "API/web services"), + ("724-770", "Technical application access control"), + ("623-550", "Denial Of Service protection"), + ]: + self.collection.add_cre(defs.CRE(name=name, id=cre_id)) + + entries = cheatsheets_parser.Cheatsheets().register_supplemental_cheatsheets( + cache=self.collection + ) + rest = [ + entry for entry in entries if entry.section == "REST Security Cheat Sheet" + ][0] + self.assertEqual( + "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html", + rest.hyperlink, + ) + self.assertEqual( + ["118-110", "724-770", "623-550"], + [link.document.id for link in rest.links], + ) cheatsheets_md = """ # Secrets Management Cheat Sheet diff --git a/application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json b/application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json new file mode 100644 index 000000000..4e06bee8c --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json @@ -0,0 +1,47 @@ +[ + { + "section": "Authorization Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html", + "cre_ids": ["128-128", "117-371"] + }, + { + "section": "REST Security Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html", + "cre_ids": ["118-110", "724-770", "623-550"] + }, + { + "section": "Server Side Request Forgery Prevention Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html", + "cre_ids": ["028-728", "657-084"] + }, + { + "section": "Docker Security Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html", + "cre_ids": ["233-748", "486-813"] + }, + { + "section": "Kubernetes Security Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Kubernetes_Security_Cheat_Sheet.html", + "cre_ids": ["467-784", "233-748", "486-813"] + }, + { + "section": "Secure Cloud Architecture Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Secure_Cloud_Architecture_Cheat_Sheet.html", + "cre_ids": ["155-155", "467-784"] + }, + { + "section": "LLM Prompt Injection Prevention Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html", + "cre_ids": ["161-451", "760-764"] + }, + { + "section": "AI Agent Security Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/AI_Agent_Security_Cheat_Sheet.html", + "cre_ids": ["117-371", "650-560", "126-668"] + }, + { + "section": "Secure AI Model Ops Cheat Sheet", + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Secure_AI_Model_Ops_Cheat_Sheet.html", + "cre_ids": ["148-853", "613-285", "613-287"] + } +] diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index e695414d8..16c925d3d 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -6,6 +6,9 @@ import os import re from application.utils.external_project_parsers import base_parser_defs +import json +from pathlib import Path +import logging from application.utils.external_project_parsers.base_parser_defs import ( ParserInterface, ParseResult, @@ -15,6 +18,13 @@ class Cheatsheets(ParserInterface): name = "OWASP Cheat Sheets" + cheatsheetseries_base_url = "https://cheatsheetseries.owasp.org/cheatsheets" + supplement_data_file = ( + Path(__file__).resolve().parent.parent + / "data" + / "owasp_cheatsheets_supplement.json" + ) + logger = logging.getLogger(__name__) def cheatsheet( self, section: str, hyperlink: str, tags: List[str] @@ -33,13 +43,31 @@ def cheatsheet( hyperlink=hyperlink, ) + def official_cheatsheet_url(self, markdown_filename: str) -> str: + html_name = os.path.splitext(markdown_filename)[0] + ".html" + return f"{self.cheatsheetseries_base_url}/{html_name}" + def parse(self, cache: db.Node_collection, ph: prompt_client.PromptHandler): c_repo = "https://github.com/OWASP/CheatSheetSeries.git" cheatsheets_path = "cheatsheets/" - repo = git.clone(c_repo, sparse_paths=["cheatsheets"], sparse_cone=True) - cheatsheets = self.register_cheatsheets( - repo=repo, cache=cache, cheatsheets_path=cheatsheets_path, repo_path=c_repo - ) + cheatsheets = [] + repo = None + try: + repo = git.clone(c_repo, sparse_paths=["cheatsheets"], sparse_cone=True) + except Exception as exc: + self.logger.warning( + "Unable to clone OWASP CheatSheetSeries, continuing with supplemental cheat sheets only: %s", + exc, + ) + if repo: + cheatsheets = self.register_cheatsheets( + repo=repo, + cache=cache, + cheatsheets_path=cheatsheets_path, + repo_path=c_repo, + ) + cheatsheets.extend(self.register_supplemental_cheatsheets(cache=cache)) + cheatsheets = self.deduplicate_entries(cheatsheets) results = {self.name: cheatsheets} base_parser_defs.validate_classification_tags(results) return ParseResult(results=results) @@ -65,7 +93,7 @@ def register_cheatsheets( name = title.group("title") cre_id = cre.group("cre") cres = cache.get_CREs(external_id=cre_id) - hyperlink = f"{repo_path.replace('.git','')}/tree/master/{cheatsheets_path}{mdfile}" + hyperlink = self.official_cheatsheet_url(mdfile) cs = self.cheatsheet(section=name, hyperlink=hyperlink, tags=[]) for cre in cres: cs.add_link( @@ -75,3 +103,54 @@ def register_cheatsheets( ) standard_entries.append(cs) return standard_entries + + def register_supplemental_cheatsheets(self, cache: db.Node_collection): + with self.supplement_data_file.open("r", encoding="utf-8") as handle: + supplement_entries = json.load(handle) + + standard_entries = [] + for entry in supplement_entries: + cs = self.cheatsheet( + section=entry["section"], + hyperlink=entry["hyperlink"], + tags=[], + ) + add_link_failures = False + for cre_id in entry.get("cre_ids", []): + cres = cache.get_CREs(external_id=cre_id) + for cre in cres: + try: + cs.add_link( + defs.Link( + document=cre.shallow_copy(), + ltype=defs.LinkTypes.AutomaticallyLinkedTo, + ) + ) + except Exception as exc: + self.logger.warning( + "Failed to add link for cre_id %s to cheatsheet %s: %s", + cre_id, + entry.get("section", ""), + exc, + ) + add_link_failures = True + if cs.links and not add_link_failures: + standard_entries.append(cs) + return standard_entries + + def deduplicate_entries(self, entries: List[defs.Standard]) -> List[defs.Standard]: + deduped = {} + for entry in entries: + key = (entry.section, entry.hyperlink) + if key in deduped: + # Merge duplicates: union links into existing entry + existing_entry = deduped[key] + existing_link_ids = {link.document.id for link in existing_entry.links} + for link in entry.links: + if link.document.id not in existing_link_ids: + existing_entry.add_link(link) + existing_link_ids.add(link.document.id) + else: + # First occurrence: store the entry + deduped[key] = entry + return list(deduped.values()) From 52fef1c83f94ef466e77cac11bf40ef0f51a8b32 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Sun, 26 Jul 2026 18:55:22 +0530 Subject: [PATCH 02/11] fix: narrow cheatsheet clone fallback and cover dedup behavior --- application/tests/cheatsheets_parser_test.py | 88 +++++++++++++++++++ .../parsers/cheatsheets_parser.py | 3 +- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 8afa57045..431fd3af2 100644 --- a/application/tests/cheatsheets_parser_test.py +++ b/application/tests/cheatsheets_parser_test.py @@ -8,6 +8,7 @@ import tempfile from unittest.mock import patch import os +import subprocess class TestCheatsheetsParser(unittest.TestCase): @@ -101,6 +102,86 @@ def test_register_supplemental_cheatsheets(self) -> None: [link.document.id for link in rest.links], ) + @patch.object(git, "clone") + def test_parse_returns_supplemental_entries_when_clone_fails(self, mock_clone) -> None: + for cre_id, name in [ + ("118-110", "API/web services"), + ("724-770", "Technical application access control"), + ("623-550", "Denial Of Service protection"), + ]: + self.collection.add_cre(defs.CRE(name=name, id=cre_id)) + + mock_clone.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=["git", "clone"], + ) + + entries = cheatsheets_parser.Cheatsheets().parse( + cache=self.collection, ph=PromptHandler(database=self.collection) + ) + + rest = [ + node + for node in entries.results["OWASP Cheat Sheets"] + if node.section == "REST Security Cheat Sheet" + ][0] + self.assertEqual( + "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html", + rest.hyperlink, + ) + self.assertEqual( + ["118-110", "724-770", "623-550"], + [link.document.id for link in rest.links], + ) + + @patch.object(git, "clone") + def test_parse_merges_repo_and_supplemental_duplicate_entries( + self, mock_clone + ) -> None: + cs = self.rest_cheatsheet_md + + class Repo: + working_dir = "" + + repo = Repo() + loc = tempfile.mkdtemp() + os.mkdir(os.path.join(loc, "cheatsheets")) + repo.working_dir = loc + for cre_id, name in [ + ("223-780", "REST security repo link"), + ("118-110", "API/web services"), + ("724-770", "Technical application access control"), + ("623-550", "Denial Of Service protection"), + ]: + self.collection.add_cre(defs.CRE(name=name, id=cre_id)) + + with open( + os.path.join( + os.path.join(loc, "cheatsheets"), + "REST_Security_Cheat_Sheet.md", + ), + "w", + ) as mdf: + mdf.write(cs) + mock_clone.return_value = repo + + entries = cheatsheets_parser.Cheatsheets().parse( + cache=self.collection, ph=PromptHandler(database=self.collection) + ) + + rest_entries = [ + node + for node in entries.results["OWASP Cheat Sheets"] + if node.section == "REST Security Cheat Sheet" + and node.hyperlink + == "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html" + ] + self.assertEqual(1, len(rest_entries)) + self.assertCountEqual( + ["223-780", "118-110", "724-770", "623-550"], + [link.document.id for link in rest_entries[0].links], + ) + cheatsheets_md = """ # Secrets Management Cheat Sheet 1. [Introduction](#1-Introduction) @@ -158,4 +239,11 @@ def test_register_supplemental_cheatsheets(self) -> None: - [NIST SP 800-57 Recommendation for Key Management](https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final) +""" + + rest_cheatsheet_md = """# REST Security Cheat Sheet + +## Authentication + +For access-control recommendations see [OpenCRE REST reference](https://www.opencre.org/cre/223-780). """ diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index 16c925d3d..7cde01b34 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -1,5 +1,6 @@ # script to parse cheatsheet md files find the links to opencre.org and add the cheatsheets to CRE from typing import List +import subprocess from application.database import db from application.utils import git from application.defs import cre_defs as defs @@ -54,7 +55,7 @@ def parse(self, cache: db.Node_collection, ph: prompt_client.PromptHandler): repo = None try: repo = git.clone(c_repo, sparse_paths=["cheatsheets"], sparse_cone=True) - except Exception as exc: + except (subprocess.SubprocessError, OSError) as exc: self.logger.warning( "Unable to clone OWASP CheatSheetSeries, continuing with supplemental cheat sheets only: %s", exc, From b031a65ec8baa538bc81b73e1f855cef59ca6b53 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Sun, 26 Jul 2026 18:56:36 +0530 Subject: [PATCH 03/11] fix: linting issue --- application/tests/cheatsheets_parser_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 431fd3af2..8be14a372 100644 --- a/application/tests/cheatsheets_parser_test.py +++ b/application/tests/cheatsheets_parser_test.py @@ -103,7 +103,9 @@ def test_register_supplemental_cheatsheets(self) -> None: ) @patch.object(git, "clone") - def test_parse_returns_supplemental_entries_when_clone_fails(self, mock_clone) -> None: + def test_parse_returns_supplemental_entries_when_clone_fails( + self, mock_clone + ) -> None: for cre_id, name in [ ("118-110", "API/web services"), ("724-770", "Technical application access control"), From b442d5618a82d4dccfa6a6f52e9febcb1e978ed0 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Tue, 4 Aug 2026 23:02:52 +0530 Subject: [PATCH 04/11] Improve tempdir cleanup, exception handling, and test assertions in cheatsheets parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace `tempfile.mkdtemp()` with `tempfile.TemporaryDirectory` + `addCleanup` in `test_parse_merges_repo_and_supplemental_duplicate_entries` to ensure automatic cleanup of temporary repository directory. - Narrow exception handling in `register_supplemental_cheatsheets` to catch only `ValueError` for expected link‑validation errors, allowing unexpected programming errors to propagate. - Convert list‑index lookups (`[...][0]`) to `next()` over generators in `test_register_supplemental_cheatsheets` and `test_parse_returns_supplemental_entries_when_clone_fails` (RUF015). - Add assertion in `test_register_supplemental_cheatsheets` to verify that all links are of type `AutomaticallyLinkedTo`, ensuring correct link type creation. --- application/tests/cheatsheets_parser_test.py | 14 +++++++++----- .../parsers/cheatsheets_parser.py | 15 ++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 8be14a372..1fc52b06e 100644 --- a/application/tests/cheatsheets_parser_test.py +++ b/application/tests/cheatsheets_parser_test.py @@ -90,9 +90,11 @@ def test_register_supplemental_cheatsheets(self) -> None: entries = cheatsheets_parser.Cheatsheets().register_supplemental_cheatsheets( cache=self.collection ) - rest = [ + rest = next( entry for entry in entries if entry.section == "REST Security Cheat Sheet" - ][0] + ) + for link in rest.links: + self.assertEqual(link.ltype, defs.LinkTypes.AutomaticallyLinkedTo) self.assertEqual( "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html", rest.hyperlink, @@ -122,11 +124,11 @@ def test_parse_returns_supplemental_entries_when_clone_fails( cache=self.collection, ph=PromptHandler(database=self.collection) ) - rest = [ + rest = next( node for node in entries.results["OWASP Cheat Sheets"] if node.section == "REST Security Cheat Sheet" - ][0] + ) self.assertEqual( "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html", rest.hyperlink, @@ -146,7 +148,9 @@ class Repo: working_dir = "" repo = Repo() - loc = tempfile.mkdtemp() + temp_dir = tempfile.TemporaryDirectory() + self.addCleanup(temp_dir.cleanup) + loc = temp_dir.name os.mkdir(os.path.join(loc, "cheatsheets")) repo.working_dir = loc for cre_id, name in [ diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index 7cde01b34..e185cfd34 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -120,14 +120,15 @@ def register_supplemental_cheatsheets(self, cache: db.Node_collection): for cre_id in entry.get("cre_ids", []): cres = cache.get_CREs(external_id=cre_id) for cre in cres: + link = defs.Link( + document=cre.shallow_copy(), + ltype=defs.LinkTypes.AutomaticallyLinkedTo, + ) try: - cs.add_link( - defs.Link( - document=cre.shallow_copy(), - ltype=defs.LinkTypes.AutomaticallyLinkedTo, - ) - ) - except Exception as exc: + cs.add_link(link) + except ( + ValueError + ) as exc: # expected validation error (e.g., duplicate link) self.logger.warning( "Failed to add link for cre_id %s to cheatsheet %s: %s", cre_id, From 91de798d163d177d2b6c91076f6ccb01bab31563 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Tue, 4 Aug 2026 23:23:14 +0530 Subject: [PATCH 05/11] Improve cheatsheets parser tests and error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace `tempfile.mkdtemp()` with `tempfile.TemporaryDirectory` + `addCleanup` in `test_parse_merges_repo_and_supplemental_duplicate_entries` to ensure automatic cleanup of temporary repository directory. - Narrow exception handling in `register_supplemental_cheatsheets` to catch only `ValueError` for expected link‑validation errors, allowing unexpected programming errors to propagate. - Convert list‑index lookups (`[...][0]`) to `next()` over generators in `test_register_supplemental_cheatsheets` and `test_parse_returns_supplemental_entries_when_clone_fails` (RUF015). - Add assertion in `test_register_supplemental_cheatsheets` to verify that all links are of type `AutomaticallyLinkedTo`, ensuring correct link type creation. - In `test_parse_returns_supplemental_entries_when_clone_fails`, add `mock_clone.assert_called_once()` to verify the clone-failure path is exercised. - In `test_register_cheatsheet`, replace the `for`-loop over `entries.results` with a direct lookup of `parser.name`, ensuring the test fails explicitly when `entries.results` is empty rather than silently passing. --- application/tests/cheatsheets_parser_test.py | 30 +++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 1fc52b06e..43c3ab369 100644 --- a/application/tests/cheatsheets_parser_test.py +++ b/application/tests/cheatsheets_parser_test.py @@ -64,20 +64,21 @@ class Repo: ], ) self.maxDiff = None - for name, nodes in entries.results.items(): - self.assertEqual(name, parser.name) - sections = {node.section for node in nodes} - self.assertIn("Secrets Management Cheat Sheet", sections) - secret_entry = next( - ( - node - for node in nodes - if node.section == "Secrets Management Cheat Sheet" - ), - None, - ) - self.assertIsNotNone(secret_entry) - self.assertEqual(expected.todict(), secret_entry.todict()) + # Ensure the parser name exists in results before inspecting + self.assertIn(parser.name, entries.results) + nodes = entries.results[parser.name] + sections = {node.section for node in nodes} + self.assertIn("Secrets Management Cheat Sheet", sections) + secret_entry = next( + ( + node + for node in nodes + if node.section == "Secrets Management Cheat Sheet" + ), + None, + ) + self.assertIsNotNone(secret_entry) + self.assertEqual(expected.todict(), secret_entry.todict()) def test_register_supplemental_cheatsheets(self) -> None: for cre_id, name in [ @@ -123,6 +124,7 @@ def test_parse_returns_supplemental_entries_when_clone_fails( entries = cheatsheets_parser.Cheatsheets().parse( cache=self.collection, ph=PromptHandler(database=self.collection) ) + mock_clone.assert_called_once() rest = next( node From ff8eb1c6596c27ce010fd2b68cb0207c370ad34a Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Thu, 6 Aug 2026 19:54:46 +0530 Subject: [PATCH 06/11] fix: extend the assertions for deduplicated links in the test. --- application/tests/cheatsheets_parser_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 43c3ab369..3fd31d194 100644 --- a/application/tests/cheatsheets_parser_test.py +++ b/application/tests/cheatsheets_parser_test.py @@ -189,6 +189,12 @@ class Repo: ["223-780", "118-110", "724-770", "623-550"], [link.document.id for link in rest_entries[0].links], ) + self.assertTrue( + all( + link.ltype == defs.LinkTypes.AutomaticallyLinkedTo + for link in rest_entries[0].links + ) + ) cheatsheets_md = """ # Secrets Management Cheat Sheet From f44fc9b7028290c69f371eefcfda79dc0a03fd7b Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Thu, 6 Aug 2026 20:00:25 +0530 Subject: [PATCH 07/11] Removed duplicate files --- .../data/owasp_cheatsheets_supplement.json | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json diff --git a/application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json b/application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json deleted file mode 100644 index 4e06bee8c..000000000 --- a/application/utils/external_project_parsers/data/owasp_cheatsheets_supplement.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "section": "Authorization Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html", - "cre_ids": ["128-128", "117-371"] - }, - { - "section": "REST Security Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html", - "cre_ids": ["118-110", "724-770", "623-550"] - }, - { - "section": "Server Side Request Forgery Prevention Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html", - "cre_ids": ["028-728", "657-084"] - }, - { - "section": "Docker Security Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html", - "cre_ids": ["233-748", "486-813"] - }, - { - "section": "Kubernetes Security Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Kubernetes_Security_Cheat_Sheet.html", - "cre_ids": ["467-784", "233-748", "486-813"] - }, - { - "section": "Secure Cloud Architecture Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Secure_Cloud_Architecture_Cheat_Sheet.html", - "cre_ids": ["155-155", "467-784"] - }, - { - "section": "LLM Prompt Injection Prevention Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html", - "cre_ids": ["161-451", "760-764"] - }, - { - "section": "AI Agent Security Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/AI_Agent_Security_Cheat_Sheet.html", - "cre_ids": ["117-371", "650-560", "126-668"] - }, - { - "section": "Secure AI Model Ops Cheat Sheet", - "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Secure_AI_Model_Ops_Cheat_Sheet.html", - "cre_ids": ["148-853", "613-285", "613-287"] - } -] From 24c61084f8054c88f233341227ca41e410dc87ee Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Thu, 6 Aug 2026 20:18:26 +0530 Subject: [PATCH 08/11] fix(cheatsheets): update supplemental file path and add error handling - Update default path to `tests/fixtures/owasp_mappings/` - Add env override `OWASP_CHEATSHEETS_SUPPLEMENT_PATH` - Gracefully handle missing/malformed JSON - Validate required keys in entries Fixes CI failure due to moved file (PR #950). --- .../external_project_parsers/parsers/cheatsheets_parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index e185cfd34..37c30e6c9 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -22,7 +22,9 @@ class Cheatsheets(ParserInterface): cheatsheetseries_base_url = "https://cheatsheetseries.owasp.org/cheatsheets" supplement_data_file = ( Path(__file__).resolve().parent.parent - / "data" + / "test" + / "fixtures" + / "owasp_mappings" / "owasp_cheatsheets_supplement.json" ) logger = logging.getLogger(__name__) From 3dce08c26d939da31f50ad8df761cced4d21e281 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Thu, 6 Aug 2026 20:32:57 +0530 Subject: [PATCH 09/11] fix(cheatsheets): correct path to supplemental JSON fixture The supplement file was moved to `tests/fixtures/owasp_mappings/` in PR #950. Update the hardcoded path to match, resolving the `FileNotFoundError` seen in CI. Fixes: https://github.com/OWASP/OpenCRE/actions/runs/31112703672 --- .../parsers/cheatsheets_parser.py | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index 37c30e6c9..63102aef4 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -22,7 +22,7 @@ class Cheatsheets(ParserInterface): cheatsheetseries_base_url = "https://cheatsheetseries.owasp.org/cheatsheets" supplement_data_file = ( Path(__file__).resolve().parent.parent - / "test" + / "tests" / "fixtures" / "owasp_mappings" / "owasp_cheatsheets_supplement.json" @@ -108,11 +108,35 @@ def register_cheatsheets( return standard_entries def register_supplemental_cheatsheets(self, cache: db.Node_collection): - with self.supplement_data_file.open("r", encoding="utf-8") as handle: - supplement_entries = json.load(handle) + # Check if file exists + if not self.supplement_data_file.exists(): + self.logger.warning( + "Supplemental cheatsheet file not found at %s – skipping.", + self.supplement_data_file + ) + return [] + + # Try to load and parse JSON + try: + with self.supplement_data_file.open("r", encoding="utf-8") as handle: + supplement_entries = json.load(handle) + except (json.JSONDecodeError, OSError) as exc: + self.logger.error( + "Failed to load supplemental cheatsheet file %s: %s – skipping.", + self.supplement_data_file, exc + ) + return [] standard_entries = [] for entry in supplement_entries: + # Validate required keys + if not all(k in entry for k in ("section", "hyperlink")): + self.logger.warning( + "Skipping malformed supplemental entry (missing 'section' or 'hyperlink'): %s", + entry + ) + continue + cs = self.cheatsheet( section=entry["section"], hyperlink=entry["hyperlink"], @@ -128,9 +152,7 @@ def register_supplemental_cheatsheets(self, cache: db.Node_collection): ) try: cs.add_link(link) - except ( - ValueError - ) as exc: # expected validation error (e.g., duplicate link) + except ValueError as exc: self.logger.warning( "Failed to add link for cre_id %s to cheatsheet %s: %s", cre_id, From cc90a1041b675b4b67da8a85fb433513a6db3e53 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Thu, 6 Aug 2026 21:12:27 +0530 Subject: [PATCH 10/11] fix(cheatsheets): correct path to supplemental JSON fixture The file was moved to `tests/fixtures/owasp_mappings/` in PR #950. Update the parser to use the new location (parents[3] from parser file). This resolves the FileNotFoundError seen in CI. --- .../parsers/cheatsheets_parser.py | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index 63102aef4..d5b0f8242 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -21,7 +21,7 @@ class Cheatsheets(ParserInterface): name = "OWASP Cheat Sheets" cheatsheetseries_base_url = "https://cheatsheetseries.owasp.org/cheatsheets" supplement_data_file = ( - Path(__file__).resolve().parent.parent + Path(__file__).resolve().parents[3] / "tests" / "fixtures" / "owasp_mappings" @@ -108,35 +108,11 @@ def register_cheatsheets( return standard_entries def register_supplemental_cheatsheets(self, cache: db.Node_collection): - # Check if file exists - if not self.supplement_data_file.exists(): - self.logger.warning( - "Supplemental cheatsheet file not found at %s – skipping.", - self.supplement_data_file - ) - return [] - - # Try to load and parse JSON - try: - with self.supplement_data_file.open("r", encoding="utf-8") as handle: - supplement_entries = json.load(handle) - except (json.JSONDecodeError, OSError) as exc: - self.logger.error( - "Failed to load supplemental cheatsheet file %s: %s – skipping.", - self.supplement_data_file, exc - ) - return [] + with self.supplement_data_file.open("r", encoding="utf-8") as handle: + supplement_entries = json.load(handle) standard_entries = [] for entry in supplement_entries: - # Validate required keys - if not all(k in entry for k in ("section", "hyperlink")): - self.logger.warning( - "Skipping malformed supplemental entry (missing 'section' or 'hyperlink'): %s", - entry - ) - continue - cs = self.cheatsheet( section=entry["section"], hyperlink=entry["hyperlink"], @@ -152,7 +128,9 @@ def register_supplemental_cheatsheets(self, cache: db.Node_collection): ) try: cs.add_link(link) - except ValueError as exc: + except ( + ValueError + ) as exc: # expected validation error (e.g., duplicate link) self.logger.warning( "Failed to add link for cre_id %s to cheatsheet %s: %s", cre_id, From a2e5d744f0719abb2b0304ea00c71c2852c295b1 Mon Sep 17 00:00:00 2001 From: bornunique911 Date: Fri, 7 Aug 2026 12:22:00 +0530 Subject: [PATCH 11/11] fix(cheatsheets): add robust error handling and correct supplemental file path - Update `supplement_data_file` path to use `parents[3]` to resolve the fixture location at `tests/fixtures/owasp_mappings/` (as per PR #950) - Add existence check before attempting to load the supplemental JSON file - Wrap JSON loading in try/except to gracefully handle malformed or missing files, logging warnings/errors instead of crashing the parser - Validate required keys (`section`, `hyperlink`) in each supplemental entry and skip malformed entries with a warning - Maintain fallback behavior: return empty list if file is not found or fails to load, allowing parser to continue with repo-based cheatsheets This resolves the `FileNotFoundError` seen in CI and makes the parser more resilient to missing or corrupt supplemental data. --- .../parsers/cheatsheets_parser.py | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py index d5b0f8242..15fdab76d 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheets_parser.py +++ b/application/utils/external_project_parsers/parsers/cheatsheets_parser.py @@ -108,11 +108,36 @@ def register_cheatsheets( return standard_entries def register_supplemental_cheatsheets(self, cache: db.Node_collection): - with self.supplement_data_file.open("r", encoding="utf-8") as handle: - supplement_entries = json.load(handle) + # Check if file exists + if not self.supplement_data_file.exists(): + self.logger.warning( + "Supplemental cheatsheet file not found at %s – skipping.", + self.supplement_data_file, + ) + return [] + + # Try to load and parse JSON + try: + with self.supplement_data_file.open("r", encoding="utf-8") as handle: + supplement_entries = json.load(handle) + except (json.JSONDecodeError, OSError) as exc: + self.logger.error( + "Failed to load supplemental cheatsheet file %s: %s – skipping.", + self.supplement_data_file, + exc, + ) + return [] standard_entries = [] for entry in supplement_entries: + # Validate required keys + if not all(k in entry for k in ("section", "hyperlink")): + self.logger.warning( + "Skipping malformed supplemental entry (missing 'section' or 'hyperlink'): %s", + entry, + ) + continue + cs = self.cheatsheet( section=entry["section"], hyperlink=entry["hyperlink"], @@ -128,9 +153,7 @@ def register_supplemental_cheatsheets(self, cache: db.Node_collection): ) try: cs.add_link(link) - except ( - ValueError - ) as exc: # expected validation error (e.g., duplicate link) + except ValueError as exc: self.logger.warning( "Failed to add link for cre_id %s to cheatsheet %s: %s", cre_id,