Releases: laruss/patcher-browser
Release list
Patcher desktop 0.1.1-alpha.3
alpha.2 made a sandboxed turn the default. This build is what came of taking
that seriously: a review went looking for ways out of the sandbox and found
them — the app key sitting in a terminal's environment, a rename that walked
around a deny, a CLI a turn spawned outside its own boundary — and each one was
measured before and after rather than reasoned about. Beyond that, a sandboxed
turn's network can be held to a list, an ACP agent and Pi run inside the
boundary their mode promises, and a repository's own setup script asks before it
runs.
Still macOS on Apple Silicon, still ad-hoc signed, and still without
auto-update: the first launch needs one explicit approval in System Settings,
and a newer alpha has to be downloaded rather than offered. On Linux the sandbox
is bubblewrap, and a machine that cannot build one refuses the turn instead of
running it unconfined.
What a sandboxed turn no longer reaches
- The files git executes. A clone whose config, hooks or
.gitattributes
name a program no longer runs it inside Patcher's git — for Codex turns as
well as Claude ones, on Linux as well as macOS, and no longer only where the
path is spelled the way the rule expected. Renaming.git, editing the file
and renaming it back walked around every one of those denies; each directory
on the way to a protected path is now protected as a name. - A program outside its own sandbox. On macOS a confined shell could ask the
system to do what it had been refused directly:defaults writeput a file in
~/Library/Preferencesthrough the preferences daemon, and an application
bundle written inside the workspace and handed toopenran with the whole of
the user's home and network — including the network of a turn whose egress was
confined to a list. Both are refused now, and so is sending an AppleEvent to
another application. The cost isopenitself: a confined terminal can no
longer hand a URL or a file to an application, so a tool that wanted a browser
has to print its link instead. - What runs outside a Linux sandbox, on the sandbox's behalf. The same
class as the one above, on the other backend. A confined turn shared the
machine's process table, so it could signal the daemon and anybody else's
terminal; it now has a process table of its own. A turn whose network is
confined could reach the sockets of services outside its namespace — a
session bus is a program run outside the sandbox, with the network the turn
had just given up — and those are gone from its view now, along with the
proxy's willingness to dial the machine's own loopback for a client that asks
it to. And Patcher's own credential files are hidden by the directory rather
than one by one, so a database's newest rows are not readable through the
file SQLite writes a moment after the turn starts. - Other threads. A turn drives its own thread and the ones it spawned. A
thread it creates can only name the caller's own thread as its parent and the
caller's own project — a parent is not a filing label, it is a turn dispatched
on that thread when the child finishes, at that thread's own permission mode. - A more privileged next turn. A turn cannot ask for more privilege than it
has, and it can no longer write the app settings it is running under: three
fields of that object decide what network the next turn gets. Appearance and
keyboard stay writable, because a documented agent workflow ends in one of
them. - A workspace outside the project's sources. Refused when the thread is
created, and asked for throughupdate_environment_directoryinstead. - The app key. Out of a terminal's environment, and out of the ACP bridge —
which runs outside the sandbox its agent runs inside and used to answer
fs/readTextFileandfs/writeTextFilefor any path at all. The bridge now
carries the same denied credential files, the same protected repository
entries, and the same write roots as the profile. - A key that outlives its turn. A thread has two credentials with two
lifetimes, sonohupfrom a turn's shell loses the API when the turn ends. - The CLI, except as an API.
patcher mcp-serveis spawned by Codex rather
than by a sandboxed shell, and ran whatever argv the model passed — one
command wrote a file through a positional path with no server involved at all.
It now runs only the commands whose whole effect is a request to the API, and
answers the rest by naming the turn's own shell, where the sandbox says which
paths exist.
Consent
- A repository's own
.patcher-env-setup.shasks before it runs. The answer is
remembered per project, machine, checkout and file contents, and is revocable
in Project Settings; allows made before this build are dropped, so the
question is asked once more. - A terminal can answer a consent prompt, so a headless host is no longer a
thread nobody can unblock. - A plugin cannot answer one — in either direction. Allowing and dismissing are
a person's, not something a plugin holdingthreadssettles while a turn
waits. - An MCP server's tool in a Codex turn asks, instead of being refused.
- A host nobody put on the list raises a prompt naming the host, the provider
and the port. The attempt that raised it has usually given up by the time it
is answered — an agent's own HTTP client stops waiting after ten seconds — so
the answer is what matters: it is remembered, and the next attempt is the one
that goes through.
Terminals
- An agent's terminal runs inside its turn's boundary, and is refused where
bubblewrap cannot build a namespace rather than opening unconfined. - The tab says
sandboxed, the panel names what is refused, and
patcher terminal listhas aSandboxcolumn. - A terminal you open from a thread view keeps your own credential. It used to
be handed the turn's, which refused youpatcher terminal restart|close|input
on the terminal you were sitting in and left your own consent prompts
unanswerable from that shell. patcher terminal create --selfandlist --self, so a turn does not have to
go find its own thread id before it can open a terminal.
Providers and the network
- Settings → General → "Confine the network of sandboxed turns", with an
allow-list of hosts. Off by default, and built on macOS and on Linux. - Settings → Codex → "Take the network from sandboxed turns", off by
default. - ACP agents — Cursor, OpenCode, Grok and Hermes — run inside the boundary their
mode promises, with the state directories and hosts each one needs measured
rather than guessed, and a registered agent can declare its own. An agent that
has declared none has nothing to confine it into, and runs unconfined with the
thread saying so rather than being refused. - Pi has a mode besides Full Access. Its network is not confined there, and
Patcher says so rather than implying otherwise: its client ignores the proxy. - The
patcherCLI reaches a Codex turn as a tool, not only over the network.
The daemon
- A loopback API credential of its own.
- A config write survives a reload the running server refuses.
- The offline reload endpoint is pinned, not just its port.
Browser tools
- Nine
patcher browserfixes, including a scoped page read, and four more a
later review found:wait --urlmatches a pattern with a?in it, so a
redirect ending in a query string is waitable at all;--tabtakes a URL or
title substring rather than only something shaped like an id; a scoped read
has a deadline of its own instead of waiting out the server's; and
wait --textsearches the whole page rather than the first 20 000 characters
of it. - A browser action that refuses says which check refused it, instead of
sometimes reporting a deadline that expired while it was asking.
Fixes and polish
- A turn can fork a thread again. The scope check read
forkas a thread id and
refused every turn that tried; the fork is now held to the same relationship
as any other thread a turn drives. - A terminal on Linux no longer prints
warning: unable to access '.git/info/attributes'twice for every git
command. That file is one Patcher protects, and on a repository that does not
have one, what stood in its place was unreadable rather than empty. - A Linux machine that has just been given permission to build sandboxes no
longer has to have its daemon restarted before the next terminal will open.
Docs
- Installing on Linux says what it needs:
bubblewrap, and the
unprivileged-user-namespace sysctl Ubuntu 24.04 restricts — wherebwrapis
installed and still answersPermission deniedto everything. - Security
is corrected where it had drifted, and names what this build still leaves
open: how long a turn's credential really lives, and two ways around the
Linux network boundary.
Patcher desktop 0.1.1-alpha.3 — alpha. macOS on Apple Silicon only.
Download the .dmg, open it, and drag Patcher to Applications.
First launch needs one approval
This build is ad-hoc signed. An ad-hoc signature is valid, but it
carries no Apple Developer ID and is not notarized, so macOS will not
open it on your word alone:
- Open Patcher from Applications. macOS refuses, saying it cannot
check it for malicious software. - Open System Settings → Privacy & Security, scroll to Security,
and choose Open Anyway for Patcher. - Confirm. Later launches ask nothing.
From a terminal instead:
xattr -d com.apple.quarantine /Applications/Patcher.appVerifying the download
There is no notarization ticket to check, so SHA256SUMS.txt is what
says the bytes are the ones CI produced:
shasum -a 256 -c SHA256SUMS.txtIt says nothing about who built the app. Only Apple's chain does that,
and this build is not in it.
Alpha builds d...
Patcher desktop 0.1.1-alpha.2
Agents now run sandboxed by default, and stepping outside the sandbox is a
choice you make on purpose. Plus a round of browser fixes, tighter plugin
isolation, and a start-up that no longer lands on a white screen.
Still macOS on Apple Silicon, still ad-hoc signed, and still without
auto-update: the first launch needs one explicit approval in System Settings,
and a newer alpha has to be downloaded rather than offered.
Agents are sandboxed by default
A thread runs inside the operating system's own sandbox: the agent writes in its
workspace and nowhere else. macOS ships that sandbox; on Linux Patcher needs
bubblewrap installed.
- A machine that cannot sandbox refuses the turn and names what to install,
instead of running the turn without one. - A provider that does not offer the mode you picked now resolves down to
the sandbox instead of up to Full Access. - Every machine still set to Full Access is lowered to the sandbox ceiling when
you update. Raise it again in Settings if that is what you want — the message
a refused turn shows names the limit and whose it is to change. - Full Access is no longer the third item in a menu. Choosing it opens a
dialog that says what it turns off, and waits for you to confirm.
What a sandboxed turn no longer reaches
- Patcher's own secrets. The app key, the machine auth secret, the daemon's
bearer token, and the database that holds every thread are denied to the turn
inside the sandbox — and the daemon refuses to serve those same paths through
its file API, to any caller. - The app key itself. A turn's processes are handed a key scoped to that one
thread instead. It answers for that thread, and the routes that would undo a
sandbox refuse it: writing files anywhere, opening a terminal, raising a
machine's ceiling, enrolling a machine, installing a provider CLI, and
approving the turn's own permission prompt. - A repository's own git config. A clone whose config or
.gitattributes
names a hook, a filter, or an external diff no longer runs it inside Patcher's
git.
Security names what this does not close yet, Codex reads and
.git inside the writable roots among them.
Plugins
- Each plugin runs in its own process, so one plugin's key and channel never
meet another's. - A plugin is held to what it declared it registers, not only to what it calls.
A page script matching every site needs the sites to say so. - A plugin cannot reach a browser command it never declared by writing to its
channel instead of calling the API it was handed. - A second connection can no longer take the browser role off the window that
holds it.
The browser
- Cmd-click opens a link as a background tab instead of crashing the shell.
- The address bar keeps a half-typed address, selects the whole address on the
click that focuses it, and stops offering to switch tabs while you type one. - A hovered omnibox row no longer decides what Enter does.
- Closing a tab no longer closes the popup it opened.
- The selected tab is a shade you can find.
Start-up
The app no longer opens on a white screen. A Node builtin reaching the app
bundle fails the build instead of warning, and CI opens the packaged app and
fails the build when the start-up errors.
Patcher desktop 0.1.1-alpha.2 — alpha. macOS on Apple Silicon only.
Download the .dmg, open it, and drag Patcher to Applications.
First launch needs one approval
This build is ad-hoc signed. An ad-hoc signature is valid, but it
carries no Apple Developer ID and is not notarized, so macOS will not
open it on your word alone:
- Open Patcher from Applications. macOS refuses, saying it cannot
check it for malicious software. - Open System Settings → Privacy & Security, scroll to Security,
and choose Open Anyway for Patcher. - Confirm. Later launches ask nothing.
From a terminal instead:
xattr -d com.apple.quarantine /Applications/Patcher.appVerifying the download
There is no notarization ticket to check, so SHA256SUMS.txt is what
says the bytes are the ones CI produced:
shasum -a 256 -c SHA256SUMS.txtIt says nothing about who built the app. Only Apple's chain does that,
and this build is not in it.
Alpha builds do not auto-update
electron-updater installs only a Developer ID-signed update, so
this build will not replace itself. Check this page for a newer alpha.
Patcher desktop 0.1.1-alpha.1
Patcher desktop 0.1.1-alpha.1 — alpha. macOS on Apple Silicon only.
Download the .dmg, open it, and drag Patcher to Applications.
First launch needs one approval
This build is ad-hoc signed. An ad-hoc signature is valid, but it
carries no Apple Developer ID and is not notarized, so macOS will not
open it on your word alone:
- Open Patcher from Applications. macOS refuses, saying it cannot
check it for malicious software. - Open System Settings → Privacy & Security, scroll to Security,
and choose Open Anyway for Patcher. - Confirm. Later launches ask nothing.
From a terminal instead:
xattr -d com.apple.quarantine /Applications/Patcher.appAlpha builds do not auto-update
electron-updater installs only a Developer ID-signed update, so
this build will not replace itself. Check this page for a newer alpha.