Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions application/tests/cheatsheets_parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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"]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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]
Expand All @@ -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)
Expand All @@ -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(
Expand All @@ -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", "<unknown>"),
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())
Loading