Skip to content

Commit 35e3db3

Browse files
committed
refactor: apply minimal code principles across backend modules
1 parent 88a92cd commit 35e3db3

11 files changed

Lines changed: 418 additions & 246 deletions

backend/secuscan/cache.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
from typing import Any, Optional, Dict
33
import time
4+
import heapq
45
import logging
56

67
from .config import settings
@@ -29,19 +30,20 @@ async def disconnect(self):
2930

3031
def _sweep_expired(self):
3132
now = time.time()
32-
keys = [k for k, exp in list(self._expires.items()) if exp <= now]
33+
keys = [k for k, exp in self._expires.items() if exp <= now]
3334
for k in keys:
3435
self._data.pop(k, None)
3536
self._expires.pop(k, None)
3637
self._access_order.pop(k, None)
3738
if keys:
39+
logger.debug("Swept %d expired cache entries", len(keys))
3840

3941
def _evict_lru(self):
4042
if len(self._data) < self.max_entries:
4143
return
42-
sorted_keys = sorted(self._access_order, key=lambda k: self._access_order[k])
4344
evict_count = max(1, int(self.max_entries * SWEEP_EVICT_FRACTION))
44-
for k in sorted_keys[:evict_count]:
45+
lru_keys = heapq.nsmallest(evict_count, self._access_order, key=self._access_order.get)
46+
for k in lru_keys:
4547
self._data.pop(k, None)
4648
self._expires.pop(k, None)
4749
self._access_order.pop(k, None)

