fix(acpmux): delivered machine secrets reach agents started after delivery

Merged
TE
6d ago
+191-6

What

A secret delivered to a Cloud machine with cmux cloud deliver-secrets (it writes ~/.config/cmux/machine-secrets.env, mode 600) now reaches agents that acpmux starts on that machine afterwards.

Why it didn't

#27487 already re-reads the login shell before a spawn once the secrets file's metadata changes, so the delivered value got into acpmux's imported login environment. After that, login_env::allowed_with let a child have only PATH, proxies, version managers, ANTHROPIC_*/CLAUDE_*/DISABLE_* (Claude), OPENAI_*/CODEX_* (Codex) and loginEnvAllow, and storable dropped every other name from memory. So a delivered DEMO_KEY, or any name outside those prefixes, never reached an agent. A fresh login shell on the machine had it, but /proc/PID/environ of the cmux-claude-real processes did not. The /usr/local/bin/claude wrapper never sources the secrets file.

Change (cmux-tui/crates/acpmux/src/login_env.rs)

  • When acpmux reads the login shell, it now also reads the names (only the names) exported in ~/.config/cmux/machine-secrets.env, in the receiver's export NAME='…' format. Like the applier, it reads the file only if it is a regular file owned by this user and at most 4 MiB, opened with O_NOFOLLOW|O_NONBLOCK. The buffer is zeroed when dropped, and the values still come from the shell.
  • Those names pass the allow-list for local children.
  • A delivered name always counts as secret, so login-env.json keeps its name and never its value, even when the name doesn't look secret.
  • The remote-origin path (apply_remote_tokio) is unchanged and still passes only PATH.
  • Revoking works: the next import doesn't list the name and the shell doesn't export it.

Tests

  • Unit delivered_names_reads_only_export_names: reads export names only, dedupes them, refuses a symlink, and returns nothing when the file is missing.
  • Integration a_secret_delivered_after_start_reaches_the_next_agent (tests/claude_auth_env.rs, real daemon plus a fake claude):
    • the secret is delivered after the daemon has started, and the next agent gets DEMO_KEY;
    • an undelivered GITHUB_TOKEN stays out;
    • the cache doesn't hold the dummy value;
    • after revocation the next agent doesn't get the key.

Risk: low. It widens what a local agent inherits, but only to names the person explicitly delivered to this machine for its shells and agents. No UI change.

šŸ¤– Generated with Claude Code

Agent session: agent session