@@ -91,9 +91,10 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
9191 """
9292 t0 = time .perf_counter ()
9393
94- # Auto-detect script type if unknown
95- if scan_input .script_type == ScriptType .UNKNOWN :
96- scan_input .script_type = self ._detect_type (scan_input .script_content )
94+ # Auto-detect script type if unknown (local copy — don't mutate the input)
95+ effective_script_type = scan_input .script_type
96+ if effective_script_type == ScriptType .UNKNOWN :
97+ effective_script_type = self ._detect_type (scan_input .script_content )
9798
9899 # Build effective scan content: script + command-line args (if any)
99100 script = scan_input .script_content
@@ -160,7 +161,7 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
160161 blocklist_detail = (" Blocklist pattern matched — denied." if blocklist_hit else " Denied for safety." )
161162 return SafetyScanReport (
162163 tool_name = scan_input .tool_name ,
163- script_type = scan_input . script_type ,
164+ script_type = effective_script_type ,
164165 script_size_lines = script_lines ,
165166 decision = Decision .DENY ,
166167 risk_level = RiskLevel .CRITICAL if blocklist_hit else RiskLevel .HIGH ,
@@ -177,10 +178,10 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
177178 # ══════════════════════════════════════════════════════════════
178179 # LAYER 1 & 2: AST-based Python + shlex-based Bash scanning
179180 # ══════════════════════════════════════════════════════════════
180- if scan_input . script_type in (ScriptType .PYTHON , ScriptType .UNKNOWN ):
181+ if effective_script_type in (ScriptType .PYTHON , ScriptType .UNKNOWN ):
181182 all_findings .extend (self ._scan_python_ast (script , scan_input ))
182183
183- if scan_input . script_type in (ScriptType .BASH , ScriptType .UNKNOWN ):
184+ if effective_script_type in (ScriptType .BASH , ScriptType .UNKNOWN ):
184185 all_findings .extend (self ._scan_bash_tokens (script , scan_input ))
185186
186187 # ══════════════════════════════════════════════════════════════
@@ -223,7 +224,7 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
223224
224225 # Apply blocklist override — blocklist patterns always → deny
225226 if decision != Decision .DENY :
226- decision , bl_findings = self ._check_blocklist_override (script , decision , scan_input . script_type )
227+ decision , bl_findings = self ._check_blocklist_override (script , decision , effective_script_type )
227228 all_findings .extend (bl_findings )
228229 # Blocklist commands — also force DENY when a forbidden command literal appears
229230 if decision != Decision .DENY and self ._policy .blocklist_commands :
@@ -287,7 +288,7 @@ def scan(self, scan_input: SafetyScanInput) -> SafetyScanReport:
287288
288289 return SafetyScanReport (
289290 tool_name = scan_input .tool_name ,
290- script_type = scan_input . script_type ,
291+ script_type = effective_script_type ,
291292 script_size_lines = script_lines ,
292293 decision = decision ,
293294 risk_level = max_risk ,
@@ -976,15 +977,29 @@ def _deduplicate_findings(findings: List[SafetyFinding]) -> List[SafetyFinding]:
976977
977978
978979def _extract_url (text : str ) -> Optional [str ]:
979- """Naive domain extractor from a line of text — used for whitelist checks."""
980- m = re .search (r"https?://([^\s/\"':]+)" , text )
980+ """Extract a domain name from *text* for whitelist checks.
981+
982+ Strips user:pass@ prefixes so that ``localhost:8080@evil.com`` is
983+ correctly identified as *evil.com* rather than being mistaken for a
984+ whitelisted localhost.
985+ """
986+ # Match the full authority portion including :port and @userinfo,
987+ # then strip userinfo and port to get the bare hostname.
988+ m = re .search (r"https?://([^\s/\"']+)" , text )
981989 if m :
982- return m .group (1 )
990+ host = m .group (1 )
991+ if "@" in host :
992+ host = host .rsplit ("@" , 1 )[- 1 ]
993+ if ":" in host :
994+ host = host .rsplit (":" , 1 )[0 ]
995+ return host
983996 m = re .search (r"(?:^|\s)((?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,})" , text )
984997 if m :
985998 candidate = m .group (0 ).strip ()
986999 if "(" in candidate or candidate .startswith ("." ):
9871000 return None
1001+ if "@" in candidate :
1002+ candidate = candidate .rsplit ("@" , 1 )[- 1 ]
9881003 return candidate
9891004 return None
9901005
0 commit comments