@@ -287,3 +287,149 @@ def test_is_root_domain_helper(self):
287287 # Invalid domains - should return False
288288 assert watchdog ._is_root_domain ('example' ) is False
289289 assert watchdog ._is_root_domain ('' ) is False
290+
291+
292+ class TestUrlProhibitlistSecurity :
293+ """Tests for URL prohibitlist (blocked domains) behavior and matching semantics."""
294+
295+ def test_simple_prohibited_domains (self ):
296+ """Domain-only patterns block exact host and www, but not other subdomains."""
297+ from bubus import EventBus
298+
299+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
300+
301+ browser_profile = BrowserProfile (prohibited_domains = ['example.com' , 'test.org' ], headless = True , user_data_dir = None )
302+ browser_session = BrowserSession (browser_profile = browser_profile )
303+ event_bus = EventBus ()
304+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
305+
306+ # Block exact and www
307+ assert watchdog ._is_url_allowed ('https://example.com' ) is False
308+ assert watchdog ._is_url_allowed ('https://www.example.com' ) is False
309+ assert watchdog ._is_url_allowed ('https://test.org' ) is False
310+ assert watchdog ._is_url_allowed ('https://www.test.org' ) is False
311+
312+ # Allow other subdomains when only root is prohibited
313+ assert watchdog ._is_url_allowed ('https://mail.example.com' ) is True
314+ assert watchdog ._is_url_allowed ('https://api.test.org' ) is True
315+
316+ # Allow unrelated domains
317+ assert watchdog ._is_url_allowed ('https://notexample.com' ) is True
318+
319+ def test_glob_pattern_prohibited (self ):
320+ """Wildcard patterns block subdomains and main domain for http/https only."""
321+ from bubus import EventBus
322+
323+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
324+
325+ browser_profile = BrowserProfile (prohibited_domains = ['*.example.com' ], headless = True , user_data_dir = None )
326+ browser_session = BrowserSession (browser_profile = browser_profile )
327+ event_bus = EventBus ()
328+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
329+
330+ # Block subdomains and main domain
331+ assert watchdog ._is_url_allowed ('https://example.com' ) is False
332+ assert watchdog ._is_url_allowed ('https://www.example.com' ) is False
333+ assert watchdog ._is_url_allowed ('https://mail.example.com' ) is False
334+
335+ # Allow other domains
336+ assert watchdog ._is_url_allowed ('https://notexample.com' ) is True
337+
338+ # Wildcard with domain-only should not apply to non-http(s)
339+ assert watchdog ._is_url_allowed ('chrome://abc.example.com' ) is True
340+
341+ def test_full_url_prohibited_patterns (self ):
342+ """Full URL patterns block only matching scheme/host/prefix."""
343+ from bubus import EventBus
344+
345+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
346+
347+ browser_profile = BrowserProfile (prohibited_domains = ['https://wiki.org' , 'brave://*' ], headless = True , user_data_dir = None )
348+ browser_session = BrowserSession (browser_profile = browser_profile )
349+ event_bus = EventBus ()
350+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
351+
352+ # Scheme-specific blocking
353+ assert watchdog ._is_url_allowed ('http://wiki.org' ) is True
354+ assert watchdog ._is_url_allowed ('https://wiki.org' ) is False
355+ assert watchdog ._is_url_allowed ('https://wiki.org/path' ) is False
356+
357+ # Internal URL prefix blocking
358+ assert watchdog ._is_url_allowed ('brave://anything/' ) is False
359+ assert watchdog ._is_url_allowed ('chrome://settings' ) is True
360+
361+ def test_internal_urls_allowed_even_when_prohibited (self ):
362+ """Internal new-tab/blank URLs are always allowed regardless of prohibited list."""
363+ from bubus import EventBus
364+
365+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
366+
367+ browser_profile = BrowserProfile (prohibited_domains = ['*' ], headless = True , user_data_dir = None )
368+ browser_session = BrowserSession (browser_profile = browser_profile )
369+ event_bus = EventBus ()
370+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
371+
372+ assert watchdog ._is_url_allowed ('about:blank' ) is True
373+ assert watchdog ._is_url_allowed ('chrome://new-tab-page/' ) is True
374+ assert watchdog ._is_url_allowed ('chrome://new-tab-page' ) is True
375+ assert watchdog ._is_url_allowed ('chrome://newtab/' ) is True
376+
377+ def test_prohibited_ignored_when_allowlist_present (self ):
378+ """When allowlist is set, prohibited list is ignored by design."""
379+ from bubus import EventBus
380+
381+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
382+
383+ browser_profile = BrowserProfile (
384+ allowed_domains = ['*.example.com' ],
385+ prohibited_domains = ['https://example.com' ],
386+ headless = True ,
387+ user_data_dir = None ,
388+ )
389+ browser_session = BrowserSession (browser_profile = browser_profile )
390+ event_bus = EventBus ()
391+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
392+
393+ # Allowed by allowlist even though exact URL is in prohibited list
394+ assert watchdog ._is_url_allowed ('https://example.com' ) is True
395+ assert watchdog ._is_url_allowed ('https://www.example.com' ) is True
396+
397+ # Not in allowlist => blocked (prohibited list is not consulted in this mode)
398+ assert watchdog ._is_url_allowed ('https://api.example.com' ) is True # wildcard allowlist includes this
399+ # A domain outside the allowlist should be blocked
400+ assert watchdog ._is_url_allowed ('https://notexample.com' ) is False
401+
402+ def test_auth_credentials_do_not_cause_false_block (self ):
403+ """Credentials injection with prohibited domain in username should not block unrelated hosts."""
404+ from bubus import EventBus
405+
406+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
407+
408+ browser_profile = BrowserProfile (prohibited_domains = ['example.com' ], headless = True , user_data_dir = None )
409+ browser_session = BrowserSession (browser_profile = browser_profile )
410+ event_bus = EventBus ()
411+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
412+
413+ # Host is malicious.com, should not be blocked just because username contains example.com
414+ assert watchdog .
_is_url_allowed (
'https://example.com:[email protected] ' )
is True 415+ assert watchdog .
_is_url_allowed (
'https://[email protected] ' )
is True 416+ assert watchdog .
_is_url_allowed (
'https://example.com%[email protected] ' )
is True 417+ assert watchdog .
_is_url_allowed (
'https://example.com%[email protected] ' )
is True 418+
419+ # Legitimate credentials to a prohibited host should be blocked
420+ assert watchdog .
_is_url_allowed (
'https://user:[email protected] ' )
is False 421+
422+ def test_case_insensitive_prohibited_domains (self ):
423+ """Prohibited domain matching should be case-insensitive."""
424+ from bubus import EventBus
425+
426+ from browser_use .browser .watchdogs .security_watchdog import SecurityWatchdog
427+
428+ browser_profile = BrowserProfile (prohibited_domains = ['Example.COM' ], headless = True , user_data_dir = None )
429+ browser_session = BrowserSession (browser_profile = browser_profile )
430+ event_bus = EventBus ()
431+ watchdog = SecurityWatchdog (browser_session = browser_session , event_bus = event_bus )
432+
433+ assert watchdog ._is_url_allowed ('https://example.com' ) is False
434+ assert watchdog ._is_url_allowed ('https://WWW.EXAMPLE.COM' ) is False
435+ assert watchdog ._is_url_allowed ('https://mail.example.com' ) is True
0 commit comments