cmux machine vault: per-entry delivery policy (--host, --no-hosts, --env)

Merged
TE
3d ago
+545-18

Leo, 2026-11-03: "yeah let's do alllll of those things! :DDD", about closing this gap: vault values are put into the environment of a machine's agent processes, so any agent can read them and send them anywhere. Agents now get a placeholder (cmux-secret://NAME), and a local egress proxy puts the real value in request headers only toward each secret's allowed hosts. A per-entry opt-out keeps a real env var, labelled weaker.

This is the CLI part. It pairs with the acpmux egress proxy PR (branch lane/vault-egress-proxy) and the hub's vault policy endpoints.

Commands

  • cmux machine vault set NAME [--host H]... [--no-hosts | --env]: with a flag, the PUT body also carries delivery and allowedHosts. With no flag neither is sent, so the hub keeps the entry's policy (a new entry gets the hub's default for its name). The value still comes from the hidden prompt or stdin, never argv.
  • cmux machine vault policy NAME (--host H... | --no-hosts | --env): PUT /v1/vault/env/NAME/policy, which changes no value and no revision. vault.no_entry reads "your vault has no NAME", and vault.invalid_hosts names the host rule.
  • cmux machine vault ls: each entry ends with proxy: api.github.com, github.com, proxy: no hosts (placeholder only) or env (weaker: agents can read the value). The JSON output carries delivery and allowedHosts. An answer with no delivery (an older hub, which puts values in the env) reads as env.
  • Usage errors: --env with --host or --no-hosts, --host with --no-hosts, more than 16 hosts, and policy flags on other verbs. Each --host is lowercased and checked against the hub's pattern (a DNS name, optional *. and :port, with no scheme, path, IP or trailing dot) before anything is sent. Hosts in answers are checked the same way and dropped if they fail, never printed.

Tests

New tests in vault/tests.rs, using the local fake hub:

  • flag parsing and refusals, including bad hosts
  • set --host sends the policy and set with no flag sends none
  • policy PUTs the policy path in all three forms, never sends a value, and maps both error codes
  • ls renders all three kinds, and its JSON carries the fields

cargo test -p cmux-tui machine_join passes on a fleet Linux box, and so does cargo fmt --check. clippy reports nothing in these files; the clippy errors in automation.rs and teleport.rs are on base too.

Changelog

  • Added: cmux machine vault set --host/--no-hosts/--env and cmux machine vault policy choose how a vault value reaches a machine's agents, and vault ls shows it.

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Lets vault entries define a delivery policy so a machine's agents can't read secrets they don't need. cmux machine vault now takes --host H..., --no-hosts, or --env to choose between a placeholder replaced only for allowed hosts and the real env var, and ships a policy verb and policy-aware ls.

  • set NAME with a flag sends delivery and allowedHosts with the value; without flags the hub keeps the entry's policy.
  • policy NAME (--host H... | --no-hosts | --env) PUTs /v1/vault/env/NAME/policy without touching the value.
  • ls prints each entry's delivery (proxy: hosts, proxy: no hosts (placeholder only), or env (weaker: agents can read the value)), and its JSON carries delivery and allowedHosts; answers without either read as env (older hub).
  • Conflicting flags, more than 16 hosts, and policy flags on other verbs are usage errors; hosts are validated against the hub's pattern before anything is sent.