Skip to Content

Security Model

CMDOP’s security story has three layers: how callers prove who they are, what they are allowed to do once authenticated, and what the agent cannot do regardless of policy. This page is the high-level tour. Each layer has a deeper page in Concepts.

Identity: who is calling

Two ways to authenticate against a workspace:

  • OAuthcmdop login runs a device flow against the cabinet, stores a refresh token, and a scope-bounded access token tied to your account and currently active workspace.
  • Workspace API key — a long-lived key you paste with cmdop connect key set. Scoped to one workspace. Lives in ~/.cmdop/ssh_workspaces.json at mode 0600.

The credential resolver prefers explicit (--api-key, env var, named workspace) over implicit (active workspace, OAuth fallback). Tokens are never sent to the relay in plaintext — gRPC over TLS handles the wire.

Where tokens live

Storage location depends on the surface and platform:

SurfaceWhere
Desktop (macOS)macOS Keychain
Desktop (Windows)Credential Manager
Desktop (Linux)Secret Service (kwallet / gnome-keyring)
CLI~/.cmdop/ssh_workspaces.json (mode 0600) for API keys; OAuth tokens managed by the auth manager and refreshed in the daemon
Headless agentSame as CLI; refresh runs inside the daemon

The daemon refreshes OAuth tokens in the background; clients reading the local socket get fresh credentials without re-prompting.

Authorization: the permission gate

For remote callers — another machine asking this machine to run a tool — every call goes through the permission gate:

  1. Floor check (non-bypassable, see below).
  2. Best rule match in permissions.yaml (deny > ask > allow; session before global).
  3. Mode default if no rule matched.
    • default — ask the user via the modal.
    • strict — deny.
    • bypass — allow (use only on isolated VMs).

Local chat (cmdop chat, the desktop chat tab, SDK on the same OAuth identity) bypasses the gate by design — you already have full local credentials. The gate exists to constrain cross-machine and cross-identity calls.

Self-to-self calls (same OAuth identity calling its own remote daemon) bypass too, verified via CallerHostname. If you want hard gating regardless, run the receiver under a different account.

The floor: things the gate cannot allow

The floor is hardcoded into the binary. No mode, no rule, no flag turns it off.

  • Files: .env, .gitconfig, .bashrc, .zshrc, .profile, .ripgreprc, .mcp.json, .claude.json.
  • Directories: .git, .ssh, .cmdop.
  • Absolute prefixes: /etc, /System, /private/etc, ~/Library/Keychains, ~/.config/cmdop.
  • Bash signatures: rm -rf /, fork bombs (:(){:|:&};:), | sh, | bash, redirects to block devices like > /dev/sda.

If you need to loosen any of these, you cannot. Restructure the request — e.g. read a config from a non-protected copy, write a deploy artifact under a project-specific directory, run a build in a sandbox.

mode: bypass does not disable the floor. .env, .git, ~/.ssh, rm -rf /, fork bombs are blocked regardless of policy. The floor is the last line of defense.

Sandboxing on Linux

On Linux, the agent applies a seccomp blacklist to subprocesses spawned by execute_command. The list blocks a set of high-risk syscalls (raw sockets, ptrace, mount manipulations) without breaking normal shell tooling. macOS and Windows have OS-level constraints instead.

Auditing

Every agent tool call is recorded in audit.log as one JSON line:

{ "ts": "2026-04-27T10:15:30Z", "decision": "allow", "tool": "execute_command", "operation": "run", "target": "uptime", "mode": "default", "source": "rule", "rule_id": "r-7", "reason": "matched allow rule", "input_digest": "sha256:...", "ask_ms": 0, "hostname": "vps-audi", "user": "ubuntu" }

Lumberjack rotation: 10 MB per file, 5 backups. Read the tail with cmdop permissions audit --tail 20.

Machine passwords (optional)

Some operations let you set a machine-local password (separate from OAuth) — useful when you want a CLI on a shared box to be unable to act without an extra factor. Cached session tokens unlock quick subsequent calls. See cmdop password ... and the Connect auth page for details.

cmdop connect share <host> --ttl 24h mints a token bound to one machine for a fixed window. It bypasses the workspace API key requirement for the guest, but it does not bypass the receiver’s permission gate. Treat share-link URLs like passwords; rotate the workspace key if a long-lived link leaks.

What CMDOP does not do

  • It does not exfiltrate prompts or tool outputs to the cabinet for “training”. The relay sees encrypted bytes; the cabinet stores audit metadata, not contents.
  • It does not run your code in shared infrastructure. Every agent runs locally.
  • It does not give a remote caller an unconstrained shell on your machine. Even mode: bypass runs through the floor.

Rule grammar, modes, ask flow, audit log.

Hands-on: write rules, debug denials, set per-environment policies.

Credential scope, resolver precedence, OAuth vs API key.

Last updated on