66import os
77import re
88from application .utils .external_project_parsers import base_parser_defs
9+ import json
10+ from pathlib import Path
11+ import logging
912from application .utils .external_project_parsers .base_parser_defs import (
1013 ParserInterface ,
1114 ParseResult ,
1518
1619class Cheatsheets (ParserInterface ):
1720 name = "OWASP Cheat Sheets"
21+ cheatsheetseries_base_url = "https://cheatsheetseries.owasp.org/cheatsheets"
22+ supplement_data_file = (
23+ Path (__file__ ).resolve ().parent .parent
24+ / "data"
25+ / "owasp_cheatsheets_supplement.json"
26+ )
27+ logger = logging .getLogger (__name__ )
1828
1929 def cheatsheet (
2030 self , section : str , hyperlink : str , tags : List [str ]
@@ -33,13 +43,31 @@ def cheatsheet(
3343 hyperlink = hyperlink ,
3444 )
3545
46+ def official_cheatsheet_url (self , markdown_filename : str ) -> str :
47+ html_name = os .path .splitext (markdown_filename )[0 ] + ".html"
48+ return f"{ self .cheatsheetseries_base_url } /{ html_name } "
49+
3650 def parse (self , cache : db .Node_collection , ph : prompt_client .PromptHandler ):
3751 c_repo = "https://github.com/OWASP/CheatSheetSeries.git"
3852 cheatsheets_path = "cheatsheets/"
39- repo = git .clone (c_repo , sparse_paths = ["cheatsheets" ], sparse_cone = True )
40- cheatsheets = self .register_cheatsheets (
41- repo = repo , cache = cache , cheatsheets_path = cheatsheets_path , repo_path = c_repo
42- )
53+ cheatsheets = []
54+ repo = None
55+ try :
56+ repo = git .clone (c_repo , sparse_paths = ["cheatsheets" ], sparse_cone = True )
57+ except Exception as exc :
58+ self .logger .warning (
59+ "Unable to clone OWASP CheatSheetSeries, continuing with supplemental cheat sheets only: %s" ,
60+ exc ,
61+ )
62+ if repo :
63+ cheatsheets = self .register_cheatsheets (
64+ repo = repo ,
65+ cache = cache ,
66+ cheatsheets_path = cheatsheets_path ,
67+ repo_path = c_repo ,
68+ )
69+ cheatsheets .extend (self .register_supplemental_cheatsheets (cache = cache ))
70+ cheatsheets = self .deduplicate_entries (cheatsheets )
4371 results = {self .name : cheatsheets }
4472 base_parser_defs .validate_classification_tags (results )
4573 return ParseResult (results = results )
@@ -65,7 +93,7 @@ def register_cheatsheets(
6593 name = title .group ("title" )
6694 cre_id = cre .group ("cre" )
6795 cres = cache .get_CREs (external_id = cre_id )
68- hyperlink = f" { repo_path . replace ( '.git' , '' ) } /tree/master/ { cheatsheets_path } { mdfile } "
96+ hyperlink = self . official_cheatsheet_url ( mdfile )
6997 cs = self .cheatsheet (section = name , hyperlink = hyperlink , tags = [])
7098 for cre in cres :
7199 cs .add_link (
@@ -75,3 +103,54 @@ def register_cheatsheets(
75103 )
76104 standard_entries .append (cs )
77105 return standard_entries
106+
107+ def register_supplemental_cheatsheets (self , cache : db .Node_collection ):
108+ with self .supplement_data_file .open ("r" , encoding = "utf-8" ) as handle :
109+ supplement_entries = json .load (handle )
110+
111+ standard_entries = []
112+ for entry in supplement_entries :
113+ cs = self .cheatsheet (
114+ section = entry ["section" ],
115+ hyperlink = entry ["hyperlink" ],
116+ tags = [],
117+ )
118+ add_link_failures = False
119+ for cre_id in entry .get ("cre_ids" , []):
120+ cres = cache .get_CREs (external_id = cre_id )
121+ for cre in cres :
122+ try :
123+ cs .add_link (
124+ defs .Link (
125+ document = cre .shallow_copy (),
126+ ltype = defs .LinkTypes .AutomaticallyLinkedTo ,
127+ )
128+ )
129+ except Exception as exc :
130+ self .logger .warning (
131+ "Failed to add link for cre_id %s to cheatsheet %s: %s" ,
132+ cre_id ,
133+ entry .get ("section" , "<unknown>" ),
134+ exc ,
135+ )
136+ add_link_failures = True
137+ if cs .links and not add_link_failures :
138+ standard_entries .append (cs )
139+ return standard_entries
140+
141+ def deduplicate_entries (self , entries : List [defs .Standard ]) -> List [defs .Standard ]:
142+ deduped = {}
143+ for entry in entries :
144+ key = (entry .section , entry .hyperlink )
145+ if key in deduped :
146+ # Merge duplicates: union links into existing entry
147+ existing_entry = deduped [key ]
148+ existing_link_ids = {link .document .id for link in existing_entry .links }
149+ for link in entry .links :
150+ if link .document .id not in existing_link_ids :
151+ existing_entry .add_link (link )
152+ existing_link_ids .add (link .document .id )
153+ else :
154+ # First occurrence: store the entry
155+ deduped [key ] = entry
156+ return list (deduped .values ())
0 commit comments