Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1e7820c
gpu-browser: npx @agent-socket/gpu-browser installs it under its own …
claude Oct 11, 2026
41ff210
gpu-browser: say the real install size (about 650 MB on disk, measure…
claude Oct 11, 2026
61daa96
Merge remote-tracking branch 'origin/claude/gpu-browser' into claude/…
claude Oct 11, 2026
5af392f
gpu-browser: undo a failed first install, leave other groups alone, A…
claude Oct 11, 2026
efdaf36
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
59d132f
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
0093415
CI: on a Linux failure, run the GPU browser's Chromium with its log, …
claude Oct 11, 2026
4f4674b
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
c462704
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
63817db
CI: targeted Chromium diagnostics on install failures (Linux, macOS)
claude Oct 11, 2026
3ef6929
CI: Linux diagnostics compare the runner and a system account, direct…
claude Oct 11, 2026
acd3de4
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
407efd2
CI: macOS diagnostics try an account without a window-server session …
claude Oct 11, 2026
b6c9370
CI: Linux diagnostics separate sudo, system account and sandbox; stra…
claude Oct 11, 2026
db5b8c0
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
cc356f7
gpu-browser: start the server with plain sudo -u on macOS too (no lau…
claude Oct 11, 2026
aa77d56
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
7475095
gpu-browser: quiet group removal on macOS; drop the one-off CI diagno…
claude Oct 11, 2026
9716794
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
c5a65eb
gpu-browser: an interrupted update restores the old copy; refuse a /o…
claude Oct 11, 2026
f386d2a
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
02f7b08
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
519dbdb
gpu-browser: one install at a time; a failed update removes an AppArm…
claude Oct 11, 2026
2273975
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
3db7594
gpu-browser: refuse a sudo rule or AppArmor profile we didn't write; …
claude Oct 11, 2026
561f435
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
ee97797
gpu-browser: install lock is a symlink to the pid; recover a .old lef…
claude Oct 11, 2026
4b4b9a9
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
9e652de
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
fea5fd9
gpu-browser: journal what a first install creates; no swap under a ru…
claude Oct 11, 2026
1ed14fd
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
dde6f24
gpu-browser: keep .old until an update is final; password-backed star…
claude Oct 11, 2026
98466c6
gpu-browser: install lock and server pid count only while that progra…
claude Oct 11, 2026
a5d7966
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
42fbcd5
Merge branch 'claude/gpu-browser' into claude/gpu-browser-cli
claude Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/gpu-browser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: GPU browser install

# A real install → start → use the link → stop → uninstall on fresh Linux
# and macOS runners (both have passwordless sudo). Only when gpu-browser/
# changes: it downloads Node.js and Chromium (~250 MB) each time.
on:
push:
branches: [master, main]
paths: ['gpu-browser/**', '.github/workflows/gpu-browser.yml']
pull_request:
paths: ['gpu-browser/**', '.github/workflows/gpu-browser.yml']

concurrency:
group: gpu-browser-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
scripts:
name: Shell scripts (shellcheck, dash, sh)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y -qq shellcheck dash >/dev/null
- run: shellcheck -s sh gpu-browser/install/install.sh gpu-browser/install/uninstall.sh gpu-browser/test/install-smoke.sh
- run: shellcheck gpu-browser/scripts/vendor-sdk.sh
- run: for f in gpu-browser/install/*.sh gpu-browser/test/install-smoke.sh; do dash -n "$f"; done

install:
name: Install, run, uninstall (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: sh gpu-browser/test/install-smoke.sh
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## Unreleased

- **GPU browser** (`gpu-browser/`, new): the server for letting an agent anywhere render on your machine's GPU through an agent-socket link: uploads served at `https://uploads.gpu-browser.internal/`, pages, JavaScript, screenshots (in parts when bigger than one reply), agent-defined tools for the session, and `/request` to ask the owner for more hosts. Every Chromium request goes through a per-run, password-protected filtering proxy (allowlisted public hosts only; loopback, private, link-local and IPv4-embedding IPv6 addresses refused on the address it connects to), with Chromium's sandbox on. Harness scenario 63 and unit tests. The installer and CLI that run it under its own OS account come next; until then it runs only as a development run.
- **GPU browser** (`gpu-browser/`, new): the server for letting an agent anywhere render on your machine's GPU through an agent-socket link: uploads served at `https://uploads.gpu-browser.internal/`, pages, JavaScript, screenshots (in parts when bigger than one reply), agent-defined tools for the session, and `/request` to ask the owner for more hosts. Every Chromium request goes through a per-run, password-protected filtering proxy (allowlisted public hosts only; loopback, private, link-local and IPv4-embedding IPv6 addresses refused on the address it connects to), with Chromium's sandbox on. Harness scenario 63 and unit tests.
- **GPU browser CLI** (`npx @agent-socket/gpu-browser`, package `@agent-socket/gpu-browser` 0.1.0): installs the server under its own OS account, `agent-socket-gpu-browser` (no login, no password, own group; on macOS made like MacPorts' user), into root-owned `/opt/agent-socket-gpu-browser` with Node.js pinned by checksum, after one confirmation and one sudo prompt. A self-test starts Chromium the way every run does before an install or update is swapped in. One sudo rule (`/etc/sudoers.d/zz-agent-socket-gpu-browser`) lets the installing user start that server, and nothing else, without a password. Prints the link (60 minutes by default, `--for`), asks in the terminal when the agent wants another host (`always` remembers it in `~/.agent-socket/gpu-browser/allowed-hosts.txt`), and `status`, `stop`, `uninstall`. CI installs, runs and uninstalls it on Ubuntu and macOS runners.

## 0.3.0

Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ npm run ext:test:reconnect # headless
npm run ext:test # full suite, needs xvfb-run
```

The GPU browser's real install (an OS account, sudo, ~250 MB of downloads) is tested by `gpu-browser/test/install-smoke.sh` on fresh CI runners (`.github/workflows/gpu-browser.yml`, only when `gpu-browser/` changes); run it only on a throwaway machine, since it ends with an uninstall.

To run the relay on its own: `npm run dev` (port 8787). Layout and deploy notes are in each package's README and in [docs/](docs/).

## Changing the SDK
Expand Down
4 changes: 4 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ The registry runs on Cloudflare Workers with Cloudflare's request logging turned

The session URL, token and reconnect secret are discarded on disconnect. None of this is transmitted except the relay base URL, which decides where the session connects, the session id and reconnect secret, which go only to that relay when reconnecting, and the tools of kept profiles, which are registered with the relay (tool names, descriptions and input schemas, and the notes in the session's agents.md; the code stays in the extension) for sessions on that site. Uninstalling the extension deletes all of it.

## GPU browser (`npx @agent-socket/gpu-browser`)

A separate tool from the extension. While it runs, the files an agent uploads, the pages it opens, the JavaScript it runs and the screenshots it takes travel between the agent and your machine through the relay, like any tool call: forwarded, not stored, except the answer to a host request (an async result) until the agent fetches it. Pages it opens on hosts you allowed are fetched from your machine, with your IP address. It sends nothing else anywhere: no analytics. Installing it downloads Node.js from nodejs.org, `playwright-core` from the npm registry and Chromium from Playwright's CDN. On your machine it keeps your settings in `~/.agent-socket/gpu-browser/` (hosts you allowed with `always`, optional config) and the browser in `/opt/agent-socket-gpu-browser/`; uploads, pages and the temporary Chromium profile exist only while it runs. `npx @agent-socket/gpu-browser uninstall` removes the install.

## Third parties

We do not sell, rent, or share user data with third parties for advertising, marketing, or any other purpose unrelated to making the active session work. The relay and the registry are hosted on Cloudflare Workers; Cloudflare's data-handling practices are governed by its own privacy policy at <https://www.cloudflare.com/privacypolicy/>.
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ The AI can read the page, click, type, scroll, navigate and take screenshots, in

More in [chrome-extension/README.md](chrome-extension/README.md).

## Let an agent render on your GPU

```bash
npx @agent-socket/gpu-browser
```

Prints a link for an agent anywhere (a cloud Claude session has no GPU) to use a headless Chromium on your machine: it uploads a page, runs it on your GPU and gets screenshots back, while you keep using your computer. The browser runs under its own OS account, which can't read your files; pages reach only the agent's uploads and hosts you allow; the first run sets it all up after asking once. More in [gpu-browser/README.md](gpu-browser/README.md).

## Add "Connect with AI" to your app

```bash
Expand Down Expand Up @@ -80,7 +88,6 @@ Details and how to report a vulnerability: [SECURITY.md](SECURITY.md).
- [docs/protocol.md](docs/protocol.md): URLs, frames, limits, threat model
- [docs/self-hosting.md](docs/self-hosting.md)
- [CONTRIBUTING.md](CONTRIBUTING.md): `npm install`, `npm test`
- [gpu-browser/](gpu-browser/README.md): let an agent anywhere render on your machine's GPU (server; the installer is in progress)
- [agent-socket-channel](https://github.com/blitzdotdev/agent-socket-channel): a chat room where several AIs talk through agent-socket

## License
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The protocol and threat model are in [docs/protocol.md](docs/protocol.md). In sh
- **Tool output is untrusted input for the AI**, and agent input is untrusted input for the app.
- **The extension gives the link holder the tab.** `/eval` runs any JavaScript in the bound tab, with the user's logged-in session on that site. The `/navigate` check for local and private-network hosts is a guardrail, not a boundary, because `/eval` can navigate too. The site lock is the boundary for where the tab goes: every tool call checks the tab's origin against the sites the user allowed (at first the one at Connect) and runs page code only in the document it checked, so a navigation by `/eval`, a link or a redirect pauses the tools instead of extending them. Within an allowed site, `/eval` can do whatever that site's pages can (including the site's own cross-origin requests). Only the bound tab is reachable, and Stop, disconnecting, closing the tab or the session timer (60 minutes by default) ends access.

- **The GPU browser gives the link holder a browser, not your machine.** The agent's JavaScript runs in pages of a separate headless Chromium (its sandbox on), not in your browser. Every request goes through the server's filtering proxy: hosts the owner allowed, ports 80/443, names only, and never an address that is loopback, private, link-local, CGNAT, multicast or an IPv6 form that embeds IPv4 (checked on the address it connects to). The proxy listens on loopback with a password that changes every run. Uploads are served from memory at an `.internal` origin. Widening the allowlist needs the owner's answer. The agent can use the machine's GPU, CPU and memory until the session ends.
- **The GPU browser gives the link holder a browser, not your machine.** The agent's JavaScript runs in pages of a separate headless Chromium (its sandbox on), not in your browser. Every request goes through the server's filtering proxy: hosts the owner allowed, ports 80/443, names only, and never an address that is loopback, private, link-local, CGNAT, multicast or an IPv6 form that embeds IPv4 (checked on the address it connects to). The proxy listens on loopback with a password that changes every run. Uploads are served from memory at an `.internal` origin. Widening the allowlist needs the owner's answer, typed in the terminal that started it. Installed, it runs as the `agent-socket-gpu-browser` account, which can't log in and can't read the installing user's home; world-readable files elsewhere are readable by it, as by any local account. Its code in `/opt/agent-socket-gpu-browser` belongs to root. The one sudo rule lets the installing user (or any program running as them) start that server as that account without a password, nothing more. The agent can use the machine's GPU, CPU and memory until the session ends (60 minutes by default).

Reports that only restate these properties are not treated as vulnerabilities.

Expand Down Expand Up @@ -71,4 +71,4 @@ There is no global cap on sessions beyond the per-IP rate limit.
- Memory exhaustion or session disruption beyond the limits above.
- The extension acting on a tab other than the bound one, or after the session ended.
- Bypassing registry review or admin authentication.
- A GPU browser page reaching a host that isn't allowed, a loopback or private address, or a local file, or another program using the GPU browser's proxy; an agent widening the allowlist without the owner's answer.
- A GPU browser page reaching a host that isn't allowed, a loopback or private address, or a local file, or another program using the GPU browser's proxy; an agent widening the allowlist without the owner's answer; the GPU browser's account reading the installing user's files or changing its own code; its sudo rule allowing more than starting the server.
54 changes: 46 additions & 8 deletions gpu-browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,47 @@ A Chromium on your own machine, with your machine's GPU, that an agent anywhere

It is a separate app from the [Chrome extension](../chrome-extension/README.md): the extension lets an agent use a tab of *your* browser; the GPU browser is a different, headless Chromium that never sees your tabs, logins or files, and keeps rendering while you use your computer.

## Status
## Use it

| Part | Where | State |
|---|---|---|
| Server: the browser, its network boundary, the agent's tools, the link | [`app/`](app/README.md) | this folder |
| Installer + CLI: `npx @agent-socket/gpu-browser`, creates the separate OS account it runs as, approvals in your terminal | `install/`, `bin/` | next PR |
```bash
npx @agent-socket/gpu-browser
```

The first run explains what it will install and asks once (one sudo password prompt). Then it prints a link and copies it to the clipboard: paste it into the agent's chat. The link works for 60 minutes (`--for 2h`, `--for none`); Ctrl-C ends it at once.

| | |
|---|---|
| `npx @agent-socket/gpu-browser` | Start (installs or updates first if needed) |
| `npx @agent-socket/gpu-browser status` | What is installed, where, the GPU it found, whether the sudo rule works, whether it runs |
| `npx @agent-socket/gpu-browser stop` | Stop one running in another terminal |
| `npx @agent-socket/gpu-browser uninstall` | Remove the account, the sudo rule and `/opt/agent-socket-gpu-browser` |

When the agent wants a page to reach another host (a CDN, an API), the terminal rings and asks: `y` allows it for this session, `always` also remembers it, anything else refuses. Only that terminal can answer.

macOS and Linux. Needs Node.js 18+ for `npx`; the browser brings its own Node.js 22. On a machine without a usable GPU (a VM, CI) it still works, rendering in software, and says so.

### What gets installed

Until the CLI lands, the server only runs as a development run (below).
One sudo prompt covers all of it (details and order: [install/README.md](install/README.md)):

- **An account, `agent-socket-gpu-browser`**, that runs the browser. It can't log in, has no password, has its own group, and can't read your files. On macOS it doesn't appear in Users & Groups or at the login window.
- **`/opt/agent-socket-gpu-browser/`**: Node.js (pinned by checksum), `playwright-core`, Chromium (about 650 MB on disk, 250 MB to download). Owned by root, so the account can run it but not change it.
- **One sudo rule**, `/etc/sudoers.d/zz-agent-socket-gpu-browser`. It lets you start that exact server as that account without a password. Nothing else.
- **On Ubuntu 23.10+**, an AppArmor profile that lets this Chromium use its sandbox.
- **If other accounts can open your home folder**, it offers to close it (`chmod 700`), and uninstall offers to reopen it.

Before switching to a new version, it starts Chromium from the new copy the way every run does and checks it renders. Updates replace the copy only if that works.

Your settings are in `~/.agent-socket/gpu-browser/`: `allowed-hosts.txt` (hosts you allowed with `always`; edit freely) and an optional `config.json`. See the README there.

### Where things are

| Part | Folder |
|---|---|
| The server: browser, network boundary, the agent's tools, the link | [`app/`](app/README.md) |
| The CLI (`npx`), runs as you | [`bin/`](bin/README.md) |
| What runs as root, once per install | [`install/`](install/README.md) |
| Tests | [`test/`](test/README.md) |

## What the agent can do

Expand All @@ -37,12 +70,16 @@ Fixed in code ([`app/policy.mjs`](app/policy.mjs), applied by [`app/server.mjs`]
- **Widening needs the owner.** `/request` sends an `ask` to whoever runs the server; the answer comes back on its stdin. Owner-supplied Chromium flags are limited to GPU settings; anything else (`--no-sandbox`, proxy or debugging flags) is dropped.
- **The link** is a bearer credential, as everywhere in agent-socket, and dies when the server stops or its timer runs out.

In an install (next PR) the server also runs as its own OS account, `agent-socket-gpu-browser`, which can't read your files.
- **The account.** The server and Chromium run as `agent-socket-gpu-browser`, which can't log in and can't read your files (the installer checks your home folder from that account's side). World-readable files elsewhere on the machine are readable by it, as by any local account.

The agent can use the machine's GPU, CPU and memory until the link ends.

## Development run

Without installing (no separate account), from the repo root:

```bash
npm install && npm run build # from the repo root
npm install && npm run build
# the config line, then your terminal as the rest of stdin: type `answer <id> allow`, or Ctrl-D to stop
( echo '{"relay":"https://agentsocket.dev","key":"'"$(openssl rand -hex 16)"'","minutes":60}'; cat ) \
| node gpu-browser/app/server.mjs # the link arrives as a `link` event
Expand All @@ -54,3 +91,4 @@ The first line on stdin is the config; see the header of [`app/server.mjs`](app/

- `npm run gpu:test:unit`: the boundary checks ([`test/`](test/README.md)).
- `node harness/run.mjs 63` with `CHROMIUM_PATH` set: the server end to end against a local relay (uploads, pages, multi-part screenshots, the private-network refusal with a local server that must get 0 hits, the proxy's password, an approval round trip, forged events, `stop`).
- `sh gpu-browser/test/install-smoke.sh`: a real install, start, use and uninstall. CI runs it on fresh Ubuntu and macOS runners (`.github/workflows/gpu-browser.yml`); don't run it on a machine where you use the GPU browser, since it ends by uninstalling.
3 changes: 3 additions & 0 deletions gpu-browser/bin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# gpu-browser/bin

`agent-socket-gpu-browser.mjs` is what `npx @agent-socket/gpu-browser` runs, as you: it installs or updates the browser (asking first, one sudo prompt, via `../install/install.sh`), starts the server as the `agent-socket-gpu-browser` account, prints the link, and asks you in this terminal when the agent wants more hosts. Also `status`, `stop`, `uninstall`, `help`. No dependencies.
Loading
Loading