relay 0.4.0
The session store stays inside itself, and every program relay starts gets an
environment allowlist and, for the claude and codex tiers, a proven isolation
profile. This was scoped as a 0.3.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:
run,test_cmdandcheckno longer inherit the server's whole
environment. A command that reads a variable outside the allowlist, such as a
test suite that needsDATABASE_URLor a provider key, sees it unset until
RELAY_CHILD_ENVnames it.- On Windows a shell no longer finds a program in the root by bare name. Call it
as.\tool. - The
claudetier runs with--tools ""and thecodextier with a read-only
sandbox, both in a new empty folder. A setup that used either tier as a
sub-agent acting on the project gets a model reply only. - With
RELAY_SESSION_DIRunset,local_agent_sessionsreads a per-user folder
instead of the folder the server started in. local_agent_sessionsrefuses asession_idthat is not a bare name.- MCP file tools refuse writes into agent and editor configuration folders.
The MCP tool names, the relay.mcp-run-request/v2 binding and the shape of
relay.status are unchanged. relay.doctor and the session listing gain
fields.
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
local_agent_sessionsjoined the caller'ssession_idonto the session
store with no check."../outside/private"or an absolute path returned the
transcript of any ledger file the server's user could read, with no grant.
Affected: 0.2.0 through 0.3.0. Fixed: asession_idis a bare name, and a file
whose real path leaves the store is neither opened nor listed.- The
claudeandcodexCLI tiers started the CLI by bare name from the
server's folder, with every variable and no isolation flags. Aclaude.exe
planted in that folder ran instead of the installed one, and a
.claude/settings.jsonthere could run its hooks. Reaching a tier needed the
exec grant. Affected: every release through 0.3.0. Fixed below. run,test_cmd,checkand bisect checks inherited every provider key, so a
model with exec could print them into the ledger it returns. Affected: every
release through 0.3.0.
Changed
- The CLI tiers start through a vendored copy of
safe_spawn1.0.0
(src/relay/_vendor/safe_spawn.py, SHA-256 recorded inVENDORED.sha256): an
absolute executable, a new empty working folder, an environment allowlist, the
prompt on stdin, and the isolation profile the probes proved. Forclaude
2.1.251 that is--setting-sources user --strict-mcp-config --tools "". For
codex0.144.6 it isexec --ignore-user-config --ignore-rules --ephemeral --skip-git-repo-check --sandbox read-only --disable hooks --disable plugins --disable memories --disable apps -c project_doc_max_bytes=0 -c skills.include_instructions=false.RELAY_CLAUDE_CLIand
RELAY_CODEX_CLIoverride the executable and must be absolute paths. The
codextier now runs on a stock Windows npm install, where 0.3.0 could not
findcodex.cmd. The tiers still need exec. - A CLI tier whose isolation profile is not proven is refused with
EXEC_NOT_GRANTEDunless the launch also names it inRELAY_ALLOW_EXEC_CLI.
No such tier ships today. relay.doctoraddscli_tiers, one row per CLI tier:PASSonly when the
CLI resolves, exec is granted, the profile is proven and--versionreports
the tested version, andWARNwith a setup code (CLI_NOT_FOUND,
EXEC_NOT_GRANTED,CLI_PROFILE_UNPROVEN,CLI_VERSION_UNTESTED,
CLI_VERSION_UNKNOWN) otherwise. It starts--versiononly under exec and
never reports the resolved path.- Shell children get the platform base, a fixed set of toolchain variables
(VIRTUAL_ENV,JAVA_HOME,CARGO_HOMEand similar) and the names in
RELAY_CHILD_ENV.PATHkeeps absolute entries only, and on Windows
NoDefaultCurrentDirectoryInExePath=1stops cmd.exe searching the root. RELAY_CHILD_ENV,RELAY_ALLOW_EXEC_CLI,RELAY_CLAUDE_CLIand
RELAY_CODEX_CLIare launch-only. The remote entrypoint never takes them from
its env file and names any it finds there.- The session store defaults to a per-user folder:
%LOCALAPPDATA%\relay\sessions
on Windows,~/Library/Application Support/relay/sessionson macOS, and
$XDG_DATA_HOME/relay/sessionsor~/.local/share/relay/sessionselsewhere.
MCP runs cannot write it. - The session listing skips a file that will not load, a dangling link or a
name that is not a valid id, and counts them inskipped. A missing session
carries"code": "NOT_FOUND"beside its error text. - The
local_agent_sessionsdescription andsession_idschema say the id is
a bare name. - The stdio
relay.doctorreads an env file only whenRELAY_ENV_FILEnames
one. The remote entrypoint still reads.envfrom its folder. - MCP file tools refuse writes into
.claude,.codex,.cursor,.vscode,
.gemini,.agents,.opencodeand.mcp.json. --auto-commitresolvesgitto an absolute path, so agit.exein the
working folder never runs.
Limits
- The profiles are proven for the tested CLI versions only, by one probe run per
scenario on Windows. The Linux half of the probes has not run. codexhas no flag that removes its shell tool. Under the profile it can still
run read-only commands in its empty folder. Whether the CLI tiers become
model-only backends that no longer need exec is an open decision.- The shell is still not path-confined, and write is still a route to code
execution outside the refused folders.