feat(runtime): deliver credentials as environment variables, not conversation text - #747
Open
gavin09527 wants to merge 1 commit into
Open
feat(runtime): deliver credentials as environment variables, not conversation text#747gavin09527 wants to merge 1 commit into
gavin09527 wants to merge 1 commit into
Conversation
β¦ersation text Item 2 of the agent-config-control-plan: credentials must reach the agent without their plaintext passing through the model context. Tools the agent drives in direct mode (gh, git, docker, kubectl, lark-cli) authenticate themselves, so credentials have to exist on the machine. The goal is therefore not "no credentials on disk" β that is unachievable β but "no plaintext credential in the model's context". A value that has been in the context has already been sent to the model provider, and no local deletion can recall it. The control plane drops one file per credential into `<ZYLOS_DIR>/security/credentials/`, named exactly as the environment variable. At launch these become real environment variables of the agent process, so the agent only ever handles the *name*: it runs `gh auth login --with-token <<< "$GH_TOKEN"` and the shell substitutes the value, which never reaches the transcript. Injecting at launch rather than through PM2 is deliberate: PM2 persists a service's env into `~/.pm2/dump.pm2` and prints it via `pm2 env <id>`, which would create extra plaintext copies in places nobody thinks to revoke. Security guard: `PROTECTED_NAMES` refuses PATH, HOME, SHELL, LD_PRELOAD, NODE_OPTIONS and friends. Without it, whatever can write into the secret directory could redirect every command the agent runs β a credential drop must not be a code-execution primitive. Every rejection is reported rather than silently dropped: a credential that quietly fails to load looks to the agent exactly like "this tool is not configured", which is a confusing way to discover a typo in a filename. Loose file permissions warn but still load β availability beats tidiness for a credential that is already on disk. Applies in compat mode too, so `ZYLOS_CLEAN_ENV=false` cannot silently opt a host out of credential delivery. Logs list injected names only, never values. Revocation stays: delete the file and restart. Removing a variable from a live process is not reliably possible, so the restart is the mechanism. Tests: 16 new (`cli/lib/__tests__/secret-store.test.js`) covering protected-name refusal, invalid names, empty files, trailing-newline handling, permission warnings, precedence over .env, and compat mode. Verified no regression by stashing: the 35 pre-existing suite failures are identical before and after. Note `node --test` alone fails on `mock.module`; use `node scripts/run-node-tests.js`, which passes --experimental-test-module-mocks. Still to come (not in this commit): the skill-side rule telling the agent to look for the env var name and never read the secret directory, plus manifest template docs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Item 2 of the agent-config-control-plan: credentials must reach the agent without their plaintext passing through the model context.
Why
Tools the agent drives in direct mode (
gh,git,docker,kubectl,lark-cli) authenticate themselves, so credentials have to exist on the machine. The goal is therefore not "no credentials on disk" β that is unachievable β but "no plaintext credential in the model's context". A value that has been in the context has already been sent to the model provider, and no local deletion can recall it.How
The control plane drops one file per credential into
<ZYLOS_DIR>/security/credentials/, named exactly as the environment variable (GH_TOKEN, content = the value). At launch these become real environment variables of the agent process, so the agent only ever handles the name:The
security/naming is a deliberate soft wall β current models' safety training makes them reluctant to cat files under such a path, lowering the chance of an accidental read. It is not a hard control; the hard guarantees remain server-side revocation and the provider-side kill switch.Injecting at launch rather than through PM2 is intentional: PM2 persists a service's env into
~/.pm2/dump.pm2and prints it viapm2 env <id>, which would create extra plaintext copies in places nobody thinks to revoke.Security guard
PROTECTED_NAMESrefusesPATH,HOME,SHELL,LD_PRELOAD,NODE_OPTIONSand friends. Without it, whatever can write into the secret directory could redirect every command the agent runs β a credential drop must not become a code-execution primitive. Enforced both at load and at apply.Failure behaviour
echo secret > FILEadds one and it would otherwise become part of the credential).ZYLOS_CLEAN_ENV=falsemust not silently opt a host out of credential delivery, or the agent would report tools as unconfigured for no visible reason.Revocation
Delete the file and restart the runtime. Removing a variable from a live process is not reliably possible, so the restart is the mechanism.
Testing
16 new tests in
cli/lib/__tests__/secret-store.test.js: protected-name refusal, invalid names, empty files, dotfiles, trailing-newline handling, permission warning, precedence over a same-named.envvalue, compat mode, and "a rejection is reported rather than silent".No regression β verified by stashing the change and re-running: the 35 pre-existing suite failures are identical before and after. Note
node --testalone fails onmock.module; usenode scripts/run-node-tests.js, which passes--experimental-test-module-mocks.Not in this commit
The skill-side rule (agent should look for the env var name and never read the secret directory) and the manifest/template docs.
π€ Generated with Claude Code