How AI Commander is secured — and what it is not
AI Commander runs shell commands on machines you own, on behalf of an AI. Nothing about that is intrinsically safe, so this page does not argue that remote MCP execution is safe in general. It describes exactly what this implementation does, where each trust boundary sits, and — in its own section — what it deliberately does not do. If a claim here cannot be checked, it should not be here.
sudo. And the agent
runs as root by default, which is the point and the risk at the same time.
1. The connection model: nothing to expose
The agent opens one outbound WebSocket to the relay over TLS (wss://) and keeps
it alive. There is nothing on the machine waiting for an incoming connection, so there is
no inbound port to forward, no SSH daemon to expose to the internet, and no VPN to join.
A firewall or NAT that already permits outbound HTTPS needs no change at all — which is the whole reason a
laptop, a NAS behind a router, or a GPU box in a colocation cage can be reached without opening anything.
Each session is a separate, fully isolated session instance — one session cannot read another's traffic — and a command's output is delivered only to the connection that issued it. Another connection on the same session never sees it. Encryption is in transit, to the edge, on both legs; it is not end-to-end, and the limits section says so without softening it.
2. Credentials: the code, the label, the window, the reset
A session code (AIC-XXXX-XXXX-XXXX) is generated on the machine from a high-entropy CSPRNG with
an unambiguous alphabet. It is the credential for that machine. It is stable across reboots and restarts, and
changes only when someone with local or admin access runs change-code.
| Credential | Stored as | Revocation |
|---|---|---|
| Session code | One-way peppered HMAC hash | change-code on the machine, or the desktop tray. |
| Two-character label | First two characters, plaintext | Only for a machine you linked to an account. |
| API keys | Salted hash | Revocable in the dashboard, immediately. |
| Agent tokens | Salted hash | Rotate automatically while the agent is idle, never mid-command. |
| Service tokens | Salted hash, shown once at creation | Revocable in the dashboard or over the API; auto-revoked on block, unlink, or code reset. |
| Passkeys | Public key + signature counter | Deletion requires a fresh, single-use step-up proof. |
The plaintext code is never stored. Every record is keyed by a one-way, salted (peppered) hash, so a database leak contains no usable codes and none can be brute-forced back out of it. The single readable fragment is the two-character label: when you link a machine to an account, the first two characters of its code are kept so the dashboard can tell your machines apart. Two characters out of a high-entropy code is a pairing fingerprint, not a secret — it cannot reconstruct the code and cannot be used on its own to reach anything.
What the one-hour window actually gates
During the first hour after a code is created or refreshed, anyone who knows it can run ordinary command and status calls without an account. That window exists so that connecting an AI client for the first time does not require a sign-up. It gates anonymous access and nothing else:
- It does gate anonymous use. After the hour, anonymous session-code access stops working.
- It does not gate signed-in accounts. An account that linked the machine keeps access until the owner blocks it or resets the code — the age of the code is irrelevant afterwards.
- It does change what a late first link means. Link within the first hour and access starts immediately. Link later and the account is saved but starts blocked pending operator approval, because a code that old may already have circulated.
- It never gates file transfer. Uploads,
remote_pullandremote_pushalways require a signed-in Pro account, in the first hour and after it.
Blocking, unblocking, and what a reset destroys
Control over who may act on a machine lives on the machine, not only in a web dashboard.
The owner can list every linked account and block or unblock one of them from the box itself. Emails are
masked server-side (lu****@wear****.com), so listing the accounts on a machine
does not disclose full addresses to whoever is at that terminal.
# Who can reach this machine?
sudo aicommander-agent list-admins
# Refuse one account, and stop it re-linking
sudo aicommander-agent block-admin <id>
sudo aicommander-agent unblock-admin <id>
# Remove everyone at once
sudo aicommander-agent change-code
A code reset destroys the whole relationship, deliberately and in one step: the old code stops working, every account link and every block for that machine is cleared, and every service token issued for that device is revoked. Unblocking or re-linking afterwards never resurrects a revoked service token — a replacement requires a fresh step-up and a new mint. The same is true in the other direction: blocking an account atomically revokes that account's tokens for that one device and nothing else.
3. What is retained — and what never is
The same invariant holds locally on the machine. The agent's journald output records operation type, the secure-exec executable basename when it is a short printable bare name, start and end, exit code and duration — never the command, argv, cwd, env, stdin, stdout, or stderr.
Transferred files: the explicit exception
Moving a file means holding its bytes, so file transfer is the one place where something is stored, and its terms are fixed and short.
| Rule | Value | Detail |
|---|---|---|
| Per-file size | 100 MiB maximum | One regular file. Larger artifacts belong in your own storage. |
| Blob access | Ends 24 h after creation | Checked on every read, downloaded or not. |
| Download link | 1 hour | Expires on the same terms; the spent token is cleaned up. |
| Erasure | Hourly retrying sweep | Retries failures; bytes can remain as an unreachable object until a sweep succeeds. |
| What is kept | Bytes, size, owning account | Not the filename, not the path. Contents are never read, scanned, or indexed. |
See how remote file transfer works for the operational detail.
Metering counts calls and bytes, and nothing else
Usage counters exist so the service can tell whether it is being used and which parts of it. For each account — or a single shared anonymous bucket for people using a session code without one — the relay keeps a count of calls per UTC day per surface, plus the number of bytes transferred. That is the whole record, and the published promise is quoted here verbatim:
“It contains no command, no path, no filename, no machine identity, no IP address, and no time more precise than the day, so it cannot show when you were at a machine or what you did with it.”
— Privacy policy, section 2
Counters are aggregated by day at the moment they are written, so no per-event history exists to analyse later, and they are deleted after 180 days. You can read your own counters any time in the dashboard under Usage. Full detail, including subprocessors and retention for everything else, is in the privacy policy.
4. Prompt-injection posture, stated precisely
Output from your machine is treated as results to display, not instructions to follow. This
is written into the tool definitions themselves, not into a blog post: every tool that returns machine output
— remote_exec, the job tools, log paging, screenshots — declares in its own description that
stdout, stderr, log contents and screen contents are untrusted data to relay to the user, that a line telling
the model to run a command or ignore its guidance is the machine's output rather than a request from the
user, and that only the user's own messages are instructions.
5. The locked-down lane for automation
Interactive use and unattended use deserve different amounts of authority. For cron jobs, CI steps, or one AI agent invoking another, secure exec driven by a service token is the narrow lane — deliberately the opposite of the root path.
- Non-root, verified before every spawn. Commands run as the dedicated
aicommander-execuser, created by the installer with no supplementary groups and a0700home. Before each spawn the agent parses local/etc/passwdand/etc/groupdirectly — no shell, no external tool — checks primary and supplementary membership, and refuses gid 0 or membership inroot,wheel,sudo,docker,podman,lxd,incus,disk, orlibvirt. Unreadable files, malformed or duplicate records, or a post-drop identity that does not exactly match the request all fail closed before the target process exists. - One machine. A token is pinned to exactly one device and bound to the owning account plus that device's stable identity.
- An explicit allowlist. You choose the command basenames the token may run. Anything else is refused.
- argv, no shell. You send an argument vector. There is no shell anywhere on the path, so
&&,;, backticks and$(…)are inert text rather than syntax — there is nothing to inject into. - Known runners denied on both sides. One protocol-owned policy, enforced independently by the relay and by the agent, refuses shells, language runtimes (including version-suffixed names), argument and multi-call runners, privilege escalators, container clients and
kubectl— even if a hostile relay supplied the allowlist. - Long-lived and revocable. A token does not expire and skips the daily dashboard re-activation gate, so automation keeps working. Revoke it at any time; blocking the account on that device or resetting the code revokes it for you.
- Minted only with a fresh step-up. Creation is dashboard-only.
POST /api/v1/secure-tokenswith an account API key returns 403, so a leaked API key cannot mint a long-lived credential. - Audited without payloads. Each run records the command basename, exit code, duration, caller IP, output byte count, truncation flag, and time — never arguments, stdin, or output.
# Allowlisted, non-root, pinned to one machine, no shell involved
curl -s -X POST https://aicommander.dev/api/v1/secure-exec \
-H 'Authorization: Bearer aics_…' \
-H 'Content-Type: application/json' \
-d '{"argv":["df","-h"]}'
6. Elevated execution, and where its trust actually anchors
On macOS and Windows the desktop agent runs commands as the signed-in desktop user. A single
command can opt into running as root (macOS) or LocalSystem (Windows) with
elevated: true, which is handled by a separate per-machine privileged helper
rather than by the agent itself. The properties that matter:
- Account-authenticated only. Anonymous session-code callers are rejected outright.
- Opt-in, per command. Nothing else about the session changes; only the flagged command elevates. Detached jobs have no elevated option at all.
- Requires the helper to be installed. It ships with the macOS
.pkgand the Windows per-machine installer. Without it the request fails closed — never a silent unprivileged fallback. - Relay-signed Ed25519 capabilities. Each elevated request carries a short-lived capability signed by the relay. The helper verifies it against a public key pinned into the helper build; the private half exists only as a Worker secret. A process running as the same local user cannot forge one.
- Bound to this machine and this boot. The helper generates a per-boot instance nonce that the relay binds into every capability, and refuses any capability that does not carry it. Restarting the helper invalidates every outstanding capability.
- Single-use, with an expiry. A replay guard remembers request identifiers until they expire, so a captured live capability is usable at most once. Signature, shape, expiry, protocol-version floor, boot binding and replay are all checked, and any failure throws rather than degrading.
- Signed before it is trusted with SYSTEM. On Windows, release CI verifies the packaged helper's Authenticode signature and timestamp, and the installer independently requires a valid signature from the exact pinned publisher before registering or starting its SYSTEM task.
- Not available on Linux. The Linux agent advertises no elevated helper and the relay rejects the flag there — its systemd service already runs every command as root.
HKCU, no mapped drives. Use elevation
for machine-wide operations — installs, service control, system files — not to impersonate the desktop user.
Work that needs a real user context will still fail as root or SYSTEM.
7. Supply chain and install integrity
A remote-exec agent is only as trustworthy as the bytes you installed, so the release path is signed end to end and the convenient-but-unverifiable install is refused rather than offered.
| Artifact | Signature | Enforced by |
|---|---|---|
| macOS app / pkg | Apple Developer ID + notarization | Release CI verifies the signed, notarized payload before upload; there is no unsigned fallback. A built .pkg is notarized and stapled separately. |
| Windows installer & helper | Authenticode | CI verifies the signer before upload; the installer re-checks the helper's signature and publisher before granting it SYSTEM. |
| Linux agent binary | Detached Ed25519 + SHA-256 | The verified installer enforces both before installing anything. |
| Linux installer script | Detached Ed25519 | You verify it as an unprivileged user, before sudo ever runs. |
| GitHub Release | Complete SHA256SUMS | Generated after rejecting filename collisions, covering every published artifact. |
curl … | sudo bash one-liner executes mutable website content as root before it can
authenticate itself, so that instruction was withdrawn rather than documented. The supported flow downloads
the versioned installer and its signature, verifies the signature as an unprivileged user, and only then runs
the verified script under sudo; that script pins its release and enforces both the checksum and
the signature of the binary it fetches. A missing OpenSSL 3, LibreSSL, a missing
pkeyutl -rawin, a missing key or signature, or a failed verification all abort.
Copy the verified install block →
Release workflows pin every external action to a verified 40-character commit SHA, install with a frozen
lockfile, default to contents: read, and never grant write permissions or secrets to a pull
request. The npm packages publish through tokenless OIDC trusted publishing, so there is no long-lived npm
credential to steal.
aicommander.dev is
same-origin with the artifacts it is meant to authenticate: if this site or its CDN were compromised, an
attacker could change the instructions and the fingerprint together. Confirm the fingerprint through a
channel independent of this website. Two related limits, stated rather than hidden: the latest-version
metadata is unsigned, so replay of an older correctly signed release is not prevented; and provenance
attestations require a public source repository, which this one is not, so releases publish without them.
8. Limits — what AI Commander is not
A security page that lists only strengths is marketing. These are the limits as they stand today. They are not a roadmap tease; assume each one is true until this page says otherwise.
- This is real access to a real machine, not a sandbox. It is remote shell access. There is no container, no jail, and no filesystem isolation around an interactive command.
- The agent runs as root by default. On Linux it installs as a systemd service running as root, because that is what the workflows people install it for require. Running it as an ordinary user is supported and is the way to restrict what commands can do — but the default is root, and defaults are what most people run.
- The session code is the credential. Anyone who knows a current code can request commands while access is allowed. Guard it exactly like an SSH private key. There is no second factor on the code itself.
- No command-level policy for interactive use. Allowlists exist only in the secure-exec lane. On
remote_execthere is nothing that decides one command is permissible and another is not. - No teams, no per-user RBAC. Access is per account and per machine: linked or blocked, plan-restricted or not. There are no roles, no groups, no per-seat team tier, and no way to give one person read-only access.
- No per-user audit log of ordinary commands. This is the direct cost of never storing payloads, and the trade is deliberate. The only audit trail that exists is for the secure-exec lane, and it records basenames and metadata, never arguments or output. If you need a per-operator command history, this does not produce one.
- No self-hosted relay. One hosted relay, no on-premise option. It runs on Cloudflare, which terminates TLS at its edge, so command text and output traverse Cloudflare's infrastructure in transit and the relay holds them in plaintext in memory long enough to forward them — never on disk, never in any store, and no subprocessor receives a persisted copy. Encryption is in transit only; this is not end-to-end. Your AI client is an endpoint too, and it reads that plaintext regardless of transport.
- Prompt-injection posture is an instruction, not a boundary. See section 4. It is a real default and a real limit at the same time.
- Older agent binaries still authenticate over a URL parameter. Current clients mint a random, single-use, 30-second ticket instead. A legacy route remains temporarily enabled so an auto-deployed relay can precede released clients, and it will be switched off and then removed once the fleet has moved. Until then, keep your agents updated.
- Saved machines have a ceiling of 100*. It is an abuse and safety limit in the code, not a plan step. See what each plan includes.
* 100 machines is a technical ceiling, not a policy limit. Need more? Get in touch — we'll sort it out.
Security FAQ
Is it safe to let an AI run commands on my server?
It is exactly as safe as giving that AI a root shell, because that is what it is. AI Commander is remote shell access, not a sandbox: the Linux agent runs as a systemd service as root by default, and a command it accepts runs with that authority. What the design does is make the access bounded and accountable rather than pretend it is harmless — the machine opens no inbound port, the session code is a revocable credential you can reset at any time, an account can be blocked from the machine itself, commands and their output are never logged or stored, and unattended automation has a separate non-root lane limited to an allowlist of commands. If you need a policy engine that decides which commands are permissible for an interactive operator, that does not exist here today; the honest control is choosing which machines you install the agent on and who holds the code.
Does AI Commander open a port on my machine?
No. The agent makes one outbound WebSocket connection to the relay over TLS and keeps it open. Nothing on your machine listens for incoming connections, there is no inbound port to forward, no SSH to expose, and no VPN to join. That is why it works behind a firewall, a NAT, or a corporate network without touching their configuration: from the network's point of view the machine is only making an outbound HTTPS connection.
Do you store my commands or their output?
No, and this is a guarantee rather than best effort. Command text and stdout/stderr are processed only transiently in the relay's memory while they stream through, then discarded — they are never written to logs or to any database, and Cloudflare observability and logpush are deliberately left off so the platform does not retain them either. The agent's own local journald output carries no payload either: it records operation type, start and end, exit code and duration, never the command. Files you explicitly transfer are the one exception, because moving a file means holding its bytes: each file can be up to 100 MiB, access to the relay blob ends 24 hours after it is created, its download link lasts 1 hour, and an hourly retrying sweep removes the expired bytes afterwards.
What happens if someone gets my session code?
Treat that as you would a leaked SSH private key: the code is the credential for the machine. Anyone who knows a current code can run commands on it — anonymously during the code's first hour, or through an account at any time once that account is linked and not blocked. There are two responses, both driven from the machine itself. Block the specific account with aicommander-agent block-admin, or the desktop tray's Linked Accounts, which refuses that account's access and stops it re-linking. Or reset the code with aicommander-agent change-code, which destroys everything at once: the old code stops working, every account link and block for that machine is cleared, and every service token issued for that device is revoked.
Can a malicious log line hijack the AI into running commands?
The design treats output from your machine as results to display, never as instructions to follow, and every tool says so in its own description: stdout, stderr, job logs, and screen contents are declared untrusted data to relay to the user, with the explicit rule that only the user's own messages are instructions. That is a real and deliberate posture, and it is not a guarantee. It is an instruction to a language model, not an enforcement boundary — a sufficiently persuasive payload in a file, a log line, or a build output can still influence a model that reads it. Nothing in the relay inspects or sanitises the text, because nothing in the relay stores or parses it. Treat prompt injection as a residual risk you manage by what you point the AI at, not as a solved problem.
Does the agent have to run as root?
On Linux it runs as root by default, because the workflows people install it for — package installs, service control, reading system files — need that. You can run the agent as an ordinary user instead and every command inherits that user's authority, which is the supported way to restrict what remote commands can do. On macOS and Windows the desktop app runs commands as the signed-in desktop user, and root or LocalSystem is opt-in per command through a separate privileged helper. For unattended automation there is a third answer: a service token runs commands as the dedicated non-root aicommander-exec user with no supplementary groups, and the agent refuses to spawn at all if that identity cannot be resolved and verified.
Can I self-host the relay?
No. There is one hosted relay at aicommander.dev and no self-hosted or on-premise deployment today. The consequence is worth stating plainly rather than burying: the relay runs on Cloudflare, which terminates TLS at its edge, so your command text and its output pass through Cloudflare's infrastructure in transit and sit in the relay's memory in plaintext long enough to be forwarded. Both legs are encrypted in transit; the path is not end-to-end encrypted. What is guaranteed is what happens next: nothing is kept. Command text and output are never written to disk, to a log, or to any database, Cloudflare observability and logpush are deliberately left off so the platform does not retain them either, and no subprocessor receives a persisted copy of them. The only bytes that persist anywhere are the files you explicitly transfer, and only for their stated TTL — blob access ends 24 hours after creation, the download link lasts 1 hour, and an hourly retrying sweep removes the expired bytes. If your requirement is that no third party's process ever holds the plaintext, this is not the right tool — and be aware that the AI client itself is an endpoint that reads your output in plaintext regardless of transport.
How do I verify the installer before running it as root?
Piping the installer into a shell is intentionally unsupported: that would execute mutable website content as root before it can authenticate itself, so the canonical curl-pipe-sudo instruction was withdrawn rather than documented. The supported Linux flow downloads the versioned installer and its detached Ed25519 signature as an unprivileged user, verifies the signature against the pinned public key first, and only then runs the verified script with sudo; that script pins its release and enforces both the SHA-256 checksum and the Ed25519 signature of the agent binary it fetches. Missing OpenSSL 3, LibreSSL, a missing key or signature, or a failed verification all abort rather than fall back. The copy-paste block is on the connect page under Install the agent. One caveat we will not hide: confirm the key fingerprint through a channel independent of this website, because a fingerprint you copied from the same origin that served the payload cannot protect you if that origin is compromised.
Reporting a vulnerability
Found something? Email support@coderai.dev — the same address handles security reports and privacy requests. Include enough detail to reproduce; you will get a human reply.
Read the rest of the model, then decide
The docs carry the component-by-component table, the privacy policy carries retention and subprocessors, the connect page carries the verified install, and the comparison pages check this model against the alternatives.
Security model published 2026-08-26 · Current agent version at publication: 1.0.56 · Reviewed against the July 2026 internal security review · Per-file transfer maximum: 100 MiB · Blob access: 24 h · Download link: 1 h · Usage-counter retention: 180 days · Machine ceiling: 100* · Report an issue: support@coderai.dev