Skip to content

Security & trust model

CommandLatch lets you trigger a few actions on your own Mac from your phone, a browser, the terminal, Siri, a webhook, or a Claude Code hook. Because it accepts commands from the network, the first question a careful user asks is “what can someone actually do to my machine if they get in?”

This document answers that. It is specific about what is enforced, where, and what the current trade-offs are.

Known trade-offs are documented in Known limitations. For where your data lives, see Privacy.


Nothing reaches your Mac directly. Every trigger queues a command in the backend; the menu-bar app polls for queued commands and runs them locally after checking them again.

you / a trigger CommandLatch backend your Mac
(phone, web, CLI, (Supabase) (menu-bar app)
Siri, webhook,
Claude Code hook)
│ │ │
│ "lock my Mac" │ │
├───────────────────────────────────▶ │
│ authenticated request │ • is the caller allowed? │
│ │ • is the action on the │
│ │ allow-list? │
│ │ • do you own this device? │
│ │ • remote control enabled? │
│ │ → queue a command row │
│ │ (expires in 60s) │
│ │ │
│ ◀──── poll every ~2s ─────────┤
│ │ command row ───────────────▶
│ │ │ • re-check expiry
│ │ │ • reject replays
│ │ │ • remote enabled?
│ │ │ • match against a
│ │ │ fixed action set
│ │ │ → run one native action

Two invariants do the heavy lifting, and both hold in the code:

  1. There is exactly one way to make the menu-bar app act: a command row in the backend, and every write to that table is ownership-checked. There is no second channel.
  2. The menu-bar app treats the cloud as untrusted. It re-checks expiry, blocks replays, honors a local “remote disabled” switch, and only ever runs a fixed list of native actions — never a string from the request payload.

The complete set of actions CommandLatch can run on your Mac:

ActionWhat it does
LockLocks the screen immediately.
SleepPuts the Mac to sleep immediately.
Lock + SleepLocks, then sleeps. Can be delayed (and cancelled before it fires).
Keep awake (start / stop)Holds the Mac awake for a set time, then releases it.
Volume up / downNudges system volume one step.
MuteToggles output mute.
NotificationShows a local macOS banner with text you supply.
Cancel pending lockCancels a delayed Lock + Sleep that hasn’t fired yet.

This is the complete list. Each action maps to a fixed, built-in system operation. Any unrecognised action is rejected and nothing runs.

The only place text from a request is used is the notification title and body — and both are treated as data, not as code. There is no way for a notification payload to execute anything on the Mac.


These are hard guarantees of the design, not configuration you can turn on by accident:

  • No arbitrary shell. No request text can become a shell command. There is no way to run code, scripts, or arbitrary commands on the Mac remotely.
  • No typing or clicking. CommandLatch does not control the keyboard or mouse.
  • No file access. It does not read, write, browse, or upload your files.
  • No screen access. It does not capture, mirror, or stream your screen.
  • No remote terminal or app launching beyond the fixed helpers above.
  • No waking a sleeping Mac. Once the Mac is fully asleep it stops polling, so nothing — not even you — can reach it until it wakes. (See Known limitations.)
  • No reaching across accounts. You can only ever see and command your own devices (see §5).

4. How a command is checked (defense in depth)

Section titled “4. How a command is checked (defense in depth)”

A command is validated at three independent layers, so a gap in one is caught by the next:

LayerCheck
BackendVerifies the caller’s identity on every request. The action must be on the explicit allow-list, the device must belong to you, the command expires in 60 seconds, and remote control must be enabled for the device.
DatabaseThe action field is a strict list — an unknown action can’t even be stored. Access controls mean one account cannot read or send commands to another account’s devices.
The menu-bar appRe-checks the command’s expiry, blocks replayed commands, honors the local remote-disable switch, and only runs from the fixed action list — never anything constructed from request data.

Extra protections worth knowing about:

  • Expiry. Commands expire 60 seconds after they’re queued. If the Mac was briefly offline, an old command is dropped rather than run late.
  • Replay protection. Each command has a unique ID. The app records it before acting and refuses to run the same command twice — so a duplicate or replayed command is silently ignored.
  • Fail safe. If the app can’t access its replay store, it refuses to run any commands rather than skipping the check.
  • Account isolation. One account cannot read, command, or create credentials for another account’s devices. This is enforced in the database and covered by automated tests.
  • Redacted logs. The activity log and diagnostics export are designed so they cannot contain your account credentials or secrets — those fields simply don’t exist in the log structure.

