Browser-Use handles sensitive operations including browser automation, credential management, and API keys. This guide covers security best practices and features.
- Sensitive Data Management
- API Key Protection
- Domain Restrictions
- Browser Security
- Network Security
- Logging and Telemetry
- Production Deployment
- Security Checklist
Browser-Use provides secure credential handling through the sensitive_data option:
import{Agent}from'browser-use';constagent=newAgent({task: 'Login to the dashboard',
llm,sensitive_data: {// Domain-scoped credentials'*.example.com': {username: 'user@example.com',password: 'secure-password-123',},// Global credentials (available on all domains)api_key: 'sk-secret-key',},});- Masking in Logs: Sensitive values are automatically masked in logs and conversation history
- Domain Scoping: Domain-scoped credentials are only available on matching domains
- Secret Placeholders: Use
<secret>key</secret>pattern in prompts to reference credentials - Memory Isolation: Sensitive data is not included in LLM context
- Unlocked Sensitive Data Warning: Browser-Use warns when
sensitive_datais configured withoutallowed_domains
If sensitive_data is provided without allowed_domains, Agent construction continues but logs a security warning. For production credentialed tasks, configure allowed_domains so navigation is locked to trusted domains.
constagent=newAgent({task: 'Log in and fetch invoices',
llm,sensitive_data: {password: 'secret'},browser_session: newBrowserSession({browser_profile: newBrowserProfile({allowed_domains: ['example.com','*.example.com'],}),}),});constsensitiveData={// Exact domain match'example.com': { ... },// Wildcard subdomain'*.example.com': { ... },// Matches app.example.com, api.example.com, etc.// Multiple domains (use separate entries)'site1.com': { ... },'site2.com': { ... },// Global (no domain prefix)'global_api_key': 'value'};// In custom actions, check for sensitive data availabilitycontroller.registry.action('Login with credentials',{param_model: z.object({username_field: z.number(),password_field: z.number(),}),})(asyncfunctionlogin(params,ctx){if(!ctx.has_sensitive_data){returnnewActionResult({error: 'No credentials configured for this domain',});}// Credentials are automatically injected based on current domain// The LLM uses <secret>username</secret> patternreturnnewActionResult({extracted_content: 'Login attempted',});});Always use environment variables for API keys:
# .env file (never commit!)
OPENAI_API_KEY=sk-your-openai-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
GOOGLE_API_KEY=your-google-keyimport'dotenv/config';import{ChatOpenAI}from'browser-use/llm/openai';constllm=newChatOpenAI({model: 'gpt-4o',apiKey: process.env.OPENAI_API_KEY,// Never hardcode!});If using the config file (~/.config/browseruse/config.json):
Set proper permissions:
chmod 600 ~/.config/browseruse/config.jsonUse environment variable references:
{ "llm": { "openai": { "api_key": "${OPENAI_API_KEY}" } } }
For Claude Desktop, use environment variable references:
{
"mcpServers": {
"browser-use": {
"command": "npx",
"args": ["browser-use", "--mcp"],
"env": {
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
}
}
}
}Restrict browser navigation to specific domains:
# Environment variable
BROWSER_USE_ALLOWED_DOMAINS=*.example.com,*.trusted.org,api.mysite.com// Programmatic configurationconstprofile=newBrowserProfile({allowed_domains: ['*.example.com','*.trusted.org','api.mysite.com'],});# CLI
npx browser-use --allowed-domains "*.example.com,*.trusted.org" -p "Complete login flow"Avoid running production credentialed tasks without allowed_domains; unlocked sensitive_data is warning-only and does not stop navigation by itself.
| Pattern | Matches |
|---|---|
example.com | Only example.com |
*.example.com | Any subdomain of example.com |
*.*.example.com | Two-level subdomains |
* | All domains (default) |
Restrict custom actions to specific domains:
controller.registry.action('Perform admin action',{param_model: z.object({ ... }),allowed_domains: ['admin.example.com','dashboard.example.com']})(asyncfunctionadmin_action(params,ctx){// Only available on admin.example.com and dashboard.example.com});Always enable the Chromium sandbox in production:
constprofile=newBrowserProfile({chromium_sandbox: true,// Default: true});If Chromium cannot launch with sandboxing (for example, restricted Linux CI/AppArmor
environments), browser-use retries once with chromium_sandbox: false and logs a warning.
Treat this warning as a deployment hardening signal, not as a normal steady state.
For Docker/CI, you may need to disable sandboxing explicitly (with appropriate container security):
constprofile=newBrowserProfile({chromium_sandbox: false,// Only in Dockerargs: ['--no-sandbox','--disable-setuid-sandbox'],});Use headless mode in production:
constprofile=newBrowserProfile({headless: true,});Or via environment variable:
BROWSER_USE_HEADLESS=trueconstprofile=newBrowserProfile({// Keep security features enabled (defaults)disable_security: false,ignore_https_errors: false,// Stealth mode (avoid detection but maintain security)stealth: true,});These options should only be used in development/testing:
// DANGEROUS - Only for testing!constprofile=newBrowserProfile({disable_security: true,// Disables web securityignore_https_errors: true,// Accepts invalid certificates});Use proxies for network isolation:
constprofile=newBrowserProfile({proxy: {server: 'http://proxy.internal:8080',bypass: 'localhost,127.0.0.1',// Bypass for localusername: 'proxy-user',password: 'proxy-pass',},});constprofile=newBrowserProfile({// Reject invalid HTTPS certificatesignore_https_errors: false,// Custom headers for securityextra_http_headers: {'Strict-Transport-Security': 'max-age=31536000',},});For advanced request filtering, use Playwright's route API:
constsession=newBrowserSession({browser_profile: profile});awaitsession.start();constpage=awaitsession.get_current_page();// Block requests to untrusted domainsawaitpage.route('**/*',(route)=>{consturl=newURL(route.request().url());constallowedDomains=['example.com','trusted.org'];if(allowedDomains.some((d)=>url.hostname.endsWith(d))){route.continue();}else{route.abort();}});Control logging verbosity:
# Minimal logging in production
BROWSER_USE_LOGGING_LEVEL=warning
# Options: debug, info, warning, errorBrowser-Use automatically masks sensitive data in logs:
INFO [agent] Filling field with <MASKED>
INFO [agent] Navigating to https://example.com/login?token=<MASKED>
Disable telemetry if needed:
ANONYMIZED_TELEMETRY=falseTelemetry data collected (when enabled):
- Tool usage counts
- Session durations
- Success/failure rates
- Model/provider information (no content)
Not collected:
- URLs visited
- Page content
- Credentials or sensitive data
- Personal information
FROM node:20-slim
# Run as non-root userRUN useradd -m -s /bin/bash appuser
# Install dependenciesRUN apt-get update && apt-get install -y \
chromium \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Set environmentENV BROWSER_USE_HEADLESS=true
ENV IN_DOCKER=true
# Copy applicationWORKDIR /app
COPY --chown=appuser:appuser . .
# Switch to non-root userUSER appuser
# Install dependenciesRUN npm ci --production
CMD ["node", "dist/index.js"]apiVersion: v1kind: Podmetadata:
name: browser-usespec:
securityContext:
runAsNonRoot: truerunAsUser: 1000containers:
- name: browser-useimage: your-imagesecurityContext:
allowPrivilegeEscalation: falsereadOnlyRootFilesystem: truecapabilities:
drop:
- ALLenv:
- name: OPENAI_API_KEYvalueFrom:
secretKeyRef:
name: api-keyskey: openai
- name: BROWSER_USE_HEADLESSvalue: 'true'resources:
limits:
memory: '2Gi'cpu: '1'Use secret management services:
// AWS Secrets Manager exampleimport{SecretsManager}from'@aws-sdk/client-secrets-manager';asyncfunctiongetApiKey(){constclient=newSecretsManager({region: 'us-east-1'});constsecret=awaitclient.getSecretValue({SecretId: 'browser-use/api-keys',});returnJSON.parse(secret.SecretString!);}constsecrets=awaitgetApiKey();constllm=newChatOpenAI({model: 'gpt-4o',apiKey: secrets.OPENAI_API_KEY,});- Use
.envfiles for API keys (add to.gitignore) - Never commit credentials to version control
- Use domain restrictions for testing
- Review logs for sensitive data leakage
- Enable headless mode
- Configure proxy if needed
- Test with production-like security settings
- Verify domain restrictions work correctly
- Use environment variables or secret management
- Enable Chromium sandbox (or use secure containers)
- Set
BROWSER_USE_HEADLESS=true - Configure domain restrictions
- Disable telemetry if required by policy
- Set appropriate log levels
- Use HTTPS only
- Run as non-root user
- Implement network segmentation
- Regular security audits
- No hardcoded credentials
- Sensitive data uses masking patterns
- Custom actions have appropriate domain restrictions
- Error messages don't leak sensitive information
- File operations are properly sandboxed
If you discover a security vulnerability:
- Do not open a public GitHub issue
- Email security concerns to the maintainers privately
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
Security issues will be addressed promptly and credited appropriately.