diff --git a/application/cmd/cre_main.py b/application/cmd/cre_main.py index b3dfe91c4..c5064fd08 100644 --- a/application/cmd/cre_main.py +++ b/application/cmd/cre_main.py @@ -730,7 +730,11 @@ def backfill_gap_analysis_only( if os.environ.get("CRE_NO_NEO4J") != "1": populate_neo4j_db(db_connection_str) - gap_analysis.backfill_opencre_direct_pairs(collection, refresh=True) + if os.environ.get("OPENCRE_ENV") not in {"heroku", "opencreorg"}: + gap_analysis.backfill_opencre_direct_pairs(collection, refresh=True) + else: + logger.warning("Skipping GA recomputation on production environment") + return missing = _missing_ga_pairs(collection) if max_pairs > 0: @@ -911,6 +915,44 @@ def run(args: argparse.Namespace) -> None: # pragma: no cover BaseParser().register_resource( secure_headers.SecureHeaders, db_connection_str=args.cache_file ) + if getattr(args, "owasp_top10_2025_in", False): + from application.utils.external_project_parsers.parsers import owasp_top10_2025 + + BaseParser().register_resource( + owasp_top10_2025.OwaspTop10_2025, db_connection_str=args.cache_file + ) + if getattr(args, "owasp_api_top10_2023_in", False): + from application.utils.external_project_parsers.parsers import ( + owasp_api_top10_2023, + ) + + BaseParser().register_resource( + owasp_api_top10_2023.OwaspApiTop10_2023, + db_connection_str=args.cache_file, + ) + if getattr(args, "owasp_kubernetes_top10_2022_in", False): + logger.warning( + "--owasp_kubernetes_top10_2022_in requested, but no Kubernetes 2022 parser module is present in this branch; skipping" + ) + if getattr(args, "owasp_kubernetes_top10_2025_in", False): + logger.warning( + "--owasp_kubernetes_top10_2025_in requested, but no Kubernetes 2025 parser module is present in this branch; skipping" + ) + if getattr(args, "owasp_llm_top10_2025_in", False): + from application.utils.external_project_parsers.parsers import ( + owasp_llm_top10_2025, + ) + + BaseParser().register_resource( + owasp_llm_top10_2025.OwaspLlmTop10_2025, + db_connection_str=args.cache_file, + ) + if getattr(args, "owasp_aisvs_in", False): + from application.utils.external_project_parsers.parsers import owasp_aisvs + + BaseParser().register_resource( + owasp_aisvs.OwaspAisvs, db_connection_str=args.cache_file + ) if args.pci_dss_4_in: from application.utils.external_project_parsers.parsers import pci_dss diff --git a/application/tests/cheatsheet_extractor_test.py b/application/tests/cheatsheet_extractor_test.py new file mode 100644 index 000000000..ea159257f --- /dev/null +++ b/application/tests/cheatsheet_extractor_test.py @@ -0,0 +1,169 @@ +import unittest +from application.utils.external_project_parsers.parsers.cheatsheet_extractor import ( + extract_cheatsheet_record, +) +from application.defs.cheatsheet_defs import SUMMARY_MAX_LENGTH + +SOURCE_PATH = "cheatsheets/Secrets_Management_Cheat_Sheet.md" +EXPECTED_SOURCE_ID = "Secrets_Management_Cheat_Sheet" +EXPECTED_HYPERLINK = ( + "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html" +) + +NORMAL_MD = """\ +# Secrets Management Cheat Sheet + +## Introduction +Storage guidance. + +## Architectural Patterns +Use vaults and environment isolation. +""" + +MISSING_H1_MD = """\ +## Introduction +No H1 present. + +## Details +More content. +""" + +EMPTY_MD = "" + +# No ## Introduction heading exists, so summary extraction +# falls back to the first available body content. +BODY_UNDER_H1_MD = """\ +# Single Heading Cheat Sheet + +Body text directly under H1, no subheadings at all. +""" + +# Leading whitespace before H1 and malformed ## headings +# should still be normalized and extracted correctly. +MALFORMED_MD = """\ + # Malformed Title + +##malformed + +## Introduction +Some intro text. + +## Valid Heading +""" + + +class TestNormal(unittest.TestCase): + def setUp(self): + self.record = extract_cheatsheet_record(NORMAL_MD, SOURCE_PATH) + + # source-derived fields should remain deterministic and + # independent of markdown content across all extraction paths. + def test_source(self): + self.assertEqual(self.record.source, "owasp_cheatsheets") + + def test_source_id(self): + self.assertEqual(self.record.source_id, EXPECTED_SOURCE_ID) + + def test_hyperlink(self): + self.assertEqual(self.record.hyperlink, EXPECTED_HYPERLINK) + + def test_raw_markdown_path(self): + self.assertEqual(self.record.raw_markdown_path, SOURCE_PATH) + + def test_title(self): + self.assertEqual(self.record.title, "Secrets Management Cheat Sheet") + + def test_summary(self): + self.assertEqual(self.record.summary, "Storage guidance.") + + def test_summary_bounded(self): + # Summary truncation is enforced centrally via + # CheatsheetRecord.__post_init__. + self.assertLessEqual(len(self.record.summary), SUMMARY_MAX_LENGTH) + + def test_headings(self): + self.assertIn("Introduction", self.record.headings) + self.assertIn("Architectural Patterns", self.record.headings) + + def test_fallback_not_used(self): + self.assertEqual(self.record.metadata["fallback_used"], "false") + + +class TestMissingH1(unittest.TestCase): + def setUp(self): + self.record = extract_cheatsheet_record(MISSING_H1_MD, SOURCE_PATH) + + def test_title_is_fallback(self): + self.assertEqual(self.record.title, "No title found.") + + def test_summary_from_introduction(self): + self.assertIn("no h1", self.record.summary.lower()) + + def test_headings_extracted(self): + self.assertIn("Introduction", self.record.headings) + self.assertIn("Details", self.record.headings) + + def test_fallback_used(self): + self.assertEqual(self.record.metadata["fallback_used"], "true") + + +class TestEmptyMarkdown(unittest.TestCase): + def setUp(self): + self.record = extract_cheatsheet_record(EMPTY_MD, SOURCE_PATH) + + def test_title_is_fallback(self): + self.assertEqual(self.record.title, "No title found.") + + def test_summary_no_summary_found(self): + # Empty markdown should trigger terminal summary fallback. + self.assertEqual(self.record.summary, "No summary found.") + + def test_headings_empty(self): + self.assertEqual(self.record.headings, []) + + def test_fallback_used(self): + self.assertEqual(self.record.metadata["fallback_used"], "true") + + +class TestBodyUnderH1(unittest.TestCase): + def setUp(self): + self.record = extract_cheatsheet_record(BODY_UNDER_H1_MD, SOURCE_PATH) + + def test_title(self): + self.assertEqual(self.record.title, "Single Heading Cheat Sheet") + + def test_summary_from_fallback_via_h1(self): + # Summary fallback should extract body content beneath the H1 section. + self.assertIn("body text", self.record.summary.lower()) + + def test_headings_empty(self): + # No valid ## headings should produce an empty headings list. + self.assertEqual(self.record.headings, []) + + def test_fallback_used(self): + self.assertEqual(self.record.metadata["fallback_used"], "true") + + +class TestMalformedHeadings(unittest.TestCase): + def setUp(self): + self.record = extract_cheatsheet_record(MALFORMED_MD, SOURCE_PATH) + + def test_malformed_h1_extracted(self): + self.assertEqual(self.record.title, "Malformed Title") + + def test_malformed_h2_in_headings(self): + self.assertIn("malformed", self.record.headings) + + def test_valid_headings_also_extracted(self): + self.assertIn("Introduction", self.record.headings) + self.assertIn("Valid Heading", self.record.headings) + + def test_summary_from_introduction(self): + self.assertIn("intro", self.record.summary.lower()) + + def test_fallback_not_used(self): + self.assertEqual(self.record.metadata["fallback_used"], "false") + + +if __name__ == "__main__": + unittest.main() diff --git a/application/tests/cheatsheets_parser_test.py b/application/tests/cheatsheets_parser_test.py index 1a3ba4bf0..47257a56a 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,22 @@ 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()) + self.assertEqual(expected.todict(), nodes[0].todict()) cheatsheets_md = """ # Secrets Management Cheat Sheet diff --git a/application/tests/owasp_aisvs_parser_test.py b/application/tests/owasp_aisvs_parser_test.py new file mode 100644 index 000000000..edf547bcd --- /dev/null +++ b/application/tests/owasp_aisvs_parser_test.py @@ -0,0 +1,66 @@ +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_aisvs + + +class TestOwaspAisvsParser(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 [ + ("227-045", "Identify sensitive data and subject it to a policy"), + ( + "307-507", + "Allow only trusted sources both build time and runtime; therefore perform integrity checks on all resources and code", + ), + ( + "162-655", + "Documentation of all components' business or security function", + ), + ]: + self.collection.add_cre(defs.CRE(id=cre_id, name=name, description="")) + + result = owasp_aisvs.OwaspAisvs().parse( + self.collection, prompt_client.PromptHandler(database=self.collection) + ) + + entries = result.results["OWASP AI Security Verification Standard (AISVS)"] + self.assertEqual(14, len(entries)) + self.assertEqual("AISVS1", entries[0].sectionID) + self.assertEqual( + "Training Data Governance & Bias Management", entries[0].section + ) + self.assertEqual( + "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C01-Training-Data-Integrity-and-Traceability.md", + entries[0].hyperlink, + ) + self.assertEqual( + ["227-045", "307-507"], + [link.document.id for link in entries[0].links], + ) + self.assertEqual("AISVS14", entries[-1].sectionID) + self.assertEqual( + "Human Oversight, Accountability & Governance", entries[-1].section + ) + self.assertEqual( + "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C14-Human-Oversight.md", + entries[-1].hyperlink, + ) + self.assertEqual( + ["162-655"], + [link.document.id for link in entries[-1].links], + ) diff --git a/application/tests/owasp_api_top10_2023_parser_test.py b/application/tests/owasp_api_top10_2023_parser_test.py new file mode 100644 index 000000000..4f7d8e1b9 --- /dev/null +++ b/application/tests/owasp_api_top10_2023_parser_test.py @@ -0,0 +1,47 @@ +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_api_top10_2023 + + +class TestOwaspApiTop10_2023Parser(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 [ + ("304-667", "Protect API against unauthorized access/modification (IDOR)"), + ("724-770", "Technical application access control"), + ("715-223", "Ensure trusted origin of third party resources"), + ]: + self.collection.add_cre(defs.CRE(id=cre_id, name=name, description="")) + + result = owasp_api_top10_2023.OwaspApiTop10_2023().parse( + self.collection, prompt_client.PromptHandler(database=self.collection) + ) + + entries = result.results["OWASP API Security Top 10 2023"] + self.assertEqual(10, len(entries)) + self.assertEqual("API1", entries[0].sectionID) + self.assertEqual("Broken Object Level Authorization", entries[0].section) + self.assertEqual( + ["304-667", "724-770"], + [link.document.id for link in entries[0].links], + ) + self.assertEqual("API10", entries[-1].sectionID) + self.assertEqual( + ["715-223"], + [link.document.id for link in entries[-1].links], + ) diff --git a/application/tests/owasp_llm_top10_2025_parser_test.py b/application/tests/owasp_llm_top10_2025_parser_test.py new file mode 100644 index 000000000..f31e4316e --- /dev/null +++ b/application/tests/owasp_llm_top10_2025_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_llm_top10_2025 + + +class TestOwaspLlmTop10_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 [ + ("161-451", "Output encoding and injection prevention"), + ("064-808", "Encode output context-specifically"), + ("760-764", "Injection protection"), + ("623-550", "Denial Of Service protection"), + ]: + self.collection.add_cre(defs.CRE(id=cre_id, name=name, description="")) + + result = owasp_llm_top10_2025.OwaspLlmTop10_2025().parse( + self.collection, prompt_client.PromptHandler(database=self.collection) + ) + + entries = result.results["OWASP Top 10 for LLM and Gen AI Apps 2025"] + self.assertEqual(10, len(entries)) + self.assertEqual("LLM01", entries[0].sectionID) + self.assertEqual("Prompt Injection", entries[0].section) + self.assertEqual( + ["161-451", "760-764"], [link.document.id for link in entries[0].links] + ) + self.assertEqual(["064-808"], [link.document.id for link in entries[4].links]) + self.assertEqual("LLM10", entries[-1].sectionID) + self.assertEqual(["623-550"], [link.document.id for link in entries[-1].links]) diff --git a/application/tests/owasp_top10_2025_parser_test.py b/application/tests/owasp_top10_2025_parser_test.py new file mode 100644 index 000000000..de4f86a9f --- /dev/null +++ b/application/tests/owasp_top10_2025_parser_test.py @@ -0,0 +1,80 @@ +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_top10_2025 + + +class TestOwaspTop10_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: + self.collection.add_cre( + defs.CRE(id="177-260", name="Session management", description="") + ) + self.collection.add_cre( + defs.CRE( + id="117-371", + name="Use a centralized access control mechanism", + description="", + ) + ) + self.collection.add_cre( + defs.CRE( + id="724-770", + name="Technical application access control", + description="", + ) + ) + self.collection.add_cre( + defs.CRE( + id="031-447", name="Whitelist all external (HTTP) input", description="" + ) + ) + self.collection.add_cre( + defs.CRE( + id="064-808", name="Encode output context-specifically", description="" + ) + ) + self.collection.add_cre( + defs.CRE(id="760-764", name="Injection protection", description="") + ) + self.collection.add_cre( + defs.CRE(id="513-183", name="Error handling", description="") + ) + + result = owasp_top10_2025.OwaspTop10_2025().parse( + self.collection, + prompt_client.PromptHandler(database=self.collection), + ) + + entries = result.results["OWASP Top 10 2025"] + self.assertEqual(10, len(entries)) + self.assertEqual("A01", entries[0].sectionID) + self.assertEqual("Broken Access Control", entries[0].section) + self.assertEqual( + "https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/", + entries[0].hyperlink, + ) + self.assertEqual( + ["117-371", "177-260", "724-770"], + [link.document.id for link in entries[0].links], + ) + self.assertEqual( + ["031-447", "064-808", "760-764"], + [link.document.id for link in entries[4].links], + ) + self.assertEqual("A10", entries[-1].sectionID) + self.assertEqual(["513-183"], [link.document.id for link in entries[-1].links]) diff --git a/application/utils/external_project_parsers/data/owasp_aisvs_1_0.json b/application/utils/external_project_parsers/data/owasp_aisvs_1_0.json new file mode 100644 index 000000000..9142ec347 --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_aisvs_1_0.json @@ -0,0 +1,86 @@ +[ + { + "section_id": "AISVS1", + "section": "Training Data Governance & Bias Management", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C01-Training-Data-Integrity-and-Traceability.md", + "cre_ids": ["227-045", "307-507"] + }, + { + "section_id": "AISVS2", + "section": "User Input Validation", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C02-Input-Validation.md", + "cre_ids": ["031-447", "760-764"] + }, + { + "section_id": "AISVS3", + "section": "Model Lifecycle Management & Change Control", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C03-Model-Lifecycle-Management.md", + "cre_ids": ["148-853", "613-285"] + }, + { + "section_id": "AISVS4", + "section": "Infrastructure, Configuration & Deployment Security", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C04-Infrastructure.md", + "cre_ids": ["233-748", "486-813"] + }, + { + "section_id": "AISVS5", + "section": "Access Control & Identity for AI Components & Users", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C05-Access-Control-and-Identity.md", + "cre_ids": ["633-428", "724-770"] + }, + { + "section_id": "AISVS6", + "section": "Supply Chain Security for Models, Frameworks & Data", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C06-Supply-Chain.md", + "cre_ids": ["613-285", "613-287", "863-521"] + }, + { + "section_id": "AISVS7", + "section": "Model Behavior, Output Control & Safety Assurance", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C07-Model-Behavior.md", + "cre_ids": ["064-808", "141-555"] + }, + { + "section_id": "AISVS8", + "section": "Memory, Embeddings & Vector Database Security", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C08-Memory-Embeddings-and-Vector-Database.md", + "cre_ids": ["126-668", "538-770"] + }, + { + "section_id": "AISVS9", + "section": "Autonomous Orchestration & Agentic Action Security", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C09-Orchestration-and-Agentic-Action.md", + "cre_ids": ["117-371", "650-560"] + }, + { + "section_id": "AISVS10", + "section": "Model Context Protocol (MCP) Security", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C10-MCP-Security.md", + "cre_ids": ["307-507", "715-223"] + }, + { + "section_id": "AISVS11", + "section": "Adversarial Robustness & Privacy Defense", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C11-Adversarial-Robustness.md", + "cre_ids": ["141-555", "623-550"] + }, + { + "section_id": "AISVS12", + "section": "Privacy Protection & Personal Data Management", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C12-Privacy.md", + "cre_ids": ["126-668", "227-045", "482-866"] + }, + { + "section_id": "AISVS13", + "section": "Monitoring, Logging & Anomaly Detection", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C13-Monitoring-and-Logging.md", + "cre_ids": ["058-083", "148-420", "402-706", "843-841"] + }, + { + "section_id": "AISVS14", + "section": "Human Oversight, Accountability & Governance", + "hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C14-Human-Oversight.md", + "cre_ids": ["162-655", "766-162"] + } +] diff --git a/application/utils/external_project_parsers/data/owasp_api_top10_2023.json b/application/utils/external_project_parsers/data/owasp_api_top10_2023.json new file mode 100644 index 000000000..7a8df0ed0 --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_api_top10_2023.json @@ -0,0 +1,62 @@ +[ + { + "section_id": "API1", + "section": "Broken Object Level Authorization", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/", + "cre_ids": ["304-667", "724-770"] + }, + { + "section_id": "API2", + "section": "Broken Authentication", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/", + "cre_ids": ["177-260", "586-842", "633-428"] + }, + { + "section_id": "API3", + "section": "Broken Object Property Level Authorization", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/", + "cre_ids": ["538-770", "724-770", "128-128"] + }, + { + "section_id": "API4", + "section": "Unrestricted Resource Consumption", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/", + "cre_ids": ["623-550"] + }, + { + "section_id": "API5", + "section": "Broken Function Level Authorization", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/", + "cre_ids": ["650-560", "724-770"] + }, + { + "section_id": "API6", + "section": "Unrestricted Access to Sensitive Business Flows", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/", + "cre_ids": ["534-605", "630-573"] + }, + { + "section_id": "API7", + "section": "Server Side Request Forgery", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/", + "cre_ids": ["028-728", "657-084"] + }, + { + "section_id": "API8", + "section": "Security Misconfiguration", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/", + "cre_ids": ["486-813"] + }, + { + "section_id": "API9", + "section": "Improper Inventory Management", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xa9-improper-inventory-management/", + "cre_ids": ["162-655", "863-521"] + }, + { + "section_id": "API10", + "section": "Unsafe Consumption of APIs", + "hyperlink": "https://owasp.org/API-Security/editions/2023/en/0xaa-unsafe-consumption-of-apis/", + "cre_ids": ["715-223"] + } +] diff --git a/application/utils/external_project_parsers/data/owasp_llm_top10_2025.json b/application/utils/external_project_parsers/data/owasp_llm_top10_2025.json new file mode 100644 index 000000000..b761d5e09 --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_llm_top10_2025.json @@ -0,0 +1,62 @@ +[ + { + "section_id": "LLM01", + "section": "Prompt Injection", + "hyperlink": "https://genai.owasp.org/llmrisk/llm01-prompt-injection/", + "cre_ids": ["161-451", "760-764"] + }, + { + "section_id": "LLM02", + "section": "Sensitive Information Disclosure", + "hyperlink": "https://genai.owasp.org/llmrisk/llm022025-sensitive-information-disclosure/", + "cre_ids": ["126-668", "227-045"] + }, + { + "section_id": "LLM03", + "section": "Supply Chain", + "hyperlink": "https://genai.owasp.org/llmrisk/llm032025-supply-chain/", + "cre_ids": ["613-285", "613-287"] + }, + { + "section_id": "LLM04", + "section": "Data and Model Poisoning", + "hyperlink": "https://genai.owasp.org/llmrisk/llm042025-data-and-model-poisoning/", + "cre_ids": ["307-507", "613-287"] + }, + { + "section_id": "LLM05", + "section": "Improper Output Handling", + "hyperlink": "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/", + "cre_ids": ["064-808"] + }, + { + "section_id": "LLM06", + "section": "Excessive Agency", + "hyperlink": "https://genai.owasp.org/llmrisk/llm062025-excessive-agency/", + "cre_ids": ["117-371", "650-560"] + }, + { + "section_id": "LLM07", + "section": "System Prompt Leakage", + "hyperlink": "https://genai.owasp.org/llmrisk/llm072025-system-prompt-leakage/", + "cre_ids": ["126-668", "227-045"] + }, + { + "section_id": "LLM08", + "section": "Vector and Embedding Weaknesses", + "hyperlink": "https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/", + "cre_ids": ["126-668", "538-770"] + }, + { + "section_id": "LLM09", + "section": "Misinformation", + "hyperlink": "https://genai.owasp.org/llmrisk/llm092025-misinformation/", + "cre_ids": ["141-555"] + }, + { + "section_id": "LLM10", + "section": "Unbounded Consumption", + "hyperlink": "https://genai.owasp.org/llmrisk/llm102025-unbounded-consumption/", + "cre_ids": ["267-031", "623-550"] + } +] diff --git a/application/utils/external_project_parsers/data/owasp_top10_2025.json b/application/utils/external_project_parsers/data/owasp_top10_2025.json new file mode 100644 index 000000000..7e19d1a4e --- /dev/null +++ b/application/utils/external_project_parsers/data/owasp_top10_2025.json @@ -0,0 +1,62 @@ +[ + { + "section_id": "A01", + "section": "Broken Access Control", + "hyperlink": "https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/", + "cre_ids": ["117-371", "177-260", "724-770"] + }, + { + "section_id": "A02", + "section": "Security Misconfiguration", + "hyperlink": "https://owasp.org/Top10/2025/A02_2025-Security_Misconfiguration/", + "cre_ids": ["486-813"] + }, + { + "section_id": "A03", + "section": "Software Supply Chain Failures", + "hyperlink": "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/", + "cre_ids": ["613-286", "613-287", "715-223", "863-521"] + }, + { + "section_id": "A04", + "section": "Cryptographic Failures", + "hyperlink": "https://owasp.org/Top10/2025/A04_2025-Cryptographic_Failures/", + "cre_ids": ["170-772", "227-045"] + }, + { + "section_id": "A05", + "section": "Injection", + "hyperlink": "https://owasp.org/Top10/2025/A05_2025-Injection/", + "cre_ids": ["031-447", "064-808", "760-764"] + }, + { + "section_id": "A06", + "section": "Insecure Design", + "hyperlink": "https://owasp.org/Top10/2025/A06_2025-Insecure_Design/", + "cre_ids": ["126-668", "155-155"] + }, + { + "section_id": "A07", + "section": "Authentication Failures", + "hyperlink": "https://owasp.org/Top10/2025/A07_2025-Authentication_Failures/", + "cre_ids": ["002-630", "177-260", "586-842", "633-428"] + }, + { + "section_id": "A08", + "section": "Software or Data Integrity Failures", + "hyperlink": "https://owasp.org/Top10/2025/A08_2025-Software_or_Data_Integrity_Failures/", + "cre_ids": ["613-287", "836-068"] + }, + { + "section_id": "A09", + "section": "Security Logging and Alerting Failures", + "hyperlink": "https://owasp.org/Top10/2025/A09_2025-Security_Logging_and_Alerting_Failures/", + "cre_ids": ["067-050", "148-420", "402-706", "843-841"] + }, + { + "section_id": "A10", + "section": "Mishandling of Exceptional Conditions", + "hyperlink": "https://owasp.org/Top10/2025/A10_2025-Mishandling_of_Exceptional_Conditions/", + "cre_ids": ["513-183"] + } +] diff --git a/application/utils/external_project_parsers/parsers/cheatsheet_extractor.py b/application/utils/external_project_parsers/parsers/cheatsheet_extractor.py index 384afe932..f6e555207 100644 --- a/application/utils/external_project_parsers/parsers/cheatsheet_extractor.py +++ b/application/utils/external_project_parsers/parsers/cheatsheet_extractor.py @@ -1,16 +1,27 @@ +import logging import os import re from application.defs.cheatsheet_defs import CheatsheetRecord PARSER_VERSION = "v1" -FALLBACK_USED = "false" CANONICAL_BASE_URL = "https://cheatsheetseries.owasp.org/cheatsheets/" -_TITLE_RE = re.compile(r"^#\s+(?P