CommandLatch uses several credentials, each deliberately narrow. None of them is a general-purpose key.

CredentialWhat it authorizesScopeWhere the secret livesHow to revoke
Sign-in sessionThe web dashboard, acting as youYour whole accountA standard session token in your browser, from an email magic link (no password)Sign out in the dashboard
Device tokenThe menu-bar app (and the CLI) to poll for and queue commands for one MacOne device you ownHashed at rest in the backend; stored locally on the Mac and in the CLI configUnpair the device, or remove it from the dashboard
Shortcut tokenOne Siri / Home-Screen shortcut to fire one action on one deviceOne device, one actionHashed at rest; embedded in the shortcut’s URLDelete the shortcut in the dashboard
Webhook tokenOne webhook to fire one action on one deviceOne device, one actionThe signing secret stays in the backend and is never returned to the client; requests are HMAC-SHA256 signed with a 5-minute timestamp windowDelete the webhook in the dashboard

Key properties:

  • Shortcut and webhook tokens are capabilities, not keys. Each is locked to a single device and a single action, is rate-limited per token, and is revocable on its own. A leaked “sleep my Mac” shortcut can sleep that one Mac and nothing else.
  • Secrets aren’t readable back. The webhook signing secret and the hashed token values can’t be read out through the normal client API, even by you — only used internally for verification.
  • You can mint a token only for your own device. Creating a shortcut or webhook re-checks device ownership server-side; direct token creation is blocked.

One thing to be aware of: the device token is stored in a file on the Mac protected by macOS file permissions. Anyone who already has access to your user account on that Mac could read it. Moving this into the macOS Keychain is a planned improvement — see §7. It is never exposed over the network and is hashed in the backend.


6. How to disable remote control or revoke access

Section titled “6. How to disable remote control or revoke access”

The fastest “leave my Mac alone” switch is on the Mac itself:

  • Menu-bar app → Disable Remote Commands (or Settings… → Accept remote commands). This stops the Mac from running anything sent remotely — the dashboard, shortcuts, webhooks, and CLI all stop affecting it — until you turn it back on. It’s enforced both in the backend (queued commands are refused) and on the Mac.
  • This switch lives only on the Mac. The dashboard shows whether remote control is on or off but cannot flip it — so an attacker with only your dashboard session can’t re-enable a Mac you’ve locked down.
  • Quitting the app has the same effect while it’s quit: the Mac shows Offline and runs nothing.

In the dashboard, for the Mac in question:

  1. Delete each Shortcut and Webhook you created for it — deleting a token kills that URL immediately.
  2. Remove the device. After this, no token can queue a command for it, even if files still linger on the Mac.

On the Mac:

  1. Settings… → Unpair this device deletes the local pairing record and the CLI config.

  2. Sign out of the dashboard if you’re done on that browser.

For a complete teardown (app, CLI, local data, login item, and account access), follow Uninstall.


These are documented trade-offs — surfaced here so nothing is a surprise.

  • Local device token is stored on disk. A process running as your user on that Mac could read it. The file is protected by macOS file permissions (readable only by your user account). Moving this into the macOS Keychain is a planned improvement.
  • Webhook signing secret is stored in the backend. By necessity — HMAC signing requires the key, not just a hash (the same design used by Stripe, GitHub, and Slack webhooks). It is locked down so it cannot be read back through the app or API.
  • No rate limiting on the dashboard and CLI send paths. Shortcut and webhook URLs are rate-limited per token; the dashboard and CLI command paths are not yet separately rate-limited.
  • Not yet independently penetration-tested. Our security review includes careful static analysis and automated account-isolation tests, but not a live pen-test by an external firm.

If you find a security issue, please report it privately — do not share exploit details publicly.

  • Email security@commandlatch.app

Please include your app version (menu-bar Settings… → Updates), your macOS version, and steps to reproduce. We’ll acknowledge the report promptly, investigate, and credit you if you’d like.