State guardrails allow you to control which URLs the agent can visit during execution. You provide a callback function that inspects the browser state after each observation and decides whether to allow or block continued execution.
Overview
State guardrails are useful for:
Preventing navigation to unauthorized domains
Blocking access to sensitive pages
Enforcing compliance requirements
Implementing URL allowlists and blocklists
Protecting against prompt injection attacks
How guardrails work
After each browser observation, Nova Act calls your guardrail function with the current browser state. Your function returns either PASS (allow execution to continue) or BLOCK (stop execution and raise an error).
from nova_act import NovaAct, GuardrailDecision, GuardrailInputState
def my_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
# Inspect state.browser_url
if is_safe_url(state.browser_url):
return GuardrailDecision. PASS
return GuardrailDecision. BLOCK
with NovaAct(
starting_page = "https://example.com" ,
state_guardrail = my_guardrail
) as nova:
nova.act( "Navigate to the products page" )
If the guardrail returns BLOCK, Nova Act raises an ActStateGuardrailError and stops execution.
The GuardrailInputState object contains information about the current browser state:
The current URL of the browser page
from nova_act import GuardrailInputState
from urllib.parse import urlparse
def log_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
print ( f "Current URL: { state.browser_url } " )
parsed = urlparse(state.browser_url)
print ( f "Hostname: { parsed.hostname } " )
print ( f "Path: { parsed.path } " )
return GuardrailDecision. PASS
GuardrailDecision
Your guardrail function must return one of two values:
Allow execution to continue
Stop execution and raise ActStateGuardrailError
from nova_act import GuardrailDecision
# Allow
return GuardrailDecision. PASS
# Block
return GuardrailDecision. BLOCK
Examples
URL blocklist
Block specific domains:
from nova_act import NovaAct, GuardrailDecision, GuardrailInputState
from urllib.parse import urlparse
import fnmatch
def url_blocklist_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
hostname = urlparse(state.browser_url).hostname
if not hostname:
return GuardrailDecision. BLOCK
# Block these domains
blocked = [
"*.malicious-site.com" ,
"*.blocked-domain.com" ,
"dangerous-site.org"
]
for pattern in blocked:
if fnmatch.fnmatch(hostname, pattern):
return GuardrailDecision. BLOCK
return GuardrailDecision. PASS
with NovaAct(
starting_page = "https://example.com" ,
state_guardrail = url_blocklist_guardrail
) as nova:
nova.act( "Navigate to the homepage" )
URL allowlist
Only allow specific domains:
def url_allowlist_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
hostname = urlparse(state.browser_url).hostname
if not hostname:
return GuardrailDecision. BLOCK
# Only allow these domains
allowed = [
"example.com" ,
"*.example.com" ,
"trusted-partner.com"
]
for pattern in allowed:
if fnmatch.fnmatch(hostname, pattern):
return GuardrailDecision. PASS
# Block everything else
return GuardrailDecision. BLOCK
Hybrid approach
Combine allowlist and blocklist:
def hybrid_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
hostname = urlparse(state.browser_url).hostname
if not hostname:
return GuardrailDecision. BLOCK
# Explicit blocklist takes precedence
blocked = [ "*.blocked.com" , "dangerous.org" ]
for pattern in blocked:
if fnmatch.fnmatch(hostname, pattern):
return GuardrailDecision. BLOCK
# Then check allowlist
allowed = [ "*.example.com" , "*.trusted.com" ]
for pattern in allowed:
if fnmatch.fnmatch(hostname, pattern):
return GuardrailDecision. PASS
# Block everything else
return GuardrailDecision. BLOCK
Path-based restrictions
Restrict access to specific paths:
def path_restriction_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
parsed = urlparse(state.browser_url)
# Block admin pages
if parsed.path.startswith( "/admin/" ):
return GuardrailDecision. BLOCK
# Block API endpoints
if parsed.path.startswith( "/api/" ):
return GuardrailDecision. BLOCK
# Block sensitive paths
sensitive_paths = [ "/billing" , "/settings/security" , "/private" ]
if any (parsed.path.startswith(p) for p in sensitive_paths):
return GuardrailDecision. BLOCK
return GuardrailDecision. PASS
Error handling
When a guardrail blocks execution, an ActStateGuardrailError is raised:
from nova_act import NovaAct, ActStateGuardrailError
try :
with NovaAct(
starting_page = "https://example.com" ,
state_guardrail = url_allowlist_guardrail
) as nova:
nova.act( "Navigate to the unauthorized page" )
except ActStateGuardrailError as e:
print ( f "Guardrail blocked navigation: { e } " )
# Log incident, alert security team, etc.
Best practices
Design considerations
Design guardrails to be fast. They’re called after every observation, so slow guardrails will impact performance.
Keep it simple : Guardrails should make quick decisions
Be explicit : Use explicit allowlists rather than implicit blocking
Log violations : Log when guardrails block actions for security audits
Test thoroughly : Test guardrails with various URL patterns
Security recommendations
Guardrails are a defense-in-depth measure, not a replacement for proper input validation and prompt design.
Combine with other security measures : Use guardrails alongside SecurityOptions and prompt validation
Start restrictive : Begin with strict allowlists and relax as needed
Monitor and adjust : Review guardrail logs to identify needed adjustments
Handle edge cases : Consider URLs with unusual formats, redirects, etc.
import fnmatch
from functools import lru_cache
# Cache pattern matching for better performance
@lru_cache ( maxsize = 1000 )
def matches_pattern ( hostname : str , pattern : str ) -> bool :
return fnmatch.fnmatch(hostname, pattern)
def optimized_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
hostname = urlparse(state.browser_url).hostname
if not hostname:
return GuardrailDecision. BLOCK
blocked = [ "*.blocked.com" , "*.dangerous.org" ]
for pattern in blocked:
if matches_pattern(hostname, pattern):
return GuardrailDecision. BLOCK
return GuardrailDecision. PASS
Testing guardrails
Test your guardrails independently:
from nova_act import GuardrailInputState, GuardrailDecision
def test_url_allowlist ():
# Test allowed URLs
state = GuardrailInputState( browser_url = "https://example.com/page" )
assert url_allowlist_guardrail(state) == GuardrailDecision. PASS
# Test blocked URLs
state = GuardrailInputState( browser_url = "https://blocked.com/page" )
assert url_allowlist_guardrail(state) == GuardrailDecision. BLOCK
# Test edge cases
state = GuardrailInputState( browser_url = "" )
assert url_allowlist_guardrail(state) == GuardrailDecision. BLOCK
test_url_allowlist()
print ( "All guardrail tests passed!" )
Common patterns
Multi-stage guardrail
def multi_stage_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
"""Implement multiple stages of validation."""
# Stage 1: Check for empty/invalid URLs
if not state.browser_url:
return GuardrailDecision. BLOCK
parsed = urlparse(state.browser_url)
# Stage 2: Protocol check
if parsed.scheme not in [ "http" , "https" ]:
return GuardrailDecision. BLOCK
# Stage 3: Domain validation
if not parsed.hostname:
return GuardrailDecision. BLOCK
# Stage 4: Domain allowlist
allowed_domains = [ "*.example.com" , "*.trusted.org" ]
if not any (fnmatch.fnmatch(parsed.hostname, p) for p in allowed_domains):
return GuardrailDecision. BLOCK
# Stage 5: Path restrictions
if parsed.path.startswith( "/admin/" ):
return GuardrailDecision. BLOCK
return GuardrailDecision. PASS
Logging guardrail
import logging
logger = logging.getLogger( __name__ )
def logging_guardrail ( state : GuardrailInputState) -> GuardrailDecision:
"""Log all URLs visited for audit purposes."""
logger.info( f "Guardrail check: { state.browser_url } " )
hostname = urlparse(state.browser_url).hostname
if not is_allowed_domain(hostname):
logger.warning( f "Blocked navigation to: { state.browser_url } " )
return GuardrailDecision. BLOCK
logger.info( f "Allowed navigation to: { state.browser_url } " )
return GuardrailDecision. PASS
Troubleshooting
Guardrail blocking legitimate navigation
Check your patterns:
Verify wildcard patterns with fnmatch.fnmatch()
Test with actual URLs from your workflow
Consider subdomain matching (e.g., *.example.com vs example.com)
Guardrail not blocking malicious URLs
Ensure:
Patterns are comprehensive (consider all variations)
Blocklist is checked before allowlist
Default behavior is to block (fail closed)
Next steps
Security guide Learn about other security features
SecurityOptions File access control and security settings
Error handling Handle ActStateGuardrailError
API reference Complete guardrail API documentation