Skip to content

Commit ad337e2

Browse files
committed
Say why curated rules skipped, grouped, in the scan's engine row
The premium curated rules row said only how many skipped. It now says why, grouped: how many wanted the out of band callback, a second account, a mapped repo, and so on. It also counts the enabled rules that were not in this profile's rulesets, which is the usual reason a premium depth scan and a saved profile see a different number of rules. An admin can now tell at a glance what to set up to turn a skip into a run.
1 parent 7976b13 commit ad337e2

1 file changed

Lines changed: 25 additions & 5 deletions

File tree

‎app/core/external.py‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,15 +2092,18 @@ def _run_curated_rules(scan_id, asset_id, url, wb, session, opts, rulesets) -> t
20922092
from app.core.models import DastRule, ExternalAsset
20932093

20942094
specs, domains = [], []
2095+
excluded = invalid = 0
20952096
second_headers = None
20962097
with session_scope() as db:
20972098
for row in db.query(DastRule).filter(DastRule.enabled.is_(True)).all():
20982099
if not profiles.selected(row.rule_class, row.rulesets, rulesets):
2099-
continue # not in a ruleset this profile runs
2100+
excluded += 1 # enabled, but not in a ruleset this profile runs
2101+
continue
21002102
try:
21012103
specs.append(spec_mod.validate(json.loads(row.spec or "{}")))
21022104
except (ValueError, spec_mod.SpecError):
2103-
continue # a bad stored rule is skipped, never run
2105+
invalid += 1 # a bad stored rule is skipped, never run
2106+
continue
21042107
domains = scope.company_domains(
21052108
settings_mod.get(db, "web.company_domains") or [])
21062109
oob = callback.for_scan(db)
@@ -2133,8 +2136,11 @@ def _run_curated_rules(scan_id, asset_id, url, wb, session, opts, rulesets) -> t
21332136
"code_routes": list(wb.get("routes") or []),
21342137
"headers": headers or None, "second_headers": second_headers,
21352138
"budget": 60, "oob": oob}
2139+
from collections import Counter
2140+
21362141
started = time.monotonic()
21372142
findings, ran, skipped, confirmed = [], 0, 0, 0
2143+
skip_reasons = Counter()
21382144
for spec in specs:
21392145
if scan_id in _stopping:
21402146
break
@@ -2145,13 +2151,27 @@ def _run_curated_rules(scan_id, asset_id, url, wb, session, opts, rulesets) -> t
21452151
confirmed += len(result["findings"])
21462152
elif result["status"] == "skipped":
21472153
skipped += 1
2154+
skip_reasons[result.get("detail") or "a prerequisite was not met"] += 1
21482155
else:
21492156
log.info("curated rule %s: %s - %s", spec.get("id"),
21502157
result["status"], result.get("detail"))
21512158
row["seconds"] = int(time.monotonic() - started)
2152-
row["detail"] = (f"{ran} rule{'' if ran == 1 else 's'} ran, "
2153-
f"{confirmed} finding{'' if confirmed == 1 else 's'}, "
2154-
f"{skipped} skipped")
2159+
detail = (f"{ran} rule{'' if ran == 1 else 's'} ran, "
2160+
f"{confirmed} finding{'' if confirmed == 1 else 's'}, "
2161+
f"{skipped} skipped")
2162+
# Say why they skipped, grouped, so an admin can see what to set up.
2163+
if skip_reasons:
2164+
detail += " (" + "; ".join(f"{n} {reason}" for reason, n
2165+
in skip_reasons.most_common()) + ")"
2166+
notes = []
2167+
if excluded:
2168+
notes.append(f"{excluded} enabled rule{'' if excluded == 1 else 's'} "
2169+
"not in this profile's rulesets")
2170+
if invalid:
2171+
notes.append(f"{invalid} could not be read")
2172+
if notes:
2173+
detail += ". " + "; ".join(notes)
2174+
row["detail"] = detail
21552175
return findings, row
21562176

21572177

0 commit comments

Comments
 (0)