Separate Unix Accounts for AI Coding Agents Safely
Separate Unix accounts for AI coding agents limit remote access, isolate credentials, tighten SSH, and make agent actions easier to audit.

Giving an AI coding agent your own Unix login is a shortcut with a long tail. The agent inherits files you forgot existed, credentials that tooling cached years ago, permissive SSH configuration, deployment scripts, and the ability to make a bad change look exactly like your ordinary work. A dedicated remote account does not make an agent harmless, but it makes its authority visible and containable.
Treat the account as a boundary around a specific job. If the agent needs to edit one repository, run its test suite, and push a branch, create an identity that can do those things. Do not start with your developer account and try to subtract privileges later. Unix permissions accumulate through groups, mounted directories, shell configuration, and tools that assume a human is in charge.
A separate Unix account gives the agent a different identity
A dedicated account gives the agent a distinct UID, process owner, home directory, SSH authorization set, and audit trail. Those five properties matter more than a clever prompt telling an agent to stay inside a repository.
When an agent runs as alex, every process it starts belongs to alex. That process can read whatever alex can read. It can use alex's SSH agent if forwarding or sockets expose it. It can inspect the shell history, Git credentials, cloud configuration, package manager caches, and project directories owned or readable by alex. Even if the agent behaves perfectly today, you have made its future permissions depend on every convenience you add to your own account.
An account such as agentbuild makes the starting point inspectable:
id agentbuild
getent passwd agentbuild
sudo -u agentbuild sh -lc 'umask; pwd; env | sort'
On a typical Linux host, the first command should show a UID and a small group list. The second should show a home directory such as /srv/agentbuild or /home/agentbuild, not a human developer's home. The final command catches an overlooked problem: inherited environment variables can point at credential files, proxies, token caches, or unusual executable paths.
This distinction is routinely blurred: a separate SSH key is not a separate account. A new key that logs into your existing account only changes authentication. It does nothing to reduce the files, commands, or network configuration available after login. Separate authentication and separate authorization solve different problems.
Use a stable account for a stable function. If one agent builds pull requests and another deploys release artifacts, give them different identities. You can then answer an uncomfortable operational question without guesswork: which account changed this file, opened this network connection, or created this process?
The account boundary does not contain every kind of damage
A Unix account limits access controlled by Unix ownership, groups, access control lists, and permissions. It does not automatically limit network destinations, CPU use, disk exhaustion, kernel vulnerabilities, access to world-readable files, or access granted by a shared service credential.
That boundary still earns its keep. A coding agent often has enough ability to alter source, run package scripts, read configuration, and use Git. Package scripts can execute arbitrary shell commands. Build systems may read environment variables. A compromised dependency can do the same. You should assume that any code the agent asks the host to run receives the permissions of the account that runs it.
Do not confuse account separation with a container, a virtual machine, or a network firewall. They have different jobs:
- A Unix account separates local files, process ownership, and ordinary command permissions.
- A container can constrain filesystem views and resource usage, but a badly mounted host directory defeats that benefit.
- A virtual machine gives a stronger operating system boundary when the workload warrants it.
- Network controls decide where the account can connect and what can leave the host.
Choose the smallest combination that matches the consequence of failure. For a disposable test checkout, a dedicated account on an isolated worker may be enough. For a production deployment host, use the account boundary with restricted SSH, narrow deployment credentials, logging, and an egress policy. If the agent can reach production databases or signing material, a separate UID alone is plainly insufficient.
A popular but wrong recommendation says to create an account and add it to the same operational groups as the developer "so builds work." That recreates the original problem under a different username. Groups such as docker, libvirt, backup groups, device groups, and privileged log groups can carry authority far beyond their friendly names. On many systems, membership in docker effectively allows control of the host because a member can start a container with the host filesystem mounted.
Create an account with an empty inheritance trail
Create the remote account with no password login, no administrator group, and a home directory that contains only files you intentionally place there. Start empty because copied dotfiles are a common source of accidental authority.
The exact commands differ by operating system. On a Debian or Ubuntu style host, an administrator can create a local account with a dedicated home directory like this:
sudo adduser --disabled-password --gecos '' --home /srv/agentbuild agentbuild
sudo passwd -l agentbuild
sudo install -d -m 700 -o agentbuild -g agentbuild /srv/agentbuild/.ssh
sudo -u agentbuild touch /srv/agentbuild/.hushlogin
--disabled-password prevents normal password authentication for the new account. passwd -l makes that intent explicit on systems that support password locking. Do not treat either setting as your only SSH control: the SSH server has its own password authentication settings, and an existing key can still authenticate if you install one.
On systems with useradd, use options that create the home directory and a private group, then verify the result rather than trusting memory. BSD and macOS account management use different tools, so consult the local dscl, sysadminctl, or system administration manual instead of pasting Linux commands into a different host.
Check ownership immediately:
namei -l /srv/agentbuild/.ssh
sudo -u agentbuild sh -lc 'touch ~/permission-test && ls -ln ~/permission-test'
sudo rm /srv/agentbuild/permission-test
The namei output walks each path component. No parent directory should grant an unrelated group write access, because group write access can let someone replace the .ssh directory or manipulate files beneath it. The test file should show the account's numeric UID and primary GID.
Do not clone your .bashrc, .zshrc, .gitconfig, or editor directory into this home "to save time." These files often add private package registries, helper aliases, SSH settings, credential managers, and shell hooks. Add individual settings after you can explain why the agent needs them. A plain noninteractive shell should be the default for remote automation anyway.
Set a conservative creation mask in the agent's execution environment. A umask of 077 makes new regular files private to the account unless a command explicitly chooses different permissions. That will occasionally expose a build assumption. Good. Fix the build's sharing point deliberately instead of making every generated file readable by every local user.
Shared repositories need a designed ownership rule
The agent should work in a checkout it owns, or in a project directory whose group and permissions you can explain. A directory where developers, deployment tools, and agents all write as unrelated users becomes impossible to reason about after the first rushed permission fix.
The cleanest pattern is a checkout owned entirely by the agent account. A human reviews changes through version control or reads files with controlled group access. This removes most local collaboration confusion, and Git gives you the handoff mechanism that actually matters.
Sometimes the agent must write into a common build tree. In that case, create a dedicated project group and set the group ID bit on the shared directory so new files inherit the group:
sudo groupadd projectbuild
sudo usermod -aG projectbuild agentbuild
sudo install -d -m 2770 -o releasebot -g projectbuild /srv/project-build
sudo setfacl -m u:agentbuild:rwx /srv/project-build
sudo setfacl -d -m g:projectbuild:rwx /srv/project-build
This example needs adjustment for your ownership model. The point is not that access control lists are fashionable. The point is that you should name the collaboration surface and limit write access to that surface. Do not respond to a permission error by running chmod -R 777 or by changing an entire source tree to a shared administrator group. Both approaches hide the failure until someone writes somewhere they should not.
A subtle failure appears when a build directory contains symlinks. The agent may have write permission to /srv/project-build, while a symlink inside it points into /etc, a release directory, or a human home. Inspect setup scripts and generated directories before granting broad recursive access. The account boundary only protects paths the filesystem actually evaluates under that account's permissions.
Git has a related ownership check. Modern Git may reject a repository that appears owned by another user as "dubious ownership." Do not solve that by adding arbitrary directories to global safe.directory settings for the agent. Make the checkout belong to the account that runs Git. If a shared checkout is unavoidable, document its ownership and add only that specific path after you understand why Git rejected it.
SSH access should identify the controller and limit the session
Use a dedicated SSH public key for the agent controller, then bind restrictions to that key in authorized_keys when the workflow permits it. A separate account with an unrestricted interactive shell is better than sharing a developer login, but it still gives an autonomous process a large command surface.
OpenSSH documents the available controls in sshd_config(5) and authorized_keys. PasswordAuthentication no disables password logins at the server level. AllowUsers can limit who may log in. Per-key options can disable port forwarding, agent forwarding, X11 forwarding, and pseudo terminal allocation. Those options are ordinary controls, not an exotic SSH setup.
For an agent that only needs to receive a Git command or run a fixed wrapper, an authorized_keys entry can look like this:
restrict,command="/usr/local/libexec/agent-git-wrapper" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... agent-controller
The restrict option is OpenSSH shorthand that turns off several forwarding and session features, subject to the server version and manual page. The forced command means the server runs the wrapper instead of accepting the command the client requested. Test your installed OpenSSH version before relying on an option, because older hosts may not support every restriction.
The wrapper must validate its own inputs. A forced command does not turn a careless script into a security boundary. If it passes an unquoted client supplied command into sh -c, the client can often regain command execution. Keep the wrapper small, use fixed paths, reject unexpected arguments, and log the request.
For a general coding agent that needs a shell to inspect, edit, build, and test, forced commands may be too restrictive. Keep the dedicated key and account, disable forwarding you do not need, and limit source addresses where the network topology allows it. The remote account should never accept your personal SSH key merely because it is already on your laptop.
Inspect the effective SSH configuration, not only the file you edited:
sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin|allowusers|allowgroups'
ssh -vvv agentbuild@build-host true
The first command prints the server's effective settings. The second exposes the authentication path and rejected methods from the client side. Test with the actual agent key. Testing with your own key proves very little.
Sudo rules usually erase the protection you just added
Do not grant an agent account unrestricted sudo. agentbuild ALL=(ALL) NOPASSWD: ALL makes the separate UID mostly cosmetic, because any command or script the agent runs can become root.
People add this rule when a build needs one privileged action, then leave it because the build finally passed. That is how a narrow exception becomes permanent host control. If a task really needs elevation, first ask whether a root owned service, a deployment worker, or a separate administrator action should perform it instead.
The sudoers(5) manual warns that command matching has limits. Arguments matter. Wildcards can match more than administrators expect. Allowing an editor, an interpreter, a package manager, a shell script writable by the agent, or a command that loads configuration from a writable directory can lead straight back to arbitrary root execution.
A safer pattern uses a root owned wrapper with fixed behavior. Suppose an agent must restart one known service after placing an already reviewed artifact in a fixed directory. The wrapper should use absolute command paths, reject arguments, verify ownership and permissions of its inputs, and perform only that restart. Then the sudo rule names that wrapper exactly:
Cmnd_Alias AGENT_RELEASE = /usr/local/sbin/restart-project-service
agentbuild ALL=(root) NOPASSWD: AGENT_RELEASE
Place the rule in a file managed with visudo, and make both the wrapper and its parent directory owned by root and not writable by agentbuild. This does not guarantee safety. It makes the narrow claim testable: the account can execute one root owned program with no caller controlled command string.
If you cannot describe the allowed privileged action in one sentence, do not grant sudo yet. Split the work until you can. The inconvenience is a design signal, not a reason to paste a broad rule.
Credentials need their own boundary from the login account
A restricted account still becomes dangerous if its home directory contains a cloud credential file, a broad deployment token, or an SSH private key that reaches every host. Do not move your credential pile from your personal home into the agent home and call the job done.
Issue credentials for the action, scope, and environment that the agent actually needs. A source control credential that can push to one repository is different from a production registry credential. A deployment key limited to one host is different from a private key accepted throughout an estate. Keep those distinctions visible in account names, comments, and revocation records.
Avoid long lived secrets in shell environment variables. Environment variables leak easily through diagnostic output, child processes, crash reports, and process inspection rules that vary by operating system. They are sometimes unavoidable, but they deserve a short lifetime and a tightly controlled launch path.
Sallyport keeps HTTP and SSH credentials in its encrypted vault and executes the requested action without giving the credential plaintext to an MCP-capable agent. That is useful when the agent needs an authenticated call but should not receive a token file or private key in its remote account.
This creates two independent checks. The remote Unix account determines what the process can do on that machine. The action gateway determines whether an agent process can request a credential bearing HTTP or SSH action. Do not collapse these into one control: a credential gateway cannot repair a remote account that can read production files, and a limited account cannot stop an agent from using a token that was handed to it.
For SSH automation, avoid copying your personal private key into /srv/agentbuild/.ssh. Create a dedicated credential and restrict the server that accepts it. If the remote target supports forced commands or source restrictions, use them. If it does not, scope the account on that target instead. Revocation should mean removing one agent credential, not changing the key you use for ordinary administration.
Logs must let you distinguish intent from execution
Record the agent session, the account's commands where practical, and the changes it makes. Logs that only say agentbuild logged in will not help when you need to reconstruct whether a human requested an action, an agent proposed it, or a remote script performed it.
Unix already gives you useful evidence. SSH authentication logs identify the accepted key and source address. Process accounting or audit facilities can track execution, depending on the host. Version control records commits and changed files. Build logs record commands and artifacts. Keep timestamps synchronized so these records can be compared.
Do not rely solely on shell history. Noninteractive commands may not enter history, users can edit it, and an agent may run tools that spawn their own commands. Shell history is a convenience for debugging, not a trustworthy record.
A useful review asks four concrete questions after a run:
- Which controller authenticated as the agent account?
- Which commands or build jobs ran under that UID?
- Which files outside the intended workspace changed?
- Which remote systems and authenticated services did the process contact?
The answer to the third question catches permission creep early. Compare the account's writable paths against the intended workspace before an incident forces that inventory. A process list can also expose surprises: if a supposedly short build agent leaves background workers behind, those workers retain the account's permissions after the orchestration session ends.
Sallyport records agent sessions and individual actions from a hash chained encrypted audit log, and sp audit verify can verify the chain offline without a vault key. That record is strongest when you also preserve the remote host evidence that shows what the authorized SSH action did after it arrived.
A shared developer login fails in predictable ways
The failure pattern usually starts with a sensible request: let the agent run the same tests you run. The developer points the agent at an existing remote host and authorizes their normal SSH key because the checkout, package caches, and build dependencies already work.
The agent runs a test command. The test bootstrap reads the developer's environment and finds a registry token. A dependency install executes a postinstall script. That script can read the developer's home directory, inspect SSH configuration, use any accessible credential helper, and connect using the developer's existing network reach. Nobody needs to exploit a kernel bug. The process simply has the same authority as the developer.
Later, a deployment script fails because it expects a writable artifact path. Someone fixes it with a broad group change. The account now writes to a release directory. A second fix adds passwordless sudo because a service restart is blocked. By then, the supposed agent setup has inherited a developer identity, broad filesystem write access, reusable credentials, and root escalation.
A dedicated account changes the path of this failure. The initial test may fail because the account cannot read a registry configuration or write an old shared cache. That failure is useful. It tells you to issue a restricted registry credential, create an owned cache directory, or redesign the build. Each repair becomes an explicit grant that you can review.
Expect early friction. If the new account works perfectly on its first attempt against a mature developer setup, inspect it closely. It may mean that the host has already made too much data and authority available to every local user.
Test the boundary as the agent, then remove what surprises you
Test the account from a separate administrator session before allowing unattended work. Do not test by switching your shell prompt and assuming the identity changed cleanly. Authenticate with the dedicated key, use the actual startup command, and observe the result from outside the session.
Run this compact acceptance check after each material access change:
ssh -i ./agentbuild_key agentbuild@build-host 'id; umask; pwd; find ~ -maxdepth 1 -printf "%M %u %g %p\n"'
ssh -i ./agentbuild_key agentbuild@build-host 'sudo -n true; echo sudo_status=$?'
ssh -i ./agentbuild_key agentbuild@build-host 'find /srv/project-build -xdev -type f -perm -0002 -print'
The first line confirms identity, working directory, creation mask, and home permissions. The second should normally return a nonzero status, because the account should not have general sudo. The third searches the shared project directory for world writable regular files. On systems whose find lacks GNU -printf, use ls -ld and stat instead.
Then test denied actions on purpose. Attempt to read a human home directory, write outside the workspace, use a personal deployment key path, and open an SSH forwarding session if forwarding should be disabled. A restriction you never test is only an intention written in configuration.
Review the account after real jobs too. Remove stale authorized keys, group memberships, caches, temporary access control lists, and deployment permissions when the task no longer needs them. Permission cleanup rarely happens during a deadline. Put it in the completion criteria for the job.
Start by creating the account with no access beyond its own home and a test checkout. Add one capability only when a real command fails and you can state the needed permission precisely. That approach feels slower during setup. It is much faster than sorting out which parts of a developer login an autonomous process copied, used, or damaged.
FAQ
Why should an AI coding agent use a separate Unix account?
A separate Unix account gives the agent its own user ID, home directory, process ownership, SSH keys, and file permissions. It stops the agent from automatically inheriting every repository, credential file, shell setting, and command your personal login can reach.
Is a dedicated Unix account enough to sandbox an AI agent?
It helps, but it is not sufficient by itself. A Unix account does not restrict outbound network access, kernel attack surface, dangerous interpreters, or access granted through shared groups and writable directories.
Should an AI agent account have an SSH password?
Usually, no. Give the agent an account that has no password login, then authenticate the controlling system with a dedicated SSH public key and tight server-side restrictions. Passwords create another secret to store and another recovery path to defend.
What files should an AI agent Unix account be allowed to access?
Put only the repositories and generated working files that the agent needs in its home directory or a deliberately shared project directory. Do not mount your personal home directory under it, and do not make broad workspaces writable just because it is convenient.
Can I give an AI agent limited sudo access?
Avoid sudo unless a narrowly defined maintenance operation truly requires it. An unrestricted sudo rule cancels most of the benefit of the separate account, while a command-specific rule still needs careful review for argument injection and writable scripts.
Which Unix groups should an AI coding agent avoid?
No. Groups often carry more authority than people realize, including container access, device access, log access, and deployment permissions. Inspect every supplementary group after creating the account and remove any group that does not have a stated purpose.
Does a separate account prevent an AI agent from exfiltrating data?
No. An agent may need network access to fetch dependencies, call an approved API, or reach a source host. Account separation limits local identity; use firewall rules, egress controls, and a credential gateway when you must control where requests can go and what secrets they can use.
How should an AI agent deploy code without sharing my login?
The usual pattern is a dedicated account with a dedicated deploy credential limited to one repository, environment, or command path. Keep it separate from the credential used by a human administrator, and remove it when the job ends.
How do I test an AI agent account before using it in production?
Start with a nonproduction host or a disposable virtual machine, then inspect the account from another login. Verify its UID, groups, home permissions, authorized SSH behavior, writable paths, and process ownership before you let an agent perform unattended work.
How does Sallyport fit with separate remote accounts for agents?
Sallyport can keep HTTP and SSH credentials out of an MCP-capable agent while it performs approved calls through the app. That complements a separate remote account: Unix permissions constrain the remote host, while the gateway controls which credential-bearing actions the agent can request.