Browserious - Browser API Wrapper
Self-hosted, API-controlled browser-as-a-service for automation, testing, and web scraping.
Overview
Browserious provides a REST API + WebSocket interface to control Chromium browsers running in Docker containers. Each browser session runs in an isolated container with:
- Persistent Profiles - Maintain cookies, localStorage, and session state
- Proxy Support - Configure HTTP/HTTPS/SOCKS5 proxies per session
- Extensions - Load custom Chrome extensions
- Request Interception - Block, redirect, or mock network requests
- Remote Viewing - Watch browser sessions via VNC
- CDP Access - Direct Chrome DevTools Protocol control
Primary Use Cases:
- Browser automation and E2E testing
- Web scraping and data extraction
- Remote browser service
Quick Start
Prerequisites
- Docker and Docker Compose
- Python 3.11+ (for development)
Installation
# Clone repository
git clone https://github.com/muzhig/browserious.git
cd browserious
# Generate API key
export API_KEYS=$(openssl rand -base64 32)
# Start service
docker-compose up -d
# Verify
curl -H "X-API-Key: $API_KEYS" http://localhost:8100/sessions
Create Your First Session
# Create browser session
SESSION_ID=$(curl -X POST http://localhost:8100/sessions \
-H "X-API-Key: $API_KEYS" \
-H "Content-Type: application/json" \
-d '{"timeout": 600}' \
| jq -r '.session_id')
# Navigate to a page
curl -X POST http://localhost:8100/sessions/$SESSION_ID/pages/page-0/goto \
-H "X-API-Key: $API_KEYS" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
# Take screenshot
curl -H "X-API-Key: $API_KEYS" \
http://localhost:8100/sessions/$SESSION_ID/pages/page-0/screenshot \
> screenshot.png
# Watch in browser (VNC)
# Open the web_vnc_url from the session response, e.g.:
# http://localhost:8100/vnc.html?session=$SESSION_ID
# Cleanup
curl -X DELETE http://localhost:8100/sessions/$SESSION_ID \
-H "X-API-Key: $API_KEYS"
Architecture
┌─────────────────────────────────────────────────────────┐
│ API Container (browserious-api) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ FastAPI Application :8100 │ │
│ │ - REST API + WebSocket │ │
│ │ - VNC WebSocket proxy /sessions/{id}/vnc │ │
│ │ - Web VNC viewer /vnc.html?session={id} │ │
│ │ - Docker API (creates/manages browser nodes) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ Docker Network: browserious
▼
┌─────────────────────────────────────────────────────────┐
│ Browser Node Containers (one per session) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ session-abc123 │ │ session-xyz789 │ ... │
│ │ │ │ │ │
│ │ Xvfb :99 │ │ Xvfb :99 │ │
│ │ x11vnc :5900 │ │ x11vnc :5900 │ │
│ │ Chromium │ │ Chromium │ │
│ │ CDP :9222 │ │ CDP :9222 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ (No port conflicts - each node uses same internal │
│ ports, addressed by container hostname/IP) │
└─────────────────────────────────────────────────────────┘
Features
✅ Session Management
- Create isolated browser sessions
- Configure proxy, viewport, user agent
- Persistent profiles with cookie/storage management
- Automatic timeout and cleanup
- Restart-safe session cleanup with durable retry state and ownership-scoped orphan reporting
✅ Page Operations
- Navigate, click, type, fill forms
- Multiple selector strategies (CSS, XPath, Playwright, JS)
- Wait for elements (visibility, loading states)
- Execute JavaScript in page context
- Screenshots (full page or viewport)
- HTML content extraction
✅ Request Interception
- Block requests (ads, trackers, images)
- Redirect URLs (mock APIs, override media)
- Override responses (status, headers, body)
- Inspect and capture requests
✅ Extensions
- Upload custom Chrome extensions
- Load extensions per session
- Support for .zip and .crx formats
✅ Remote Access
- VNC server with web client (noVNC)
- Per-session password protection
- Real-time browser viewing
✅ CDP WebSocket
- Direct Chrome DevTools Protocol access
- Compatible with Puppeteer/Playwright
- Full browser control for advanced use cases
Documentation
| Document | Description |
|---|---|
| PRD | Product Requirements Document - features, use cases, MVP scope |
| ADR | Architecture Decision Records - key technical decisions and rationale |
| API | Complete API Reference - endpoints, examples, error codes |
| EXAMPLES | Code examples - form interaction, content extraction, visual debugging |
| SECURITY | Security Analysis - threat model, hardening, best practices |
| Project Brief | Original project specification |
Browserious CLI and MCP
The browserious wheel contains the complete CLI, shared OAuth client, and optional MCP compatibility adapter. The CLI is the primary interface.
Setup
Install:
pip install browserious
browserious login
browserious whoami
browserious session create
For an isolated run use uvx browserious --help. Pin a release for reproducible automation; because uvx caches environments, add --refresh when it must resolve the requested release again:
uvx --refresh --from browserious==0.2.0 browserious --version
browserious login uses device authorization and stores rotating OAuth credentials. Use --no-browser on a headless host. CLI/MCP browser commands use authenticated shared REST operations and share server-owned sessions by ID without a process-local registry. The local tunnel owner alone requests a short-lived tunnel capability. Run browserious --help and browserious GROUP --help for the installed wheel's normative command inventory; the API guide lists all command groups, output and exit semantics, tunnels, and MCP parity.
The CLI defaults to human output; --json or --output json emits compact JSON. Errors go to stderr. Secrets are recursively redacted unless --show-secrets is used on one of the four bounded commands that permit it (session create, file get, profile get, profile export). Exit codes classify success (0), local validation (2), auth (3), forbidden (4), not found (5), conflict (6), rate limit (7), transport/server (8), other HTTP (9), and interrupted foreground tunnel (130).
Open a foreground localhost tunnel with:
browserious tunnel open SESSION_ID --local-port 3000
The command owns its WebSocket until interrupted. --detach transfers ownership to the per-user daemon; inspect it with browserious tunnel list and stop it with browserious daemon stop. Detached tunnels require Unix-domain sockets, while foreground tunnels remain available on unsupported daemon platforms.
Local MCP compatibility
Run MCP explicitly from the same wheel with browserious mcp. It uses the CLI credential store, refresh lifecycle, capability client, operations, scopes, and revocation path. A pinned local host configuration is:
{
"mcpServers": {
"browserious": {
"command": "uvx",
"args": ["--refresh", "--from", "browserious==0.2.1", "browserious", "mcp"],
"env": {
"BROWSERIOUS_API_URL": "https://api.browserious.com"
}
}
}
}
Credential precedence is --access-token, BROWSERIOUS_ACCESS_TOKEN, legacy BROWSERIOUS_API_KEY, then the stored login. Normal interactive setup uses device login rather than pasted br_live_* tokens. The 0.2.0 compatibility release contains exactly two deprecated, transitional bridges: python -m browserious_mcp, and zero-argument browserious when both standard streams are non-TTY. Interactive zero-argument invocation shows CLI help. Eventual MCP retirement removes the verb, bridges, browserious_mcp module, MCP dependencies, parity tests, and MCP docs from this wheel; it does not create or migrate to a second package. No later removal version is promised.
In the next release after 0.2.1, local stdio MCP closes sessions it successfully
created when stdin reaches EOF or it receives SIGTERM/SIGINT on POSIX. Sessions
created by CLI commands stay persistent. Listing, attaching to, or using another
process's session never makes it owned by the local MCP process. To keep a session
across MCP runs, create it with browserious session create and use its ID in MCP.
The published 0.2.1 package does not yet perform this shutdown cleanup.
Local shutdown starts all owned-session DELETEs concurrently, with a 30-second limit per request, independently of any pending creates. Pending creates have one second from EOF/SIGTERM to return their session IDs; successful late responses join the same cleanup exactly once. An ID that does not reach the client within that drain window remains subject to the original TTL/reaper fallback and is reported as a cleanup failure. It retains the exact API origin and credential that created each session; it never substitutes a later login for cleanup. An expired or revoked creation credential can therefore prevent immediate deletion. Failures are reported to stderr using hashed session identifiers, error classes and HTTP status codes, without credentials, URLs or response bodies; a completed shutdown with cleanup failures exits nonzero.
MCP hosts may allow less time: the Python SDK currently waits two seconds after EOF, then sends SIGTERM and SIGKILL about two seconds apart. Slow provider deletion can outlive that budget. An authorized API DELETE durably expires the session and records its deletion reason before slow teardown, so the API/reaper can finish after the MCP process exits. SIGKILL, Windows process termination, network failure, or a kill before creation/DELETE is acknowledged cannot run or guarantee cleanup. Sessions without a recorded DELETE remain subject to their original timeout and the server's reaper interval/backoff. No immediate hard-kill cleanup is promised. Remote stateless HTTP MCP retains its existing persistent-session semantics; request completion and shared API shutdown do not confer session deletion ownership.
Optional remote MCP
The same bundled adapter is also available over stateless Streamable HTTP at https://api.browserious.com/mcp. Remote clients discover the shared Browserious authorization server, dynamically register as public clients, and complete authorization-code authentication with S256 PKCE. They request the existing browser:read browser:write tunnel scopes and use the same rotating br_at_* and br_rt_* token families as other Browserious clients.
Discovery starts from the 401 challenge's RFC 9728 resource_metadata URL, https://api.browserious.com/.well-known/oauth-protected-resource/mcp. That document identifies the resource and authorization server; the client then reads https://api.browserious.com/.well-known/oauth-authorization-server, registers at the advertised endpoint, and uses the advertised authorization/token/revocation endpoints with the exact redirect URI and S256 verifier.
Claude Code can add the remote endpoint with:
claude mcp add --transport http browserious https://api.browserious.com/mcp
Claude Code configuration was sanitized and probed against this URL. Cursor and Windsurf onboarding remains manual-only; no completed client-specific OAuth flow is claimed. If a client cannot complete remote OAuth, use local stdio. Remote and stdio enumerate the same tools and call the same operations. Remote open_tunnel and close_tunnel fail safely because a service-host process cannot own a client-local port; use the CLI or local stdio MCP for tunnels.
Public DNS is Cloudflare Anycast/proxied, and requests traverse Cloudflare → Caddy → FastAPI. The local 192.168.1.3 address is an intentional host/VPN shortcut. Node callbacks send the explicit Browserious-Node/0.2.0 service User-Agent to avoid Cloudflare error 1010. This topology makes no unverified streaming timeout promise.
Available Tools
The wheel currently exposes 23 MCP tools. Runtime enumeration and the installed-wheel parity check are normative, not this descriptive count. Every tool has a CLI equivalent:
| Tool | Description |
|---|---|
create_session |
Create browser session, returns session_id and URLs |
delete_session |
Close and cleanup session |
list_sessions |
List all active sessions |
create_page |
Open new tab, optionally navigate to URL |
page_goto |
Navigate to URL |
page_click |
Click element (CSS, XPath, text, role, JS selectors) |
page_type |
Type text with keyboard events |
page_fill |
Fill input instantly |
page_eval |
Execute JavaScript, return result |
page_wait_for_selector |
Wait for element state |
page_content |
Get full HTML |
page_screenshot |
Capture page, return a reference plus metadata (opt into bytes with inline) |
display_screenshot |
Capture X11 display with cursor, same reference-first contract |
play_input |
Hardware-level mouse/keyboard via X11 |
get_cursor_position |
Get current cursor screen coordinates |
convert_coordinate |
Convert a viewport/page point to screen space, and return the offset for local batch conversion |
click_element |
Click an element with hardware input, resolved by selector — no calibration needed |
element_at_point |
Identify the element at viewport coordinates |
captcha_detect |
Detect CAPTCHA regions |
open_tunnel |
Open a client-owned localhost tunnel (local transports only) |
close_tunnel |
Close a client-owned localhost tunnel (local transports only) |
list_files |
List stored files |
get_file |
Return owner-scoped metadata and a redacted-by-default presigned download URL |
delete_file |
Delete a stored file |
Screenshot handling
CLI screenshot commands write image bytes to --output-file or browserious-output.bin.
MCP screenshot tools return a reference plus metadata instead of an inline image, so a large capture cannot flood a caller's context:
{"session_id": "sess-1", "page_id": "page-0", "format": "png", "size_bytes": 454931,
"width": 1920, "height": 4200, "path": "/tmp/browserious-screenshots/sess-1-page-0.png",
"path_scope": "mcp-client-host", "file_id": null, "retrievable": true, "inline": false,
"hint": "Local stdio MCP: ...", "warnings": []}
inline=true returns FastMCP image content, but only below BROWSERIOUS_MCP_INLINE_MAX_BYTES
(default 1 MiB; a value that is not a byte count is ignored); above it the tool raises a structured
error that still carries the reference. path resolves on the MCP server's host — the caller's own
machine over local stdio (BROWSERIOUS_SCREENSHOT_DIR overrides the directory), and null over
remote MCP, where get_file(file_id) is the way to the bytes. Each capture target reuses one stable
path, and the directory keeps only the most recent captures, so a verification loop cannot fill the
disk. When no path and no file_id can be produced, the tool still returns the reference with
retrievable: false and a hint naming the prerequisites, rather than discarding a capture that
already succeeded. Secret-bearing JSON fields remain redacted by default.
Fonts are required for meaningful screenshots. A browser container without fonts renders
invisible text, producing a structurally correct and completely worthless capture; installing
fonts-dejavu-core and fontconfig grew a real text-heavy capture roughly 15x. This is a property
of whichever environment renders the page — Browserious' own node image inherits fonts from its
Playwright base, so the risk is a browser container you build or run yourself. The signal is
relative, not absolute: a correct but sparse page (a login form, an empty state) is within ~1.2x of
a fontless full-page render in bytes per pixel, so size alone cannot diagnose it. Compare
size_bytes for the same page before and after installing fonts.
Coordinate System
Hardware input (play_input) addresses the X11 display, not the browser viewport. You do not
have to derive that offset yourself — the server does it.
To click an element, use click_element: it resolves the selector, converts the geometry, and plays
an eased move + press/release, all server-side.
browserious input click-element sess-1 "textarea[name='q']"
To convert points yourself — for drags, custom playlists, or batching — use input coordinate. It
returns the converted point and the offset, so N targets cost one call:
browserious input coordinate sess-1 640 400
{"x": 650, "y": 497, "offset": {"x": 10, "y": 97}, "on_screen": true,
"geometry": {"screen_x": 10, "screen_y": 10, "inner_width": 1919, "inner_height": 992,
"chrome_height": 87, "device_pixel_ratio": 1, "...": "..."}}
Then convert further points locally as screen = offset + viewport_point. on_screen reports
whether a point is actually clickable — inside both the viewport and the display.
The offset derives from window geometry as (screenX + chromeWidth // 2, screenY + chromeHeight),
which measures (10, 97) on a default 1920x1080 node. Deriving it by hand is discouraged: the
endpoint refuses rather than guessing when it cannot calibrate (e.g. devicePixelRatio != 1),
whereas hand-rolled arithmetic silently mis-clicks.
API Examples
Web Scraping with Proxy
import requests
API_KEY = "your-api-key"
BASE_URL = "http://localhost:8100"
headers = {"X-API-Key": API_KEY}
# Create session with proxy
session = requests.post(
f"{BASE_URL}/sessions",
headers=headers,
json={
"profile_id": "scraper-001",
"proxy": {"server": "http://proxy.example.com:8080"},
"timeout": 3600
}
).json()
session_id = session["session_id"]
# Block images to speed up scraping
requests.post(
f"{BASE_URL}/sessions/{session_id}/routes",
headers=headers,
json={
"pattern": "**/*.{png,jpg,jpeg,gif}",
"action": {"type": "block"}
}
)
# Navigate and extract data
requests.post(
f"{BASE_URL}/sessions/{session_id}/pages/page-0/goto",
headers=headers,
json={"url": "https://example.com"}
)
data = requests.post(
f"{BASE_URL}/sessions/{session_id}/pages/page-0/eval",
headers=headers,
json={"script": "return document.querySelectorAll('.item').length"}
).json()
print(f"Found {data['result']} items")
# Cleanup
requests.delete(f"{BASE_URL}/sessions/{session_id}", headers=headers)
E2E Testing with Mocked API
# Create test session
session = requests.post(
f"{BASE_URL}/sessions",
headers=headers,
json={"profile_id": "test-user"}
).json()
session_id = session["session_id"]
# Mock API response
requests.post(
f"{BASE_URL}/sessions/{session_id}/routes",
headers=headers,
json={
"pattern": "**/api/user",
"action": {
"type": "override",
"status": 200,
"body": '{"id": 123, "name": "Test User"}'
}
}
)
# Run test
requests.post(
f"{BASE_URL}/sessions/{session_id}/pages/page-0/goto",
headers=headers,
json={"url": "https://app.example.com"}
)
# Interact with page
requests.post(
f"{BASE_URL}/sessions/{session_id}/pages/page-0/click",
headers=headers,
json={"selector": {"strategy": "css", "value": "#login-button"}}
)
# Capture screenshot on failure
screenshot = requests.get(
f"{BASE_URL}/sessions/{session_id}/pages/page-0/screenshot",
headers=headers,
params={"full_page": True}
).content
with open("test-failure.png", "wb") as f:
f.write(screenshot)
Using Puppeteer via CDP
const puppeteer = require('puppeteer-core');
// Connect to existing session via CDP
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:8100/sessions/{session_id}/cdp',
headers: {Authorization: `Bearer ${process.env.BROWSERIOUS_ACCESS_TOKEN}`}
});
const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
console.log('Page title:', title);
await page.screenshot({path: 'screenshot.png'});
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
API_KEYS |
- | Comma-separated API keys (required) |
PROFILES_DIR |
/data/profiles |
Profile storage directory |
EXTENSIONS_DIR |
/data/extensions |
Extensions storage directory |
MAX_SESSIONS |
10 | Maximum concurrent sessions |
DEFAULT_TIMEOUT |
3600 | Default session timeout (seconds) |
RESOLUTION |
1920x1080x24 |
Virtual display resolution |
Browser download capture
Off by default. When enabled, files the browser downloads are uploaded to file
storage as file_type=download and are then listed by GET /files?file_type=download
and the list_files MCP tool.
| Variable | Default | Description |
|---|---|---|
DOWNLOAD_CAPTURE_ENABLED |
false |
Capture browser downloads into file storage |
DOWNLOADS_DIR |
/data/downloads |
Download directory as the API sees it |
HOST_DOWNLOADS_DIR |
`` | Same directory as the Docker host sees it; required when the API itself runs in a container |
DOWNLOAD_MAX_BYTES |
536870912 |
Largest download stored (512 MiB) |
DOWNLOAD_DRAIN_TIMEOUT |
30 |
Seconds a closing session waits for downloads to finish |
DOWNLOAD_UPLOAD_TIMEOUT |
30 |
Seconds a closing session waits for those uploads to reach storage |
DOWNLOAD_STALL_TIMEOUT |
5 |
Seconds without progress after which a download is treated as hung |
DOWNLOAD_MAX_CONCURRENT_CAPTURES |
4 |
Concurrent uploads per session |
Prerequisites, all checked and logged at startup:
FILE_STORAGE_BACKENDmust not benone.DOWNLOADS_DIRmust exist and be writable by the API process.- The API and the browser container must resolve the directory to the same bytes. That holds for a local (BYOM) Docker backend with the volume below. It does not hold for the Fly backend, which cannot take host bind mounts — capture is skipped there, with a warning.
- Sessions authenticated with a legacy
X-API-Keyhave no tenant to own the stored file, so capture is skipped for them, with a warning.
Docker Compose
services:
api:
build: .
container_name: browserious-api
ports:
- "8100:8100" # API + VNC viewer
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./profiles:/data/profiles
- ./extensions:/data/extensions
- ./downloads:/data/downloads
environment:
- API_KEYS=${API_KEYS}
- API_HOST=localhost
- API_PORT=8100
- PROFILES_DIR=/data/profiles
- EXTENSIONS_DIR=/data/extensions
- DOWNLOADS_DIR=/data/downloads
- HOST_PROFILES_DIR=${HOST_PROFILES_DIR}
- HOST_EXTENSIONS_DIR=${HOST_EXTENSIONS_DIR}
- HOST_DOWNLOADS_DIR=${HOST_DOWNLOADS_DIR}
- DOCKER_NETWORK=browserious
- DOCKER_IMAGE=browserious-node:latest
- MAX_SESSIONS=10
- DEFAULT_TIMEOUT=3600
networks:
- browserious
restart: unless-stopped
networks:
browserious:
name: browserious
driver: bridge
Note: The API container requires access to Docker socket to spawn browser node containers. Set HOST_PROFILES_DIR, HOST_EXTENSIONS_DIR and HOST_DOWNLOADS_DIR to the absolute paths on your host machine for volume mounts to work correctly in browser nodes. These are the paths the Docker daemon resolves, not the paths inside the API container; getting them wrong makes downloads land somewhere the API cannot read.
Development
Setup
# Clone repository
git clone https://github.com/muzhig/browserious.git
cd browserious
# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Install Playwright browsers
playwright install chromium
# Run tests
pytest
# Run locally (without Docker)
export API_KEYS=test-key
export DISPLAY=:99
Xvfb :99 -screen 0 1920x1080x24 &
python -m src.server
src.server enables Uvicorn proxy-header processing only for peers in
FORWARDED_ALLOW_IPS, an explicit comma-separated IP/CIDR allowlist. It defaults
to 127.0.0.1, matching the supported local Caddy deployment. Keep that default
for direct API exposure; when another ingress connects to the container, set the
variable to that ingress network only. Wildcard trust is rejected.
Rebuilding After Code Changes
Different parts of the codebase require different rebuild steps:
| Changed | Rebuild Command | Notes |
|---|---|---|
src/*.py |
docker-compose build api |
Python API code |
node/api/*.go |
docker build -t browserious-node:latest ./node |
Go node API (input simulation) |
node/Dockerfile |
docker build -t browserious-node:latest ./node |
Node container config |
node/entrypoint.sh |
docker build -t browserious-node:latest ./node |
Node startup script |
Dockerfile |
docker-compose build api |
API container config |
docker-compose.yml |
docker-compose up -d |
Just restart, no rebuild |
Quick reference:
# After changing Python code (src/)
docker-compose build api && docker-compose up -d
# After changing Go code (node/api/)
docker build -t browserious-node:latest ./node
# After changing both
docker build -t browserious-node:latest ./node && docker-compose build api && docker-compose up -d
# Full rebuild from scratch
docker build -t browserious-node:latest ./node && docker-compose build --no-cache && docker-compose up -d
Note: Existing browser sessions use the node image that was current when they were created. New sessions will use the updated image.
Project Structure
browserious/
├── docs/ # Documentation
│ ├── PRD.md
│ ├── ADR.md
│ ├── API.md
│ └── SECURITY.md
├── src/ # Source code
│ ├── main.py
│ ├── browser_manager.py
│ ├── page_operations.py
│ ├── route_manager.py
│ └── ...
├── tests/ # Test suite
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── README.md
Deployment
Production Checklist
- Generate strong API keys (
openssl rand -base64 32) - Set
JWT_SECRETto 32+ random bytes (openssl rand -base64 32) — required outsidedev/testing, the API refuses to start without it - Enable HTTPS (nginx/Caddy reverse proxy)
- Configure firewall (restrict API/VNC to trusted IPs)
- Set resource limits (CPU, memory)
- Enable profile encryption (LUKS or application-level)
- Set up monitoring and alerting
- Configure backups
- Review Security Documentation
Horizontal Scaling
Run multiple instances with external load balancer:
# Instance 1
docker-compose up -d
# Instance 2 (different ports)
docker-compose -f docker-compose.instance2.yml up -d
# Load balancer (nginx)
upstream browserapi {
server instance1:8000;
server instance2:8000;
}
See ADR-002 for scaling strategy.
Security
Browserious provides multiple security layers:
- API key authentication
- Docker container isolation
- Chromium sandbox
- Per-session VNC passwords
- Resource limits
Important: This service exposes significant attack surface. Review SECURITY.md before production deployment.
Quick Security Wins
- HTTPS Only: Use TLS for API (nginx reverse proxy)
- Network Isolation: Firewall rules to restrict access
- Strong API Keys: Generate with
openssl rand -base64 32 - Resource Limits: Prevent DoS via docker-compose limits
- Profile Encryption: Encrypt
/data/profilesvolume
Troubleshooting
Common Issues
Session creation fails
- Check Docker is running:
docker ps - Check logs:
docker-compose logs - Verify shm_size:
docker inspect | grep ShmSize
VNC not showing browser
- Ensure you're using the web VNC URL:
http://localhost:8100/vnc.html?session={session_id} - Check that the session exists:
curl http://localhost:8100/sessions/{session_id} - Verify the browser node container is running:
docker ps | grep session-
Browser crashes
- Increase shm_size in docker-compose.yml (recommended: 2g)
- Check memory limits
- Review container logs
Profile corruption
- Avoid concurrent access to same profile_id
- Use unique profile IDs:
user-{id}-{session-num} - See ADR-003
Roadmap
MVP (Current)
- Session management
- Page operations (all selector strategies)
- Request interception
- Profile management
- Extension support
- VNC access
- CDP WebSocket
Post-MVP
- Metrics and monitoring (
/metrics,/health) - Session event webhooks
- Redis-based session registry (multi-instance)
- Session recording (Playwright trace)
- Firefox support
- Mobile device emulation
Contributing
Contributions welcome! Please:
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Make changes following code style guidelines
- Run tests:
pytest tests/ -v - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
Quick Setup for Contributors
git clone https://github.com/muzhig/browserious.git
cd browserious
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
playwright install chromium
pytest tests/ -v
See CONTRIBUTING.md for detailed guidelines.
Acknowledgments
- Playwright - Browser automation framework
- FastAPI - Modern Python web framework
- noVNC - HTML5 VNC client
- x11vnc - VNC server
Support
- Issues: GitHub Issues
- Documentation: docs/
- Security: Report vulnerabilities to security@example.com
Built for automation, testing, and scraping workflows.
Release files for browserious 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| browserious-0.2.2.tar.gz | 913.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| browserious-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 973.0 kB
Release files / browserious-0.2.2.tar.gz
| Download URL | browserious-0.2.2.tar.gz |
|---|---|
| Size | 913.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
08df1fc3e2f80492b350dc932f9e795574e868593a6afd0938603412298e0f53
|
|
BLAKE2b-256 checksum How to use checksums |
debe1dafc00c1945b40b0343268aa2e9556fd5356f667043d4abd84c3e9e190d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / browserious-0.2.2-py3-none-any.whl
| Download URL | browserious-0.2.2-py3-none-any.whl |
|---|---|
| Size | 59.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
be5f2f593a4c886486014c34638c6a4b37f0d6173663abc49d593d0a30f8c88a
|
|
BLAKE2b-256 checksum How to use checksums |
6a5b5ebaf9d17b825ee5a50d3ca28e46215140b5b389faa269adf9a790ee8e25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency log