Atlas / Skills / zenbu-labs / terminal-browser

terminal-browserBLOCK

skills/zenbu-labs/terminal-browser

A browser inside your terminal

Verdict
BLOCK
Grade
D
Trust score
69 /100
Version
—
Hosts
3 documented
License
MIT
Stars
3,684
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

A real browser that runs inside your terminal

Installation

curl (macOS & Linux):

curl -fsSL https://terminal-browser.sh/install | bash
Note: Run terminal-browser upgrade to upgrade versions

Homebrew (macOS & Linux):

brew install terminal-browser

Windows

To install on Windows you must be using WSL. Terminals with kitty graphics support are also very limited on Windows, the following are terminals I have tested that terminal-browser will work on inside Windows:

  • https://noctty.com/
curl -fsSL https://terminal-browser.sh/install | bash

Claude code plugin

Install instructions here

Usage

terminal-browser # launches the browser
terminal-browser open  # opens the browser at a url
terminal-browser --split right # opens the browser in a split pane to the right
terminal-browser open --ssh   # performs all network requests through a remote server
terminal-browser open --transparent  # the terminal background will show through pages without a background color
terminal-browser ls # lists open browsers
terminal-browser action # an agent-browser compatible cli for interacting with open terminal-browsers
terminal-browser upgrade # upgrade to the latest version

Use cases:

  • You can have a coding agent and website scoped to the same terminal tab
  • Your agent has full access to interact with open terminal-browsers, which gives your agent the capability to use the web
  • You can ask an agent to make HTML plans and then open them inside terminal-browser, which will automatically open in a split pane next to your agent
  • terminal-browser works over SSH, which allows you to preview websites running on remote machines easily

Shortcuts

Read from source at commit 015423d7ed78OBSERVED · 2026-10-07
02

Install

Commands as the repository documents them. They are shown, not run.

claude plugin install terminal-browser@terminal-browser
npm install
npm install @zenbu-labs/pixel
03

Host compatibility

What the documentation claims. We have not run a compatibility test.

HostStatusNotes
claude-codementioned
codexmentioned
cursormentioned
04

What it tells the agent

The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.

---
name: verify
description: Drive an engine app headlessly in a pty, record a video of the whole verification, and open a summary page (video + timeline + checks) with pixel open.
---

Apps here render via the kitty graphics protocol, so they can be verified
without a real terminal. Every verification is **recorded**: the harness in
`tools/verify-recorder/` captures every frame the app emits, overlays your
inputs (click ripples, caption bar), encodes a video, and generates a summary
page. Do not hand-roll one-off pty scripts that only dump PNGs.

## Writing a verification

Write a driver script (in /tmp is fine) using the checked-in package:

```python
import sys
sys.path.insert(0, "<repo>/tools/verify-recorder")
from driver import Driver
from recorder import Recorder

rec = Recorder("wheel-pan", title="Wheel pan keeps cursor anchored")
d = Driver(["<repo>/engine/target/debug/typing"], rec,
           cols=120, rows=32, xpixel=1200, ypixel=800)

d.pump(3.0)                                  # pump between actions so frames arrive
rec.check("app painted", d.frame_size is not None, f"{d.frame_size}")
w, h = d.frame_size                          # REAL framebuffer size — always use this

d.text("hello", "type into editor")          # every input takes a description
d.click(w // 2, h // 3, "select the note")   # mouse coords are pixels (1016 mode)
d.wheel(w // 2, h // 2, down=True, n=3, description="scroll content")
d.pump(1.0)
rec.check("scroll redrew", len(rec.frames) > 40, f"{len(rec.frames)} frames")
rec.still("after-scroll")                    # named snapshot for the summary page

d.stop("ctrl+c")                             # or "ctrl+q" depending on the app
rec.finish()                                 # composites markers, encodes mp4, writes summary
```

- `Driver` spawns the argv in a pty (TERM=xterm-kitty, TIOCSWINSZ with pixel
  dims, answers the `\x1b[?1016$p` mouse probe), decodes kitty `a=T,f=32,o=z`
  frames, and feeds them to the recorder. Node apps: spawn
  `["npx", "tsx", "src/main.tsx", ...]` with `cwd=` the package dir (tsx
  resolves tsconfig from cwd; a wrong cwd silently drops jsx config).
- Input methods: `key("enter"/"esc"/"ctrl+q"/"super+shift+z")`, `text`,
  `click`, `press`/`drag`/`release` (a drag needs all three — `click` sends
  press+release together), `move`, `wheel`. Descriptions become the video
  caption bar and the summary timeline — write what the step is *testing*.
- `rec.check(name, ok, detail)` for every assertion; `rec.still(name)` to pin
  the current frame into the summary.
- Give checks real assertions (frame deltas, decoded pixel colors via
  `recorder.png_read(rec.frames[-1]["path"])`) — the summary shows pass/fail.

## Ending a verification (required)

`rec.finish()` prints the run dir and summary path. **Always end by opening
the summary in a split:**

```
pixel open file:///tmp/verify-runs/<name>-<stamp>/summary.html
```

That page is the deliverable: what was tested (clickable timeline that seeks
the video), the checks table, the stills, and the video of the whole run.
Watch out for FAIL rows before declaring the verification passed.

## Gotchas

- The engine rounds the window down to the cell grid, so the framebuffer can
  be narrower than the requested winsize. Take coordinates from
  `d.frame_size`, never from the requested pixels, or clicks land ~5% off.
- Keep pumping after quit (`d.stop` does) or the exit is never observed.
- Escape must be kitty CSI-u (`d.key("esc")` handles it); a bare `\x1b` makes
  the app swallow the next escape sequence as literal text.
- If a press lands on a node without handlers, the engine dispatches the click
  at the *release* position — a missed drag can silently click something else.
- Hover state only updates on move events; end interactions with a `move` if
  the screenshot should show hover styling.
- Apps taking a file path argv need an ABSOLUTE path (their cwd is the
  package dir).
- Run artifacts live in `/tmp/verify-runs/<name>-<stamp>/`: `frames/`,
  `events.jsonl`, `run.json`, `verification.mp4`, `summary.html`.
05

Trust audit

BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryFAIL
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (12 observation(s))
Network
declared (9 observation(s))
Shell
declared (6 observation(s))
Dependencies
not all pinned
Secrets in source
none-found

Findings (24)

HIGHInventory / provenance · inv.suspicious_name · CWE-1104
pixel/engine/crates/pixel-core/src/terminal/payload.rs
payload.rs
Why it matters. member named after an attack tool
Fix. remove or justify
HIGHCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
pixel/packages/pixel/src/terminal/run.ts:10
const running = exec(bin, args, { env, maxBuffer: 4 * 1024 * 1024, timeout: 8000 });
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
claude-code-plugin/hooks/register.tsx:45
const bridgeUrl = (path: string) => `http://127.0.0.1:${state.port}${path}`
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
cli/src/claude-bridge.ts:329
env.TERMINAL_BROWSER_AGENT_BRIDGE = `http://127.0.0.1:${this.port}`;
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
cli/src/claude-bridge.ts:531
const url = new URL(request.url ?? "/", "http://127.0.0.1");
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
pixel/packages/pixel/src/web/favicon.ts:28
.createHash("sha1")
LOWFilesystem / path · fs.credential_store · CWE-22, CWE-59
pixel/packages/pixel/test/ssh.test.js:16
const resolved = resolveSshTarget("ssh -i ~/.ssh/id -p 2200 dev@box");
Why it matters. touches a credential store
LOWFilesystem / path · fs.credential_store · CWE-22, CWE-59
pixel/packages/pixel/test/ssh.test.js:18
assert.deepEqual(resolved.hostArgs, ["-i", "~/.ssh/id", "-p", "2200"]);
Why it matters. touches a credential store
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
pixel/engine/crates/pixel-core/src/menu.rs:356
include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
pixel/engine/crates/pixel-core/src/paint.rs:805
include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
pixel/engine/crates/pixel-core/src/paint/opaque.rs:231
static FONT_BYTES: &[u8] = include_bytes!("../../../../assets/fonts/JetBrainsMono-Regular.ttf");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
pixel/engine/crates/pixel-core/src/text_input.rs:926
include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
pixel/engine/crates/pixel-core/src/tree/tests.rs:8
include_bytes!("../../../../assets/fonts/JetBrainsMono-Regular.ttf");
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
pixel/examples/ssh/bundle/start:10
echo "READY http://127.0.0.1:$PORT/"
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
browser/package.json
micromark, micromark-extension-gfm, react, zod, @types/node, @types/react, typescript
Why it matters. 7 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
cli/package.json
zod, @types/node, typescript
Why it matters. 3 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
examples/embedded/package.json
@types/node, typescript
Why it matters. 2 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
monitor/package.json
react, @types/node, @types/react, typescript
Why it matters. 4 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
package.json
@electron/osx-sign, esbuild
Why it matters. 2 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWPrompt injection · prompt.authority_framing · CWE-94, CWE-1427
README.md:53
- Your agent has full access to interact with open terminal-browsers, which gives your agent the capability to use the web
LOWPrompt injection · prompt.authority_framing · CWE-94, CWE-1427
herdr-plugin/README.md:30
- Your agent has full access to interact with open terminal-browsers, which gives your agent the capability to use the web
LOWSupply chain · prompt.pipe_to_shell · CWE-829, CWE-1357
README.md:15
curl -fsSL https://terminal-browser.sh/install | bash
LOWSupply chain · prompt.pipe_to_shell · CWE-829, CWE-1357
README.md:29
curl -fsSL https://terminal-browser.sh/install | bash
LOWSupply chain · prompt.pipe_to_shell · CWE-829, CWE-1357
claude-code-plugin/README.md:16
curl -fsSL https://terminal-browser.sh/install | bash # or brew install terminal-browser

Gates applied: no_behavioural_pass.

Audited 2026-10-07 · audit v0.4.1 · source sha 015423d7ed78full audit observations/trust-audit/skill/zenbu-labs__terminal-browser.json · Report an issue / request a re-scan
06

Audit history

Every audit this skill has had.

DateSourceVerdictGradeScoreChange
2026-10-07015423d7ed78BLOCKD69first audit
07

Questions

What does the terminal-browser skill do?

A browser inside your terminal

Is terminal-browser safe to install?

No — not without reading the findings first. The audit graded it D (69/100) and found 2 critical or high issues in the source. Each one is listed on this page with the file and line it is on.

What can terminal-browser access on my machine?

The audit observed that it reaches the network, runs shell commands and reads or writes files. Each of those is consistent with what it says it does. Secrets in the source: none found.

What do I need installed to use terminal-browser?

Its own instructions reference click. Dependencies are not all pinned to exact versions, so what installs today may differ tomorrow.

Which assistants does terminal-browser work with?

Its documentation mentions claude-code, codex and cursor. That is what the text claims, not a compatibility test we ran.

How current is this page?

The grade is for one exact copy of the source (015423d7ed78), read on 2026-10-07. The repository is watched, and a new audit runs when it changes — this is the first audit.

Advertisement