@@ -198,6 +198,17 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
198198 all_findings .extend (findings )
199199 except Exception : # pylint: disable=broad-except
200200 logger .error ("Safety rule raised an exception; skipping: %s" , str (getattr (rule , "__class__" , rule )))
201+ all_findings .append (
202+ SafetyFinding (
203+ rule_id = "GLOBAL-003" ,
204+ category = RiskCategory .RESOURCE_ABUSE ,
205+ risk_level = RiskLevel .MEDIUM ,
206+ evidence = "A safety rule crashed during scanning." ,
207+ message = f"Rule { getattr (rule , '__class__' , rule )} failed — scan may be incomplete." ,
208+ recommendation = "Review the script manually; automated analysis was partial." ,
209+ line_number = 0 ,
210+ matched_pattern = "" ,
211+ ))
201212
202213 # Check environment variables against blocklist
203214 if scan_input .environment_variables :
@@ -270,8 +281,12 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
270281 # Also refuses to upgrade when any CRITICAL finding exists, regardless
271282 # of how the policy maps CRITICAL → decision.
272283 has_critical = any (f .risk_level == RiskLevel .CRITICAL for f in all_findings )
284+ has_high_or_critical = any (f .risk_level in (RiskLevel .HIGH , RiskLevel .CRITICAL ) for f in all_findings )
273285 allow_upgraded = False
274- if (decision == Decision .NEEDS_HUMAN_REVIEW and not has_critical and self ._check_allow_patterns (script )):
286+ # Only upgrade MEDIUM or lower (NEEDS_HUMAN_REVIEW) — never upgrade HIGH/CRITICAL
287+ # even if the policy maps them to NEEDS_HUMAN_REVIEW.
288+ if (decision == Decision .NEEDS_HUMAN_REVIEW and not has_high_or_critical
289+ and self ._check_allow_patterns (script )):
275290 logger .info ("allow_patterns upgraded NEEDS_HUMAN_REVIEW → ALLOW for '%s'" , scan_input .tool_name )
276291 decision = Decision .ALLOW
277292 allow_upgraded = True
@@ -321,8 +336,9 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
321336 )
322337
323338 def reload_policy (self ) -> None :
324- """Reload the policy from disk (useful for hot-reload)."""
339+ """Reload the policy and rules from disk (useful for hot-reload)."""
325340 self ._policy = reload_policy ()
341+ self ._rules = get_all_rules ()
326342
327343 # ------------------------------------------------------------------
328344 # Layer 1: AST-based Python scanning
0 commit comments