Skip to content

Commit 5197f3b

Browse files
devatsecureclaude
andcommitted
fix: Address 5 bugs from Cursor Bugbot code review (#33)
- epss_scorer.py: Reorder HTTPError before URLError (subclass ordering) - config_loader.py: Remove duplicate dast_auth_config_path/dast_enable_totp keys - compliance_mapper.py: Compute mappings once in generate_all_reports (6x perf) - error_classifier.py: Run pattern matching before isinstance fallback so FileNotFoundError/PermissionError are classified correctly, not as transient - dast_orchestrator.py: Catch all exceptions from YAML config loading Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a5af1b2 commit 5197f3b

5 files changed

Lines changed: 28 additions & 31 deletions

File tree

scripts/compliance_mapper.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,12 @@ def generate_report(
474474
)
475475

476476
all_mappings = self.map_findings(findings)
477+
return self._build_report(framework, all_mappings)
477478

479+
def _build_report(
480+
self, framework: str, all_mappings: list[ComplianceMapping]
481+
) -> ComplianceReport:
482+
"""Build a report for *framework* from pre-computed mappings."""
478483
# Filter mappings for the requested framework.
479484
framework_mappings = [
480485
m for m in all_mappings if m.framework == framework
@@ -507,16 +512,19 @@ def generate_all_reports(
507512
) -> list[ComplianceReport]:
508513
"""Generate compliance reports for every configured framework.
509514
515+
Computes mappings once and reuses across all frameworks.
516+
510517
Args:
511518
findings: list of finding dictionaries.
512519
513520
Returns:
514521
A list of :class:`ComplianceReport` instances, one per framework.
515522
"""
516-
reports = []
517-
for framework in self.frameworks:
518-
report = self.generate_report(findings, framework)
519-
reports.append(report)
523+
all_mappings = self.map_findings(findings)
524+
reports = [
525+
self._build_report(framework, all_mappings)
526+
for framework in self.frameworks
527+
]
520528
logger.info("Generated %d compliance reports", len(reports))
521529
return reports
522530

scripts/config_loader.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,6 @@ def get_default_config() -> Dict[str, Any]:
142142
"temporal_namespace": "argus",
143143
"temporal_retry_mode": "production",
144144

145-
# -- DAST auth --
146-
"dast_auth_config_path": "", # path to YAML auth config
147-
"dast_enable_totp": True,
148-
149145
# -- Vulnerability enrichment & compliance --
150146
"enable_license_risk_scoring": True,
151147
"enable_epss_scoring": True,

scripts/dast_orchestrator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def __init__(self, config: Optional[OrchestratorConfig] = None):
105105
self.dast_auth.login_type,
106106
self.dast_auth.login_url or "(none)",
107107
)
108-
except (FileNotFoundError, ValueError) as exc:
108+
except Exception as exc:
109109
logger.warning("DAST auth config load failed: %s", exc)
110110

111111
# Initialize agents

scripts/epss_scorer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,10 @@ def _fetch_batch(self, cve_ids: list[str]) -> dict[str, EPSSScore]:
361361
len(cve_ids),
362362
)
363363

364-
except urllib.error.URLError as e:
365-
logger.warning("EPSS API network error: %s", e)
366364
except urllib.error.HTTPError as e:
367365
logger.warning("EPSS API HTTP error %d: %s", e.code, e.reason)
366+
except urllib.error.URLError as e:
367+
logger.warning("EPSS API network error: %s", e)
368368
except json.JSONDecodeError as e:
369369
logger.warning("EPSS API returned invalid JSON: %s", e)
370370
except (OSError, ValueError, TypeError) as e:

scripts/error_classifier.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -217,26 +217,9 @@ def classify_llm_error(
217217
if status_code is not None:
218218
context["status_code"] = status_code
219219

220-
# Also check for common exception types directly
221-
if isinstance(error, (ConnectionError, OSError)):
222-
return ClassifiedError(
223-
error_type=ERROR_TYPE_TRANSIENT,
224-
retryable=True,
225-
original=error,
226-
context=context,
227-
provider=provider,
228-
)
229-
230-
if isinstance(error, TimeoutError):
231-
return ClassifiedError(
232-
error_type=ERROR_TYPE_TRANSIENT,
233-
retryable=True,
234-
original=error,
235-
context=context,
236-
provider=provider,
237-
)
238-
239-
# Pattern matching against known error types
220+
# Pattern matching against known error types (checked first so that
221+
# specific patterns like "permission denied" or "no such file" are
222+
# classified correctly before the broad isinstance fallback).
240223
for error_type, patterns, retryable in _PATTERN_REGISTRY:
241224
for pattern in patterns:
242225
if pattern in combined:
@@ -248,6 +231,16 @@ def classify_llm_error(
248231
provider=provider,
249232
)
250233

234+
# Fallback: network-level connection errors are transient
235+
if isinstance(error, ConnectionError):
236+
return ClassifiedError(
237+
error_type=ERROR_TYPE_TRANSIENT,
238+
retryable=True,
239+
original=error,
240+
context=context,
241+
provider=provider,
242+
)
243+
251244
# Fail-safe: unknown errors are classified as permanent (not retryable)
252245
return ClassifiedError(
253246
error_type=ERROR_TYPE_PERMANENT,

0 commit comments

Comments
 (0)