Octopus
Security and Sandboxing
Browser automation is a high-risk capability. The WebDriverPlugin enforces a network domain allowlist, headless-only mode in production, and content extraction limits to prevent abuse and SSRF-style attacks.
Threat Model
| Threat | Risk | Control |
|---|---|---|
| SSRF via browser navigation | Agent directed to http://169.254.169.254 (cloud metadata) or internal services | Domain allowlist blocks non-approved domains |
| Prompt injection via page content | Malicious page injects instructions into extracted text that hijack agent behaviour | Extraction size limits; prefer structured selectors over full-page text |
| Data exfiltration via screenshot | Screenshot captures sensitive data displayed in the browser | Operator must review which agents have WebDriver tools assigned |
| Long-running browser resource drain | Agent opens many sessions, exhausting server memory | Session limits: MaxIdleSeconds, MaxSessionDurationSeconds, per-tenant session cap |
| Non-headless browser on server | GUI process fails to start or exposes display server | Headless: true is enforced; startup fails if set to false in non-dev environments |
Domain Allowlist Implementation
public class DomainPolicy
{
private readonly IReadOnlyList<string> _allowedPatterns;
public DomainPolicy(WebDriverPluginConfig config)
{
_allowedPatterns = config.AllowedDomains;
}
public bool IsAllowed(string url)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
return false;
// Always block localhost and RFC-1918 private ranges
if (IsPrivateOrLoopback(uri.Host))
return false;
// Check against wildcard patterns
return _allowedPatterns.Any(pattern => MatchesPattern(uri.Host, pattern));
}
private static bool MatchesPattern(string host, string pattern)
{
if (pattern.StartsWith("*."))
{
var suffix = pattern[1..]; // e.g., ".company.com"
return host.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)
|| host.Equals(suffix[1..], StringComparison.OrdinalIgnoreCase);
}
return host.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}
private static bool IsPrivateOrLoopback(string host)
{
if (host == "localhost" || host == "127.0.0.1" || host == "::1") return true;
if (IPAddress.TryParse(host, out var ip))
{
// Block 10.x.x.x, 172.16.x.x - 172.31.x.x, 192.168.x.x, 169.254.x.x
var bytes = ip.GetAddressBytes();
if (bytes[0] == 10) return true;
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) return true;
if (bytes[0] == 192 && bytes[1] == 168) return true;
if (bytes[0] == 169 && bytes[1] == 254) return true;
}
return false;
}
}
Headless Enforcement
// In OnStartAsync — fail fast if headless is false in non-dev environment
public async Task OnStartAsync(IServiceProvider sp, CancellationToken ct)
{
var env = sp.GetRequiredService<IWebHostEnvironment>();
var config = sp.GetRequiredService<IOptions<WebDriverPluginConfig>>().Value;
if (!config.Headless && !env.IsDevelopment())
{
throw new InvalidOperationException(
"WebDriverPlugin: Headless must be true outside Development environment. " +
"Set WebDriverPlugin:Headless to true in appsettings.Production.json.");
}
}
AllowedDomains must be configured before production deployment. An empty allowlist blocks all navigation. A poorly specified allowlist allows SSRF. Review the allowlist with your security team before deploying agents that use browser tools.
Per-Tenant Session Limits
{
"WebDriverPlugin": {
"MaxSessionsPerTenant": 5,
"MaxGlobalSessions": 50
}
}
When a tenant's session count reaches MaxSessionsPerTenant, the oldest idle session for that tenant is closed before opening a new one. If the global session cap is reached, new browser_navigate calls return an error until a session expires.