relay 0.5.0
0.5.0, 2026-09-27
A program planted in a project folder no longer runs through a PATH entry that
reaches that folder. 0.4.0 started its children through safe_spawn 1.0.0,
which skipped only relative PATH entries. This release vendors safe_spawn
1.0.1 and hands it the folder each child works in.
This was prepared as a 0.4.1 patch. It takes the minor number instead because
it changes what existing setups do, the rule 0.3.0 set for a pre-1.0 break: a
run, test_cmd or check command, or a git hook or filter during
--auto-commit, no longer finds a program through a project folder on PATH,
such as .venv/bin or node_modules/.bin, unless that folder holds the
interpreter relay runs on. A pin such as ~=0.4.0 excludes this release;
widen it to take the fix. The MCP tool names, the relay.mcp-run-request/v2
binding and the shapes of relay.status and relay.doctor are unchanged.
Source version metadata is not release availability proof; release availability
is established only by the accepted Git tag, uploaded GitHub Release assets, and
matching hash readback. Do not publish or recommend the bare PyPI name
relay-agent; that public namespace belongs to an unrelated project and is not
the HarperZ9 Relay distribution.
Security
- An absolute
PATHentry could reach a folder a child works in: the server's
folder, the root of arun,test_cmdorcheck, the project a bisect
check copies, or the repository--auto-commitcommits to. The entry could
name the folder or a folder below it, spell it another way, or lead there
through a junction or symlink. On Windows a quoted entry such as
"C:\proj"\binreached it too, because cmd.exe drops the quotes. A program
planted there ran in place of the installedclaude,codexorgit,
including the--versioncheck thatrelay.doctorstarts under exec, and a
shell child found it by bare name. Affected: every release through 0.4.0.
0.4.0 closed the direct search of the server's folder and left these routes
open. gititself ran with thatPATH, so a program git starts by bare name ran
from the repository: a clean filter the repository selects in
.gitattributes, orgpgwhen commits are signed. Affected: every release
through 0.4.0.- On Windows a drive-relative name such as
C:claudenamed a file in the
current folder of drive C:. relay's own tiers use fixed names, so this needed
a caller that builds aCliBackendwith such a name. It is now refused with
BAD_PATH.
Changed
- The vendored
safe_spawnis 1.0.1 (src/relay/_vendor/safe_spawn.py,
SHA-256 recorded inVENDORED.sha256). It drops everyPATHentry that
reaches the server's folder or the folder named for the child, from the
lookup and from the child'sPATH. Folders are compared by name and by file
identity after links are resolved. run,test_cmdandcheckhand their root to the helper. A bisect check
runs in a fresh copy of the project, so it hands the helper the project it
copied.--auto-commitresolvesgitwith the repository as the named folder, and
starts it with aPATHguarded the same way. git keeps the rest of its
environment (HOME,GNUPGHOME,SSH_AUTH_SOCK, itsGIT_variables). One
lookup serves every git call of a commit.- A command in
run,test_cmdorcheckthat found a program through a
folder inside the root or the server's folder, such as a project's
.venv/binornode_modules/.bin, now needs that program's path
(.venv/bin/pytest). So does a git hook or filter that found a program that
way during--auto-commit. The folder that holds the interpreter relay runs
on stays onPATH, so starting relay from the project's virtual environment
keeps that environment's tools (.venv/bin, or.venv\Scriptson Windows).
Tools in other folders inside the project, such as a Windows conda
environment'sScriptsandLibrary\bin, need their path. On Windows the
Windows, System32 and SysWOW64 folders also stay. - A child's
PATHnames each kept folder as its real folder, with links
resolved, so a link repointed after the check cannot change what starts. On
merged-/usr Linux,/binreaches a child as/usr/bin. - On Windows,
PATHis read the way cmd.exe reads it, quotes included. An entry
whose folder name holds;is left out, since Windows programs disagree on
how to read it. - On POSIX, a
PATHthat the filter leaves empty reaches the child as
/bin:/usr/bin, because an emptyPATHmeans the current folder there.
Limits
- When the server's folder or a run's root is a filesystem root, or holds the
home folder, only an entry naming that folder itself leaves. The tool folders
below home, such as~/.local/bin, stay. - The check reads the filesystem before the start. Swapping the program file,
or a folder inside a kept folder, between the two still wins. That needs write
access to a folderPATHalready trusts. - On a filesystem without file indices, such as some network shares, every
PATHentry on the working folder's device leaves. A CLI there needs
RELAY_CLAUDE_CLIorRELAY_CODEX_CLI, and a shell tool there needs its
path. - Each start reads every
PATHentry, a step that took under 1 ms on 0.4.0.
On Windows, with 58 entries, one read took about 11 ms. Under WSL with the
WindowsPATHappended (67 entries, 58 of them under/mnt) it took 0.5 to
1.3 s, and under 1 ms with the/mntentries removed. Every shell child and
every CLI tier start pays at least one read. An--auto-commitreadsPATH
once for all its git calls, about 1.3 s there against 12 ms on 0.4.0. - The new tests ran on Windows 11 and on Linux (Ubuntu 24.04 under WSL2).
macOS was not run.