# Genswarms Browser Use

> Wire the Genswarms.Browser object: web browser for agents with two modes (allowlist fail-closed, or denylist allow-any with IP-filtering proxy). Render/click/type/back with compact replies that keep pages OUT of agent context. Use when giving agents web access, or debugging containment issues. Denylist mode requires a deployment-provided IP-filtering egress proxy for sub-resource SSRF containment.

- Skill: `genlayerlabs/genswarms-browser-use` (Agent Skill)
- Install (CLI): `npx skillmds@latest add genlayerlabs/genswarms-browser-use`
- Raw SKILL.md: https://api.skillmd.com/api/skills/genlayerlabs/genswarms-browser-use/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: genlayerlabs (https://skillmd.com/u/genlayerlabs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/genlayerlabs/genswarms-browser-use

---


# Genswarms.Browser — using the browser object

## Wiring

```elixir
# Allowlist mode (fail-closed, default)
%{name: :browser, handler: Genswarms.Browser, config: %{
  mode: :allowlist,
  allowlist_path: "config/browser_allowlist.txt",  # one host per line — fail-closed
  head_chars: 2000,          # compact reply size
  session_ttl_ms: 120_000
  # renderer/resolver/fetcher injectable for tests; default renderer drives
  # the `agent-browser` CLI (must be on PATH); curl via GENSWARMS_CURL_BIN/PATH
}}

# Denylist mode (allow-any except blocklist; requires IP-filtering proxy)
%{name: :browser, handler: Genswarms.Browser, config: %{
  mode: :denylist,
  blocklist_path: "config/browser_blocklist.txt",  # one host per line to deny
  proxy_url: "http://proxy.internal:8080",  # required: deployment-provided IP-filtering egress proxy
  head_chars: 2000,
  session_ttl_ms: 120_000
}}
```

## Runtime grants (allow_sync — 0.2.0)

Trusted objects may extend the allow set live, without a restart:

```elixir
config: %{
  # …allowlist/denylist config as above, plus:
  grant_sources: [:rally],        # ONLY these senders may allow_sync; absent/[] = disabled
  grants_store: MyApp.Store       # optional: load_grants/0 + save_grant/2 (persistence)
}
```

- A grant source sends `{"action":"allow_sync","hosts":["docs.example.com"],
  "meta":{…}}` → reply `{"ok":true,"added":N,"skipped":K,"total":M}`. `meta`
  is opaque provenance, passed to the store untouched.
- Every host re-passes the package's sanity floor on receipt AND on boot load
  (bare dotted DNS names only — IP literals, `localhost`, `.local`/`.internal`
  are skipped and counted). Curate harder upstream: the floor is a backstop,
  not a policy. The render-time SSRF gate still applies to granted hosts.
- Boot allowset = allowlist file ∪ stored grants; a raising store falls back
  to the file-only floor. A store WRITE failure keeps the grant in memory
  (lost on restart, logged loudly). The store is called directly — a store
  wired against a stale contract (wrong arity) fails loudly, never silently.
- **Kill switch**: an EMPTY/unreadable allowlist file means nothing is
  reachable — stored AND runtime grants are suppressed (persisted, noted in
  the reply, but inactive) until the floor is restored and the object
  restarts. Emptying the file during an incident actually stops browsing.
- Denylist mode: grants are accepted + persisted (ready for a mode flip) but
  don't change reachability — the reply carries a `note`.
- Any other sender gets `{"error":"unauthorized"}`. The action is deliberately
  absent from `interface/0` — agents are never told it exists; don't add agent
  slots to `grant_sources`.
- Each applied grant emits a `:browser_grant` display event.
- Revocation: delete the store row and restart (the object holds the union in
  memory).

## The containment model (don't weaken it)

### Allowlist mode (default — fail-closed)
- Only `https://` URLs on allowlisted hosts are fetched/rendered.
- **Re-gate on settle**: a client-side redirect landing OFF the allowlist
  destroys the session (never reused) — pinned by checks/browser_regate_settle.
- **Compact replies by default**: head of main body + nav-link index + size.
  The agent asks `{"full":true}` only to have the full body written to ITS OWN
  workspace file (it greps there) — the page never re-enters conversation
  context. This is the context-bloat fix; don't return full pages inline.

### Denylist mode (allow-any except blocklist)
- Allows `https://` URLs to any public host except those in `blocklist_path`.
- **Requires IP-filtering egress proxy** (`proxy_url` config): the proxy must
  intercept and deny requests to internal / reserved IPs (10.0.0.0/8, 172.16.0.0/12,
  192.168.0.0/16, 127.0.0.0/8, etc.) to prevent sub-resource SSRF (agent-initiated
  fetch of internal databases, cloud metadata, etc.). The proxy is YOUR responsibility.
- **Compact replies** work identically: agent gets head + nav index, can request
  full body to its workspace file.

## Gotchas

- `render_failed {:open_failed, ""}` → the `agent-browser` CLI is not on PATH
  (the default renderer shells out to it). Inject a renderer or install it.
- **Denylist mode without proxy**: traffic to internal IPs is NOT blocked by the
  browser itself — a missing or misconfigured proxy will expose your internal
  networks. Test your proxy (e.g. `curl -x http://proxy:8080 http://192.168.1.1`)
  before enabling denylist mode in production.
- Display telemetry rides the `:genswarms_objects, :display_wire` app env
  (default [:genswarms, :display]) — set it to your host's wire name if your
  EventFeed listens elsewhere.

