Skip to content

Commit 1f8c74a

Browse files
Merge branch 'main' into feat/frontend-useuser-header-auth
2 parents f960dbd + 4485936 commit 1f8c74a

23 files changed

Lines changed: 9138 additions & 36 deletions

application/database/db.py

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from flask_sqlalchemy.model import DefaultMeta
2424
from sqlalchemy import func, delete, cast as sql_cast, literal, or_
2525
from sqlalchemy.dialects.postgresql import JSONB
26-
from sqlalchemy.exc import OperationalError, IntegrityError
26+
from sqlalchemy.exc import OperationalError, IntegrityError, SQLAlchemyError
2727

2828
from neomodel import (
2929
config,
@@ -2162,6 +2162,10 @@ def text_search(self, text: str) -> List[Optional[cre_defs.Document]]:
21622162
CRE ids before it performs a free text search
21632163
Anything else will be a case insensitive LIKE query in the database
21642164
"""
2165+
search_terms = self._expand_text_search_aliases(text)
2166+
if not search_terms:
2167+
return []
2168+
text = text.strip()
21652169
# structured text search first
21662170
cre_id_search = r"CRE(:| )(?P<id>\d+-\d+)"
21672171
cre_naked_id_search = r"\d\d\d-\d\d\d"
@@ -2213,34 +2217,81 @@ def text_search(self, text: str) -> List[Optional[cre_defs.Document]]:
22132217
if results:
22142218
return list(set(results))
22152219
# fuzzy matches second
2216-
args = [f"%{text}%", "", "", "", "", ""]
22172220
results = {}
2218-
s = set([p for p in permutations(args, 6)])
2219-
for combo in s:
2220-
nodes = self.get_nodes(
2221-
name=combo[0],
2222-
section=combo[1],
2223-
subsection=combo[2],
2224-
link=combo[3],
2225-
description=combo[4],
2226-
partial=True,
2227-
ntype=None, # type: ignore
2228-
sectionID=combo[5],
2229-
)
2230-
if nodes:
2231-
for node in nodes:
2232-
node_key = f"{node.name}:{node.version}:{node.section}:{node.sectionID}:{node.subsection}:"
2233-
results[node_key] = node
2234-
args = [f"%{text}%", None, None]
2235-
for combo in permutations(args, 3):
2236-
cres = self.get_CREs(
2237-
name=combo[0], external_id=combo[1], description=combo[2], partial=True
2238-
)
2239-
if cres:
2240-
for cre in cres:
2241-
results[cre.id] = cre
2221+
for search_term in search_terms:
2222+
args = [f"%{search_term}%", "", "", "", "", ""]
2223+
s = set([p for p in permutations(args, 6)])
2224+
for combo in s:
2225+
nodes = self.get_nodes(
2226+
name=combo[0],
2227+
section=combo[1],
2228+
subsection=combo[2],
2229+
link=combo[3],
2230+
description=combo[4],
2231+
partial=True,
2232+
ntype=None, # type: ignore
2233+
sectionID=combo[5],
2234+
)
2235+
if nodes:
2236+
for node in nodes:
2237+
node_key = f"{node.name}:{node.version}:{node.section}:{node.sectionID}:{node.subsection}:"
2238+
results[node_key] = node
2239+
args = [f"%{search_term}%", None, None]
2240+
for combo in permutations(args, 3):
2241+
cres = self.get_CREs(
2242+
name=combo[0],
2243+
external_id=combo[1],
2244+
description=combo[2],
2245+
partial=True,
2246+
)
2247+
if cres:
2248+
for cre in cres:
2249+
results[cre.id] = cre
22422250
return list(results.values())
22432251

2252+
def _expand_text_search_aliases(self, text: str) -> List[str]:
2253+
normalized = text.strip()
2254+
if not normalized:
2255+
return []
2256+
lowered = normalized.lower()
2257+
aliases = {
2258+
"xxe": [
2259+
"XXE",
2260+
"XML External Entity",
2261+
"XML External Entity Reference",
2262+
],
2263+
"xss": [
2264+
"XSS",
2265+
"Cross Site Scripting",
2266+
"Cross-Site Scripting",
2267+
],
2268+
"ssrf": [
2269+
"SSRF",
2270+
"Server Side Request Forgery",
2271+
"Server-Side Request Forgery",
2272+
],
2273+
"csrf": [
2274+
"CSRF",
2275+
"Cross Site Request Forgery",
2276+
"Cross-Site Request Forgery",
2277+
],
2278+
"rce": [
2279+
"RCE",
2280+
"Remote Code Execution",
2281+
],
2282+
}
2283+
expanded = [normalized]
2284+
expanded.extend(aliases.get(lowered, []))
2285+
deduped = []
2286+
seen = set()
2287+
for entry in expanded:
2288+
key = entry.lower()
2289+
if key in seen:
2290+
continue
2291+
seen.add(key)
2292+
deduped.append(entry)
2293+
return deduped
2294+
22442295
def get_root_cres(self):
22452296
"""Returns CRES that only have "Contains" links"""
22462297
# select name from cre where cre.id not in (select cre from cre_links where type="Contains") and cre.id not in (select "group" from cre_links where type="Is Part OF");
@@ -2289,7 +2340,7 @@ def health_check(self) -> Dict[str, Any]:
22892340
"db_reachable": False,
22902341
"reason": "database unreachable",
22912342
}
2292-
except Exception: # pragma: no cover - defensive, never fail open
2343+
except SQLAlchemyError: # pragma: no cover - defensive, never fail open
22932344
return {
22942345
"ok": False,
22952346
"db_reachable": False,

application/tests/cwe_parser_test.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,32 @@ def iter_content(self, chunk_size=None):
267267
deserialization_cre.todict(),
268268
)
269269

270+
@patch.object(requests, "get")
271+
def test_register_CWE_skips_prohibited_entries(self, mock_requests) -> None:
272+
tmpdir = mkdtemp()
273+
tmpFile = os.path.join(tmpdir, "cwe.xml")
274+
tmpzip = os.path.join(tmpdir, "cwe.zip")
275+
with open(tmpFile, "w") as cx:
276+
cx.write(self.CWE_prohibited_xml)
277+
with zipfile.ZipFile(tmpzip, "w", zipfile.ZIP_DEFLATED) as zipf:
278+
zipf.write(tmpFile, arcname="cwe.xml")
279+
280+
class fakeRequest:
281+
def iter_content(self, chunk_size=None):
282+
with open(tmpzip, "rb") as zipf:
283+
return [zipf.read()]
284+
285+
mock_requests.return_value = fakeRequest()
286+
287+
entries = cwe.CWE().parse(
288+
cache=self.collection,
289+
ph=prompt_client.PromptHandler(database=self.collection),
290+
)
291+
imported_cwes = {node.sectionID: node for node in entries.results["CWE"]}
292+
293+
self.assertIn("611", imported_cwes)
294+
self.assertNotIn("9999", imported_cwes)
295+
270296
CWE_xml = """<?xml version="1.0" encoding="UTF-8"?>
271297
<Weakness_Catalog Name="CWE" Version="4.10" Date="2023-01-31" xmlns="http://cwe.mitre.org/cwe-6"
272298
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -739,4 +765,17 @@ def iter_content(self, chunk_size=None):
739765
</Weakness>
740766
</Weaknesses>
741767
</Weakness_Catalog>
768+
"""
769+
770+
CWE_prohibited_xml = """<?xml version="1.0" encoding="UTF-8"?>
771+
<Weakness_Catalog Name="CWE" Version="4.10" Date="2023-01-31" xmlns="http://cwe.mitre.org/cwe-6">
772+
<Weaknesses>
773+
<Weakness ID="611" Name="Improper Restriction of XML External Entity Reference" Abstraction="Base" Structure="Simple" Status="Draft">
774+
<Description>Allowed XXE entry.</Description>
775+
</Weakness>
776+
<Weakness ID="9999" Name="Prohibited Example" Abstraction="Base" Structure="Simple" Status="PROHIBITED">
777+
<Description>This entry should not be imported.</Description>
778+
</Weakness>
779+
</Weaknesses>
780+
</Weakness_Catalog>
742781
"""

application/tests/db_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,33 @@ def test_text_search(self) -> None:
10541054
res = self.collection.text_search(k)
10551055
self.assertCountEqual(res, val)
10561056

1057+
def test_text_search_expands_xxe_aliases(self) -> None:
1058+
xxe_cre = defs.CRE(
1059+
id="764-507",
1060+
name="Restrict XML parsing (against XXE)",
1061+
)
1062+
cwe_611 = defs.Standard(
1063+
name="CWE",
1064+
section="Improper Restriction of XML External Entity Reference",
1065+
sectionID="611",
1066+
hyperlink="https://cwe.mitre.org/data/definitions/611.html",
1067+
)
1068+
1069+
self.collection.add_cre(xxe_cre)
1070+
self.collection.add_node(cwe_611)
1071+
1072+
results = self.collection.text_search("XXE")
1073+
1074+
self.assertIn("764-507", [doc.id for doc in results if doc.doctype == "CRE"])
1075+
self.assertTrue(
1076+
any(
1077+
doc.doctype == "Standard"
1078+
and doc.name == "CWE"
1079+
and doc.sectionID == "611"
1080+
for doc in results
1081+
)
1082+
)
1083+
10571084
def test_dbNodeFromNode(self) -> None:
10581085
data = {
10591086
"tool": defs.Tool(

application/tests/noise_filter/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)