backend/secuscan/executor.py

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,11 +1166,9 @@ async def _build_result_contract(
11661166
structured_result["finding_groups"] = build_finding_groups(normalized_findings)
11671167
structured_result["asset_summary"] = build_asset_summary(normalized_findings, asset_services)
11681168
structured_result["scan_diff"] = build_scan_diff(normalized_findings, previous_findings)
1169-
severity_counts: Dict[str, int] = {}
1170-
for f in normalized_findings:
1171-
sev = str(f.get("severity", "info")).lower()
1172-
severity_counts[sev] = severity_counts.get(sev, 0) + 1
1173-
structured_result["severity_counts"] = severity_counts
1169+
from collections import Counter
1170+
severity_counts = Counter(str(f.get("severity", "info")).lower() for f in normalized_findings)
1171+
structured_result["severity_counts"] = dict(severity_counts)
11741172
structured_result["count"] = len(normalized_findings)
11751173
return structured_result, previous_findings, asset_services
11761174
@@ -1299,20 +1297,104 @@ async def _upsert_findings_and_report(self, db, task_id: str, owner_id: str, plu
12991297
)
13001298
findings_data: List[Dict[str, Any]] = []
13011299
async with db.transaction():
1300+
insert_sql = """INSERT INTO findings (
1301+
id, owner_id, task_id, plugin_id, title, category, severity, target, description,
1302+
remediation, proof, cvss, cve, metadata_json, discovered_at, exploitability,
1303+
confidence, validated, validation_method, confidence_reason, finding_kind,
1304+
finding_group_id, asset_id, first_seen_at, last_seen_at, occurrence_count,
1305+
corroborating_sources_json, evidence_count, analyst_status, retest_status,
1306+
evidence_json, asset_refs_json, service_fingerprint, cpe, references_json,
1307+
asset_exposure, risk_score, risk_factors_json
1308+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1309+
ON CONFLICT(owner_id, finding_group_id) DO UPDATE SET
1310+
occurrence_count = occurrence_count + 1,
1311+
last_seen_at = excluded.last_seen_at,
1312+
evidence_count = excluded.evidence_count,
1313+
corroborating_sources_json = excluded.corroborating_sources_json"""
1314+
1315+
batch_data = []
13021316
for finding in structured_result.get("findings", []):
1303-
findings_data.append(
1304-
await self._persist_finding(
1305-
db,
1306-
owner_id=owner_id,
1307-
task_id=task_id,
1308-
plugin_id=plugin_id,
1309-
target=target,
1310-
finding=finding,
1311-
)
1317+
finding_id = generate_id("finding")
1318+
discovered = datetime.now(timezone.utc)
1319+
target_value = str(finding.get("target") or target)
1320+
metadata = finding.get("metadata", {}) if isinstance(finding.get("metadata"), dict) else {}
1321+
evidence = finding.get("evidence", []) if isinstance(finding.get("evidence"), list) else []
1322+
asset_refs = finding.get("asset_refs", []) if isinstance(finding.get("asset_refs"), list) else []
1323+
exploitability = finding.get("exploitability")
1324+
asset_exposure = finding.get("asset_exposure")
1325+
confidence = finding.get("confidence")
1326+
finding_group_id = self._compute_finding_group_id(
1327+
owner_id=owner_id,
1328+
target=target_value,
1329+
title=finding["title"],
1330+
category=finding["category"],
1331+
severity=finding["severity"],
1332+
host=finding.get("host"),
1333+
port=finding.get("port"),
1334+
path=finding.get("path"),
1335+
cve=finding.get("cve"),
1336+
)
1337+
references = finding.get("references", []) if isinstance(finding.get("references"), list) else []
1338+
corroborating_sources = finding.get("corroborating_sources", []) if isinstance(finding.get("corroborating_sources"), list) else []
1339+
first_seen_at = str(finding.get("first_seen_at") or to_utc_iso(discovered))
1340+
last_seen_at = str(finding.get("last_seen_at") or to_utc_iso(discovered))
1341+
occurrence_count = int(finding.get("occurrence_count") or 1)
1342+
evidence_count = int(finding.get("evidence_count") or len(evidence))
1343+
risk_score = compute_risk_score(
1344+
severity=finding["severity"],
1345+
exploitability=exploitability,
1346+
asset_exposure=asset_exposure,
1347+
discovered_at=discovered,
1348+
confidence=confidence,
13121349
)
1350+
risk_factors = compute_risk_factors(
1351+
severity=finding["severity"],
1352+
exploitability=exploitability,
1353+
asset_exposure=asset_exposure,
1354+
discovered_at=discovered,
1355+
confidence=confidence,
1356+
risk_score=risk_score,
1357+
)
1358+
1359+
batch_data.append((
1360+
finding_id, owner_id, task_id, plugin_id, finding["title"], finding["category"],
1361+
finding["severity"], target_value, finding["description"], finding.get("remediation", ""),
1362+
finding.get("proof"), finding.get("cvss"), finding.get("cve"), json.dumps(metadata),
1363+
to_utc_iso(discovered), exploitability, confidence, 1 if finding.get("validated") else 0,
1364+
finding.get("validation_method"), finding.get("confidence_reason"),
1365+
str(finding.get("finding_kind") or "observation"), finding_group_id, finding.get("asset_id"),
1366+
first_seen_at, last_seen_at, occurrence_count, json.dumps(corroborating_sources),
1367+
evidence_count, str(finding.get("analyst_status") or "new"),
1368+
str(finding.get("retest_status") or "not_requested"), json.dumps(evidence),
1369+
json.dumps(asset_refs), finding.get("service_fingerprint"), finding.get("cpe"),
1370+
json.dumps(references), asset_exposure, risk_score, json.dumps(risk_factors)
1371+
))
1372+
1373+
findings_data.append({
1374+
**finding,
1375+
"id": finding_id,
1376+
"plugin_id": plugin_id,
1377+
"target": target_value,
1378+
"discovered_at": to_utc_iso(discovered),
1379+
"metadata": metadata,
1380+
"evidence": evidence,
1381+
"asset_refs": asset_refs,
1382+
"references": references,
1383+
"corroborating_sources": corroborating_sources,
1384+
"first_seen_at": first_seen_at,
1385+
"last_seen_at": last_seen_at,
1386+
"occurrence_count": occurrence_count,
1387+
"evidence_count": evidence_count,
1388+
"risk_score": risk_score,
1389+
"risk_factors": risk_factors,
1390+
})
1391+
1392+
if batch_data:
1393+
await db.executemany(insert_sql, batch_data)
13131394

13141395
structured_result["findings"] = findings_data
1315-
structured_result["severity_counts"] = self._build_severity_counts(findings_data)
1396+
from collections import Counter
1397+
structured_result["severity_counts"] = dict(Counter(str(f.get("severity", "info")).lower() for f in findings_data))
13161398
structured_result["finding_groups"] = build_finding_groups(findings_data)
13171399
structured_result["asset_summary"] = build_asset_summary(findings_data, asset_services)
13181400
structured_result["scan_diff"] = build_scan_diff(findings_data, previous_findings)

backend/secuscan/finding_intelligence.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,12 @@ def _dedupe_evidence(items: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
196196
unique: List[Dict[str, Any]] = []
197197
seen = set()
198198
for item in items:
199-
key = json.dumps(
200-
{
201-
"type": item.get("type"),
202-
"label": item.get("label"),
203-
"value": item.get("value"),
204-
"artifact_ref": item.get("artifact_ref"),
205-
"source": item.get("source"),
206-
},
207-
sort_keys=True,
208-
default=str,
199+
key = (
200+
item.get("type"),
201+
item.get("label"),
202+
str(item.get("value")),
203+
item.get("artifact_ref"),
204+
item.get("source"),
209205
)
210206
if key in seen:
211207
continue
@@ -372,11 +368,18 @@ async def normalize_and_correlate_findings(
372368
staged_item["corroborating_sources"] = sorted({str(s).strip() for s in [*staged_item.get("corroborating_sources", [] if str(s).strip()}), *sources])
373369
staged_item["metadata"].update({key: value for key, value in (finding.get("metadata") or {}).items() if value not in ("", None, [], {})})
374370

371+
all_group_ids = list(staged.keys())
372+
previous_map = {}
373+
if all_group_ids:
374+
previous_rows = await db.fetchall(
375+
f"SELECT * FROM findings WHERE owner_id = ? AND finding_group_id IN ({','.join('?' * len(all_group_ids))})",
376+
(owner_id, *all_group_ids)
377+
)
378+
previous_map = {row["finding_group_id"]: row for row in previous_rows}
379+
375380
normalized: List[Dict[str, Any]] = []
376381
for finding_group_id, finding in staged.items():
377-
previous = await db.fetchone(
378-
(owner_id, finding_group_id),
379-
)
382+
previous = previous_map.get(finding_group_id)
380383
prior_sources = []
381384
if previous and previous.get("corroborating_sources_json"):
382385
try:
@@ -414,7 +417,7 @@ async def normalize_and_correlate_findings(
414417

415418
if settings.triage_engine_enabled and settings.triage_engine_api_key:
416419
try:
417-
triage_engine.triage_findings(
420+
await triage_engine.triage_findings_async(
418421
normalized,
419422
model=settings.triage_engine_model,
420423
api_key=settings.triage_engine_api_key,

backend/secuscan/ratelimit.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from collections import defaultdict
1+
from collections import defaultdict, deque
22
from datetime import datetime, timedelta
3-
from typing import Tuple, Dict, List
3+
from typing import Tuple, Dict
44
import asyncio
55

66
from fastapi import Request, Response, HTTPException
@@ -9,7 +9,7 @@
99

1010
class RateLimiter:
1111
def __init__(self):
12-
self.task_history: Dict[str, List[datetime]] = defaultdict(list)
12+
self.task_history: Dict[str, deque] = defaultdict(lambda: deque())
1313
self.lock = asyncio.Lock()
1414

1515
async def can_execute(
@@ -24,28 +24,24 @@ async def can_execute(
2424
now = datetime.now()
2525
hour_ago = now - timedelta(hours=1)
2626

27-
# Clean old entries for this bucket
28-
self.task_history[bucket] = [
29-
ts for ts in self.task_history[bucket]
30-
if ts > hour_ago
31-
]
27+
history = self.task_history[bucket]
28+
while history and history[0] <= hour_ago:
29+
history.popleft()
3230

33-
recent_count = len(self.task_history[bucket])
31+
recent_count = len(history)
3432

3533
if recent_count >= max_per_hour:
3634
return False, f"Rate limit exceeded: {recent_count}/{max_per_hour} per hour"
3735

38-
# Record this execution
39-
self.task_history[bucket].append(now)
36+
history.append(now)
4037
return True, ""
4138

4239
async def reset(self, plugin_id: str = None):
4340
async with self.lock:
4441
if plugin_id:
45-
# Remove every bucket that ends with :<plugin_id>
4642
keys_to_clear = [k for k in self.task_history if k.endswith(f":{plugin_id}")]
4743
for k in keys_to_clear:
48-
self.task_history[k] = []
44+
self.task_history[k].clear()
4945
else:
5046
self.task_history.clear()
5147

backend/secuscan/routes.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,8 +1045,6 @@ async def get_findings(
10451045
page: int = Query(1, ge=1),
10461046
per_page: int = Query(50, ge=1, le=200),
10471047
):
1048-
# Return the caller's vulnerability findings with pagination.
1049-
10501048
async def build():
10511049
db = await get_db()
10521050
offset = (page - 1) * per_page
@@ -1060,22 +1058,21 @@ async def build():
10601058
)
10611059
total = total_row["count"] if total_row else 0
10621060
findings = deserialize_finding_rows(rows)
1063-
# Build finding_groups from *all* findings so group counts remain accurate
1064-
# regardless of which page is being viewed.
1065-
all_rows = await db.fetchall(
1066-
"SELECT * FROM findings WHERE owner_id = ? ORDER BY discovered_at DESC",
1061+
1062+
group_rows = await db.fetchall(
1063+
"SELECT finding_group_id, COUNT(*) as count FROM findings WHERE owner_id = ? GROUP BY finding_group_id",
10671064
(owner,),
10681065
)
1069-
all_findings = deserialize_finding_rows(all_rows)
1066+
finding_groups = [{"id": row["finding_group_id"], "count": row["count"]} for row in group_rows]
1067+
10701068
return {
10711069
"findings": findings,
1072-
"finding_groups": build_finding_groups(all_findings),
1070+
"finding_groups": finding_groups,
10731071
"total": total,
10741072
"page": page,
10751073
"per_page": per_page,
10761074
}
10771075

1078-
# Cache key includes pagination params so different pages do not collide.
10791076
return await get_or_set_cached(f"findings:list:{owner}:page={page}:per_page={per_page}", build)
10801077

10811078

0 commit comments

Comments
 (0)