Security model

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.

The one-paragraph version. The machine makes one outbound TLS WebSocket connection and listens for nothing. The session code is the credential, is never stored in readable form, and can be reset or blocked from the machine itself. Commands and their output are never logged or stored anywhere. Unattended automation runs in a separate lane: non-root, one machine, an allowlist of commands, argv with no shell. Releases are signed and the installer is verified before it ever sees 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.

Your machineagent, outbound only
Relay (on Cloudflare)TLS at the edge, in-memory forwarding
Your AI clientMCP or REST over HTTPS

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.

CredentialStored asRevocation
Session codeOne-way peppered HMAC hashchange-code on the machine, or the desktop tray.
Two-character labelFirst two characters, plaintextOnly for a machine you linked to an account.
API keysSalted hashRevocable in the dashboard, immediately.
Agent tokensSalted hashRotate automatically while the agent is idle, never mid-command.
Service tokensSalted hash, shown once at creationRevocable in the dashboard or over the API; auto-revoked on block, unlink, or code reset.
PasskeysPublic key + signature counterDeletion 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:

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.

Standing sessions cannot mint new power. Adding a passkey, creating a confidential OAuth client, approving OAuth from an existing dashboard session, creating an API key or a service token, and turning off the daily re-activation protection each require a fresh, single-use, action-bound step-up proof. A stolen session cookie on its own cannot turn itself into a long-lived credential.

3. What is retained — and what never is

Commands and their output are never logged or stored. 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/logpush is deliberately left off so the platform does not retain them either. There is no command history to subpoena, leak, or accidentally index.

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.

RuleValueDetail
Per-file size100 MiB maximumOne regular file. Larger artifacts belong in your own storage.
Blob accessEnds 24 h after creationChecked on every read, downloaded or not.
Download link1 hourExpires on the same terms; the spent token is cleaned up.
ErasureHourly retrying sweepRetries failures; bytes can remain as an unreachable object until a sweep succeeds.
What is keptBytes, size, owning accountNot 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.

What this is not. 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, and nothing in the relay inspects or sanitises that text — because nothing in the relay stores or parses it. We will not call this prompt-injection protection. It raises the bar and it is the right default; it does not make the risk go away. Manage it by choosing what you point the AI at.

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.

# 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"]}'
The denylist is not the containment mechanism. We are explicit about this in the code and will be here: a denylist cannot classify every program, and many useful binaries can spawn something else in some mode. The real boundary is the dedicated non-root uid/gid. Secure exec is also Linux-only, needs the agent running as root so it can drop privileges itself, and fails closed on any other platform. Setup detail: service tokens.

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:

LocalSystem and root are not an interactive admin user. The helper runs in session 0 with no user profile: no Keychain or TCC, no login keychain, no 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.

ArtifactSignatureEnforced by
macOS app / pkgApple Developer ID + notarizationRelease CI verifies the signed, notarized payload before upload; there is no unsigned fallback. A built .pkg is notarized and stapled separately.
Windows installer & helperAuthenticodeCI verifies the signer before upload; the installer re-checks the helper's signature and publisher before granting it SYSTEM.
Linux agent binaryDetached Ed25519 + SHA-256The verified installer enforces both before installing anything.
Linux installer scriptDetached Ed25519You verify it as an unprivileged user, before sudo ever runs.
GitHub ReleaseComplete SHA256SUMSGenerated after rejecting filename collisions, covering every published artifact.
Piping the installer into a shell is intentionally unsupported. A 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.

The honest gap in this chain. A key fingerprint published on 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.

* 100 machines is a technical ceiling, not a policy limit. Need more? Get in touch — we'll sort it out.

You are responsible for your machines and every command run on them. Install the agent only on machines you own or are explicitly authorized to control, and treat every code and key you share as granting the access described on this page. The full terms are in the privacy policy and terms.

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