| Version | Status |
|---|---|
| 3.x | Security fixes |
| < 3.0 | Unsupported |
Please use GitHub's "Report a vulnerability" form on the
Security tab
of this repository, or email vz@vz.team with the subject
uxon security:.
Expected acknowledgement within 72 hours. Coordinated-disclosure window is 30 days unless agreed otherwise. Please do not open public issues for security reports.
uxon is a privileged orchestrator on a shared host. The trust boundaries are:
-
Caller → launch user. uxon uses
sudo -iu <user>to forktmuxand the agent binary as a different OS user. Authorisation is enforced by the operator'ssudoersconfiguration; uxon never elevates beyond whatsudoersalready grants.The recommended team setup pairs each developer's shell user with a low-privilege launch user (
<user>_agent). A team-lead grant of the formlead ALL=(alice_agent,bob_agent) NOPASSWD: ALLlets the lead attach to and reap Alice's and Bob's agent sessions without gaining the developers' shell-user identity. This is the "supervision without impersonation" property used throughout the docs. It has three honest caveats the operator should be aware of:tmux attachis read–write by default. Once an operator attaches to a developer's running pane, keypresses are delivered to whatever process is in that pane. uxon does not enforce read-only attach. If a true read-only audit posture is required, attach withtmux attach -r(or wrap the superuser action in such an alias) and document that in the team's runbook.ForwardAgent yeswidens the boundary. If the developer ran the agent with SSH-agent forwarding into the<user>_agentaccount, the agent's process holds a live handle to the developer's SSH agent socket. An operator attached to that pane can use the agent socket to sign as the developer (without the private key ever being copied). The paired-account model protects the developer's SSH key on disk; it does not revoke ambient delegations the agent already holds. Avoid blanketForwardAgent yes; forward only for the duration of operations that need it.- Secrets stored inside the
<user>_agentaccount are reachable. Anything the agent has been handed and persisted to its home — long-livedOPENAI_API_KEY,~/.aws/credentialscopied in for convenience, session tokens cached under~/.claude/, or.envfiles in the project tree — is readable by anyone who cansudo -iu <user>_agent. Use ephemeral credentials (aws-vault-shaped helpers, short-lived tokens, per-session secret managers) rather than long-lived keys in agent home directories.
Note on the developer's own grant. The recommended
<dev> ALL=(<dev>_agent) NOPASSWD: ALLlets the developer run any command as<dev>_agent, including launchingtmuxdirectly on the per-user socket outsideuxon. A session started that way withoutuxon's session-name prefix is not surfaced by the TUI's scanner. This is intentional: the developer is the trust root on the host — they already have a shell, and could run agents under their own shell user withoutuxonever seeing them. The grant's job is to bound the agent's blast radius (a yolo run cannot touch~<dev>), not to constrain the developer. A command whitelist would not reduce the developer's attack surface; it would break every time an agent binary, launcher, or wrapper changed. Lead-side visibility is anchored to the OS account, not to theuxonprocess: the lead'sALL=(<dev>_agent)grant lets themsudo -niu <dev>_agent tmux lsand attach to whatever is running, prefix-matching or not. -
Per-peer authority. Cross-host operation does not delegate trust between peers. Each peer's
sudoersis evaluated independently by that peer's own SSH daemon. A compromised operator SSH key on host A grants only what host A'ssudoersgrants on host A; it does not propagate to host B. There is no shared-secret handshake, no central authority, no certificate chain that uxon installs across the fleet. To revoke an operator's reach on host B, editsudoerson host B; touching the central config or the operator's machine does not change what host B will accept. -
Allowed roots. When
allowed_rootsis non-empty, sessions cannot be started outside those paths. Whenallowed_rootsis empty (default), sessions can be started in any directory the launch user can write to. Operators who need a directory whitelist must setallowed_rootsexplicitly. New projects are created only undernew_project_root, which itself must be inside an allowed root. -
Git remote profiles. Repo creation is limited to the explicit
git_remote_profileswhitelist. Withauth = "token", uxon reads the PAT fromtoken_file(read bycreds_user), holds it in memory only for the duration of the API call, never logs it, and never echoes it in--dry-runoutput. -
Config writes. The TUI Settings screen rewrites
config/config.tomlin place via atomlkitround-trip. If the file is not directly writable, uxon shells out tosudo tee. The new content is staged in a temporary file and then atomically replaced.
- Sandbox escape from inside the agent binary. uxon does not
constrain what
claude,codex, orcursor-agentcan do once launched. Anything the agent's OS account can do, the agent can do. - The operator's
sudoersconfiguration. A misconfiguredNOPASSWD: ALLentry, or a<lead> ALL=(<dev-shell-user>) NOPASSWD: ALLthat defeats the paired-account model, is the operator's responsibility. - Container / VM isolation between users. uxon is a thin
wrapper over
tmux+sudo+ssh. It does not configure cgroups, AppArmor, seccomp, kernel namespaces, or per-UID network policies. - tmux configuration. uxon can apply a small set of tmux
setoptions (mouse, OSC-52 passthrough, extended keys, terminal-features) to the sessions it launches, layered on top of each launch user's own tmux config — off by default, enabled withtmux.manage_options = true. The option values come only from the resolvedconfig.toml(the shipped defaults or the operator's override), and a rejected option fails the launch rather than starting a degraded session. uxon never edits the user's~/.tmux.confor any file. - Centralised RBAC, SSO, or audit infrastructure. uxon is the
runtime layer beneath these — it emits structured audit events
to the host's platform log channel (journald native or
/dev/logsyslog; seedocs/audit-events.md) and the host's ownsudotrail covers cross-user invocations, but uxon is not a replacement for an enterprise audit pipeline.
uxon uses dedicated low-privilege Linux users (<user>_agent)
and sudo -iu rather than spinning up a container per agent
session. Docker / Podman are stronger isolation primitives, but on
a shared development host they add four kinds of operational cost:
- Bind-mount UID mapping. With naive
docker rundefaults, files created in the container come back owned byroot(or whatever UID was baked in) on the host, breaking save-and-edit. Rootless Podman with the:Umount option, and rootless Docker viasubuid/subgid, close this — at the cost of per-host setup that is itself non-trivial. - Networking. Anything the agent talks to on
localhost(a local DB, a model proxy, an internal service, anmDNS/.localhost) needshost.docker.internal,--network=host, or explicit port plumbing. SSH-agent forwarding needs socket bind-mounts and breaks across reconnects. - Auth duplication.
~/.claude,~/.gitconfig,~/.aws/,known_hosts, SSH keys — each has to be passed through, or the container becomes a second place to re-auth every agent. - Per-image maintenance. Tool updates → image rebuilds → push or share. For a team that just wants "Claude Code with the project's deps", this is extra operational work.
OS-user isolation removes those four at the cost of relying on Linux user separation rather than container primitives:
- Same kernel. A kernel-level escape from inside the agent
binary reaches the host. Containers narrow this surface via
default seccomp / AppArmor profiles;
<user>_agentdoes not. - Same network namespace. The agent can reach
127.0.0.1services on the host and scan the LAN.iptables/nftablesrules per UID can mitigate, but uxon does not configure them. - Same
/proc. Withouthidepid=2mounted on/proc, every user can see every other user's command lines and environments (not their memory).
The isolation <user>_agent actually provides is what regular
Linux UID separation provides: the agent cannot read files outside
its UID's reach, cannot signal another UID's processes, cannot
read another user's ~/.ssh/. That is enough when the host's
threat model is "developers on this team, plus their agents
running yolo by accident". It is not enough when you do not trust
the developers logging into the box.
If you need stronger isolation than that, run uxon itself inside a VM (or container) per team and keep the OS-user model inside it. The layers compose.
-
Run agents as a dedicated, low-privilege OS user. The paired-account pattern (
aliceshell user +alice_agentlaunch user) is the recommended team setup. Seedocs/reference/configuration.mdfor the full template. -
Audit
sudoinvocations against*_agentaccounts. uxon's own audit channel (journald / syslog) records who attached, who killed, and what was launched at the application level — seedocs/audit-events.md. For an authoritative OS-level record (and full keystroke I/O), pair it withsudo's own log via the following in/etc/sudoers.d/alongside the operator grants:Defaults!UXON_AGENT_OPS log_input, log_output Cmnd_Alias UXON_AGENT_OPS = /usr/bin/tmux, /bin/bash, /bin/shSee
examples/sudoers/audit.examplefor a complete fragment, including I/O log path layout and retention notes. I/O logs land under/var/log/sudo-io/<lead>/<timestamp>/by default. -
hidepid=2on/proc. Without it, every OS user can read every other user's command lines and environment via/proc. Mount withmount -o remount,hidepid=2,gid=<adm-group> /proc(and persist via/etc/fstab). -
ControlMasterfor multi-host operators. Without SSH connection multiplexing, every TUI refresh tick to a peer opens a fresh handshake — slow, noisy in the peer'sauth.log. Seedocs/explain/multi-host-philosophy.mdfor the recommended~/.ssh/configsnippet. -
Do not store long-lived credentials in
<user>_agenthome. Anyone who cansudo -iu <user>_agentreads them. Prefer ephemeral credentials (aws-vault-shaped helpers, short-lived tokens, OAuth refresh in a session-scoped agent). -
Avoid blanket
ForwardAgent yes. Forward only for the duration of an operation that needs it; the SSH agent socket is a live delegation of the developer's identity into whatever the agent is running. -
Keep
git_remote_profilesshort and explicit. Preferauth = "gh"(delegated to a logged-inghCLI) over storing a long-lived PAT on disk. -
Set
enable_all_users_list = falseunless multi-user inspection is genuinely required. The cross-user list relies onsudo -niuprobes; turning it off reduces probe traffic and the chance of accidental visibility. -
Restrict write access to
config/config.tomlto administrators. The TUI'ssudo teefallback is a convenience, not an authorisation model.