Repository navigation
fix(standard): give the relay's container its key as a file - #44
Merged
Merged
Conversation
harenome
force-pushed
the
fix/relay-key-file
branch
from
November 10, 2026 15:27
2bfe13b to
e2ce8ff
Compare
The docker backend passed the provider's key to each unit's relay as an environment variable, so docker inspect, and anything that dumps container configurations, showed it. The key is now written owner-only in a folder of its own under relay-keys/ in the user's cache, outside the unit's record. Colima and Lima share the user's home with their engine by default, but not the system's temporary folder, so that folder is not used. The file is bind-mounted alone and read-only into the relay's container and passed to the relay with --key-file. The relay no longer reads NULLIUS_RELAY_UPSTREAM_KEY. Nullius removes the folder once the relay listens, and on stop, which a failed start also calls. A job's sweep removes the folders its relays left. Each unit's start and each sweep also remove any key folder older than 10 minutes, which no start takes: a killed run leaves such a folder.
harenome
force-pushed
the
fix/relay-key-file
branch
from
November 10, 2026 15:50
e2ce8ff to
a6becb9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #42
What
On the docker backend, the relay sidecar got the provider's key as
-e NULLIUS_RELAY_UPSTREAM_KEY, sodocker inspectof the relay showed it. NowUnit.startwrites the key to a 0600 file in a new 0700 folder underrelay-keys/in the user's cache (paths.cache_dir(), soNULLIUS_HOMEand XDG apply), never in the record folder. Colima and Lima share the user's home with their engine by default, but not the system's temporary folder.relay_argvbind-mounts that one file read-only at/opt/nullius/relay-keywith--mount, which fails at start when the engine cannot see the source, and passes--key-file.relay servereads the key from that file and refuses a missing or empty file.Unitremoves the folder once the relay listens, and instop, which a failed start also calls.container.sweep(job), which runs at the end of every job on the docker backend, removes the job's key folders by name. Each unit's start and each sweep also remove any key folder older thanKEY_STALE(10 minutes). The local backend keeps the key in memory and does not change.Touches
backend · the standard · docs and site · CI and skills
app/nullius/standard/container.py:relay_argv(key_file=...),Unit._key_file,Unit._forget_key,key_root,forget_stale_keys,key_prefix,sweep,LISTEN_TIMEOUT,KEY_STALE, module docstringapp/nullius/standard/relay.py:serve --key-filedocs/standard.md: Safety table, the relay rowdocs/builder.md: Key and endpoint rowdocs/settings/model-providers.md: A run rowapp/nullius/execution.py: module docstring, how keys reach a run.claude/rules/running.md: Keysapp/tests/test_standard_container.py,app/tests/test_standard_relay.pyVerified
All in throwaway rootless containers, on this branch and on main (22140a6).
Search for a fake key after one
container.Unitrelay starts with it, on rootful Docker 29.9.3 indocker:dind:The run left no container behind.
Tests:
dockercall's arguments or environment hold the key.key_root()(0700). It is gone once the relay listens.KEY_STALEand keeps a fresh one.relay serveuses the key from its file and refuses a missing or empty file.docker inspectof the relay shows no key.1607 tests in 102 modules: OK262 tests in 35 modules: OKtest_standard_containerandtest_standard_relay:Ran 51 tests ... OKRan 51 tests, the two relay tests ofDockerTestfail. The relay runs asuid:gid, a subordinate id there, and stops at start withcannot read --key-file: [Errno 13] Permission denied. On main that engine already cannot run units (the same user cannot write the record folder).check_docs.py: 0 findings.Risk
The engine must see the user's cache folder, as it must already see the run's folders. If
NULLIUS_HOMEorXDG_CACHE_HOMEmoves the cache to a place the engine cannot see, the relay'sdocker runstops with "bind source path does not exist", and the unit fails before any call. It fails closed: the key is not exposed.The key stays readable inside the running relay's container, at
/opt/nullius/relay-key, until that container stops. Only access that is already root-equivalent (docker execinto the relay) reads it there.A Nullius process killed while a relay starts (SIGKILL, OOM) leaves one owner-only
<cache>/relay-keys/nullius-relay-key-<hash>-<random>/keyper unit that was starting. The next unit's start, or the next sweep, of any job removes it once it is older thanKEY_STALE(10 minutes).An engine that maps the container's user to another id (rootless Docker, or userns-remap) cannot read the 0600 file. The relay stops at start with
cannot read --key-file: [Errno 13] Permission denied, and no call is made. On main, such an engine already cannot write the record folder.Conflicts: this branch conflicts textually with #43 in
app/tests/test_standard_container.py(tests added side by side). Whichever merges second needs a rebase that keeps both sides. The merge passes the backend suite (1614 tests in 103 modules: OK) and the route suite.Undo with a revert.