Skip to content

Safety on your machines

roamd runs on your computer, so it's built to be careful there.

 +----------------------- your computer -----------------------+
 |                                                             |
 |  roamd                                                      |
 |   - runs as YOU, never as the all-powerful admin            |
 |   - only lets in browsers you approved at this computer     |
 |   - writes down every login attempt (access.log)            |
 |   - refuses to start if other users could read its files    |
 |   - its secret files only work on THIS computer             |
 |   - offers a command line, and nothing else                 |
 |                                                             |
 +-------------------------------------------------------------+

Conversation view

Conversation view reads the running agent's messages or the terminal's text through your existing encrypted connection. Only the machine owner can use it. Messages and drafts stay in the browser's memory and are cleared when the session closes or roamd locks; reloading clears them too. Your agent's original records stay on your computer. The hub cannot read them. Visible tool details may contain private information. When Codex shares a background server between terminals, you can choose an open conversation yourself. Its name, folder and ID stay inside the encrypted connection. The choice is kept privately on your computer for this running terminal agent.

your browser <-> encrypted connection <-> your session
  read <- agent record or terminal text
  Send -> check the same session -> terminal or chosen Codex chat

Sending checks that the same session and agent are still running. roamd does not automatically retry a message after a connection problem. Use the terminal to answer permission questions. Switching views does not start another agent or approve its work. For a chosen Codex chat, Send and Stop go directly to that chat. The caption names the destination. Changing the chat inside Codex does not change your explicit choice in roamd; choose again when needed. The picker does not change Codex's startup settings.

Files opened from terminal output

Clicking a file path asks your computer to read it through the encrypted connection already open. The document and your edits are not visible to the hub. The reader is limited to small text files the account running roamd can read, anywhere on the computer, and only the machine's owner can use it. A full path, a parent path or a link can open a file outside the session's folder. Saving requires permission to write the file and its folder; roamd does not ask for administrator access. Each session keeps its own reader and draft in the page's memory. Switching sessions hides the old reader; returning restores it. Closing a tab with edits asks whether to save or discard them. Locking clears every session's draft. The usual Files panel keeps its separate browsing permissions.

browser reader <-> encrypted connection <-> your computer's file
  Edit -> Save -> check for changes -> write or keep draft

Reading a document does not run its contents or load its embedded images. Saving needs an explicit click and keeps the file's access permissions on Linux. roamd records who read or edited a file, without its name or contents. A draft lasts only while the unlocked page remains open. Changes made at the very instant of saving can still overlap, so avoid having an agent edit the same file at that moment.

tmux sessions after a restart

A restart ends every program. To bring your tmux sessions back, roamd sets up two well-known, free tmux add-ons on your computer: tmux-resurrect, which saves and rebuilds sessions, and tmux-continuum, which saves every few minutes and rebuilds them when tmux starts.

Only a short list of well-known programs is started again (editors and similar tools, and the popular AI coding tools, which carry on their last conversation). Anything else that was running, such as a script or a one-off job, is not re-run: that terminal comes back as a shell in the same folder.

 every few minutes                 after a restart
 -----------------                 ---------------
 the add-ons save each session:    roamd starts with the computer
   windows and panes,              and starts tmux; the add-ons
   each pane's folder,       --->  rebuild your sessions:
   the program running in it,        same windows and panes,
   the text on screen (if on)        same folders, programs
                                     started again
  • Everything stays on your computer. Nothing about your sessions reaches us.
  • Listed programs start again as they were typed. If one of them was interrupted halfway by the restart, it runs again from the start.
  • Only the listed programs come back. The popular AI coding command-line tools are started in their own "resume" mode, so they carry on their last conversation in that folder. Anything else comes back as a plain shell in the same folder. Whatever a program only had in memory is gone.
  • Screen text is on unless the owner turns it off. It's saved in a normal file on the computer that only your account can read, and it can include passwords.
  • Turn it all off in the session list, or with roamd tmux-restore off. If you already set these add-ons up yourself, roamd leaves your setup alone.
  • On Windows it works the same way with psmux-resurrect and psmux-continuum, the psmux versions of the two add-ons. They need PowerShell 7, and your sessions come back when you log in again (psmux stops when you log out). An AI tool started through Node (an npm install) looks like "node" to psmux, so it comes back as a shell in the same folder instead of resuming.

Bringing sessions back without a restart

If tmux crashes, or you end a session by mistake, you don't have to restart the computer. Open the session list for that machine: sessions in the last save that aren't running are listed under Saved but not running. Press Restore all to bring them back.

 open the session list
      |
 anything saved but not running?  no --> nothing extra shown
      | yes
      v
 "Saved but not running: work, build"   [Restore all]
      |
 the add-ons rebuild the missing sessions;
 sessions still running are left as they are
      |
 "Brought back work, build."  (the list shows them again)
  • Only the machine's owner sees this list and can use the button.
  • It brings back everything missing at once, not one chosen session: that is how the add-ons work.
  • It uses the last save, so anything changed after that save is missed.
  • It shows only when roamd set the add-ons up (not when your own tmux config does).

Running it as the administrator is your choice. Anyone you approve gets the powers of the account roamd runs as. So roamd stays in your normal account unless you say otherwise: tick Allow root / Administrator when setting up a server from the website, or add --allow-root to the command. Then every browser you approve can do anything on that computer. That's your call, and your risk.

It only offers a terminal. It refuses the extra tricks remote-login programs sometimes allow (tunnelling other connections through, forwarding keys and so on), which attackers like to abuse.

You can see and control who gets in.

  • roamd keys lists the approved browsers (people you've shared the computer with are marked "guest"; they can log in but can't let anyone else in).
  • roamd keys revoke … removes one, immediately.
  • An approved browser can swap its own key for a new one (when you Replace browser key), but can't add anyone else. A guest can swap theirs too, so the computer remembers which share each guest's key belongs to and Stop sharing removes whatever key that share holds now, not only the one you first saw.
  • access.log records every attempt, allowed or refused.

Its secrets are sealed to this computer. The computer's ID key and its connection token are locked so that a copy of roamd's folder (in a backup, a synced folder or an email) is useless anywhere else. Windows and Mac use their built-in secure storage. On a Linux desktop, roamd uses your desktop keyring (the password safe that unlocks when you log in, such as GNOME Keyring or KWallet). On a Linux server, which has none, the files are private to your user, the same way SSH keys are kept.

 roamd login on Linux
      |
      +-- desktop keyring running and unlocked?
      |      yes -> keys locked with a key kept in the keyring
      |             (roamd then starts only while you're logged in
      |              to the desktop, when the keyring is unlocked)
      |      no  -> private files, like SSH keys
      |
      +-- roamd login --no-keyring -> always private files
          (for a machine that must come back after a reboot
           with nobody logged in)

If the keyring is locked when roamd starts (say, the computer rebooted and nobody has logged in yet), roamd waits and tries again every so often, then carries on by itself once you log in.

Removing it is clean. Remove the computer in Open sessions or run roamd logout: it's deleted from your account, its token stops working, and roamd stops. (roamd logout also forgets the approved browsers on that computer. To remove roamd itself, run roamd uninstall; see Installation.)

A warning when a Mac agent stops progressing

roamd can warn the owner when a Mac agent appears busy but makes no visible progress. A permission window on the Mac is one possible cause. The warning is a suggestion to check the Mac, not proof that a window is open.

Mac checks existing session activity
  -> encrypted status -> owner's session warning
Work resumes or task ends -> warning clears

This uses the session monitoring roamd already performs. It does not take screenshots, inspect private Mac permission records or request extra access. It cannot grant permission and does not send a push alert for this suspicion. The extra warning is not included in reports to people sharing your machine. Some agents may not be recognised; slow tasks can also trigger the warning. Real permission prompts on a Mac still need testing with this feature.

Process and privilege

Control Detail
Root / Administrator only by choice login, run, service install and uninstall refuse root (Unix) or elevated (Windows) unless --allow-root is given; the web setup script stops before installing anything on a root login (id -u is 0) unless Allow root / Administrator is ticked, which passes --allow-root on
Settings permission check (Linux, macOS) Refuses to start unless the settings folder belongs to the user and isn't writable by others, and its secret files belong to the user, are readable by no one else and aren't symlinks. New files are created private, written atomically
Settings permission check (Windows) Refuses to start if the settings folder's access list lets in anyone other than the user, SYSTEM and Administrators (the usual state under the user's profile). If Windows' own tools can't be run to look, the check is skipped rather than stopping roamd
Autostart Per-user: systemd user unit (no extra restrictions, so sudo works in your terminals), LaunchAgent, or least-privilege logon task. As root with --allow-root: systemd system unit (User=root, started at boot), LaunchDaemon, or highest-privilege logon task, each running roamd run --service --allow-root. roamd login installs it automatically after approval; see Installation

SSH server policy

Setting Value
Authentication Public key only, from the approved list; a few failures end the connection
Timeouts and limits Unauthenticated connections are dropped quickly; sessions and connections are capped; dead connections are detected by keepalives
Refused Port forwarding (both directions), agent forwarding, X11, other subsystems (including SFTP), environment variables
Files roamd's own files subsystem: owners, and guests the owner allowed. Upload names are tidied, uploads never overwrite files or make them executable, and uploads go to one folder unless an owner names another that exists. Downloads (single files, or several as one zip that leaves out links) read only what the machine's user can already read. New folders and renames never replace anything, and delete only moves to the system's Bin. A watch-only guest, or one given only some sessions, is held to the upload folder and can't change anything
Offered An interactive terminal (PTY / Windows ConPTY) running the user's own shell (their SHELL, else sh; PowerShell on Windows), and roamd's own requests: the logged-in key may replace itself with a new public key, only while it is still approved; owners may let guests in or out, change the tmux restore setting, and ask for an update; guests may manage tmux sessions only if the owner allowed it. On Windows the browser's "which sessions are running?" question (also POSIX shell words) is answered by the machine itself with the tmux.exe it found, so the session picker appears; likewise the browser's join command (POSIX shell words) is not run through PowerShell: the machine recognises it (joined_session) and runs tmux new-session -A -s NAME itself as an argument list, with the tmux.exe it finds (psmux: on the PATH, else the WinGet, Scoop, Cargo or Chocolatey folders)

Secrets at rest

 host key, device settings
    |
    +-- Windows: DPAPI (per user), stored in local, non-roaming AppData
    +-- macOS:   XChaCha20-Poly1305 under a key held in the login Keychain
    +-- Linux:   XChaCha20-Poly1305 under a key held in the
    |            desktop keyring (Secret Service API: GNOME Keyring,
    |            KWallet, KeePassXC), chosen at `roamd login` when
    |            one is running and unlocked
    +-- Linux:   otherwise, or with --no-keyring: owner-only files

Each sealed file is bound to its name. A sealed file copied to another machine or user, or edited, fails with a clear "sealed on another machine or user" error. DPAPI is tested under Wine; the macOS Keychain path is not yet run on a real Mac. The Linux keyring path is tested against a real GNOME Keyring (not yet KWallet). Malware running as the same user can usually still reach the keystore: sealing protects copies, not a compromised account.

Linux keyring details. The key is created at roamd login and looked up by settings folder. roamd never asks the keyring to unlock (that could pop up a password prompt nobody sees); a locked or missing keyring makes roamd run wait and retry. Settings keep the method they were sealed with until the next roamd login, so an existing setup (or a service that starts before anyone logs in) is never moved into the keyring behind its back. Deleting roamd's keyring entry makes the settings unreadable: remove the settings folder and log in again.

tmux sessions after a restart, with tmux-resurrect and tmux-continuum

roamd doesn't save or restore sessions itself: it sets up the standard plugins tmux-resurrect and tmux-continuum (by Bruno Sutic, MIT licence), and starts tmux after a boot.

 turn on (service install, web app, roamd tmux-restore on)
   -> fetch both plugins from GitHub at pinned commits,
      check each archive's SHA-256, unpack into roamd's folder
   -> add one marked block to the user's tmux config (last)
   -> apply it to a running tmux at once
 roamd run
   -> once per boot: start tmux if it isn't running, so
      continuum restores the last save
   -> every few minutes: run continuum's save check (it
      otherwise saves only while a client is attached)
  • Plugin settings: only a short list of known programs is brought back, never everything that was running. resurrect's own list of programs it knows how to restart is kept, and AI coding agents are restarted with their documented resume options: codex resume --last, claude --continue, gemini --resume, aider --restore-chat-history (keeping its other options), opencode --continue, copilot --continue; each matched both as an installed program and when run through its interpreter (npm and pip installs). Pane contents on or off by the owner's choice; continuum auto-restore on, saving every few minutes.
  • What's kept, and where: resurrect's save files and pane-content archives, in its usual folder in the user's home, readable only by the user and not encrypted. Nothing is sent to the hub.
  • The config block is marked, rewritten in place when the choice changes, and removed exactly on "off". A config that already loads either plugin (for instance through TPM) is left alone, and the web app says so. The plugins live in roamd's settings folder, so they never clash with a user's own plugin folder.
  • Once per boot: roamd records the boot it last handled (Linux: the kernel's boot id; macOS: the boot time), so installing or updating roamd never starts tmux or brings back sessions that were closed on purpose.
  • Who changes it: anyone who can log in can see the setting; only an owner's key can change it, over the end-to-end SSH session.
  • roamd restarts: the Linux service stops only roamd's own process, so tmux and its programs keep running when roamd is restarted or updated (services installed before this need roamd service install again).
  • Limits: only the listed programs are re-run (including one a restart interrupted); everything else comes back as a shell in the same folder; their memory and environment variables set by hand aren't restored; continuum skips automatic restore when the user runs more than one tmux server; changes since the last save are missed; not yet run on a real Mac; restoring needs bash and tar.

Restore all, on request

 picker opens (owner, restoring on)
   -> "saved": the machine reads resurrect's last save
      (tmux: the "pane" lines' session names; psmux: the JSON
      save that "last" points to) and answers the names not in
      tmux's own list of sessions
 Restore all
   -> "restore saved" (owners only), on the same SSH subsystem
      tmux running?  yes -> run resurrect's restore script
                     no  -> start tmux (psmux: a throwaway session);
                            if continuum hasn't begun restoring within
                            a few seconds, run resurrect's restore itself
   -> wait until every saved session is running (or give up after a
      while, naming the ones that didn't come back)
   -> the page lists the sessions again
  • Both resurrect ports skip a pane or session that already exists, so running restore with some sessions still up only adds the missing ones; nothing is doubled.
  • A guest's request is refused, and an older roamd answers "unknown request", so the page shows nothing.
  • Where the save is: roamd's chosen folder if set, else the plugin's own default (~/.tmux/resurrect if it exists, else $XDG_DATA_HOME/tmux/resurrect; psmux: ~/.psmux/resurrect). A @resurrect-dir set in your own config isn't read.

The same on Windows, with psmux-resurrect and psmux-continuum

On Windows roamd sets up the psmux ports of the two plugins (psmux-plugins, MIT licence): the same idea, with these differences.

 turn on (service install, web app, roamd tmux-restore on)
   -> fetch the psmux-plugins archive at a pinned commit, check its
      SHA-256, unpack both plugins into ~/.psmux/plugins
   -> write a small strategy script per program into ~/.psmux/strategies
   -> add one marked block to psmux's config (last)
 roamd run
   -> a heartbeat file every minute
   -> after a fresh logon (a gap in the heartbeat) with a save to
      restore: start psmux with a throwaway session; continuum's
      start hook restores, then roamd removes the throwaway
   -> every few minutes: run resurrect's save
  • Needs PowerShell 7 (pwsh): the ports are PowerShell scripts. Turning on without it fails with the winget command that installs it, and changes nothing. roamd looks on the PATH and in Program Files (PowerShell\7 and 7-preview), %LOCALAPPDATA% (Programs\PowerShell\7, the Store's WindowsApps), .dotnet\tools and scoop\shims; the installer looks in the same places.
  • Config file: the first that exists of ~/.psmux.conf, ~/.psmuxrc, ~/.tmux.conf and ~/.config/psmux/psmux.conf (the order psmux reads them), else a new ~/.psmux.conf, so roamd's block never hides a .tmux.conf psmux was already using. The block loads each plugin's own plugin.conf with source-file, by absolute path.
  • Programs: psmux saves only a program's name, so roamd writes a strategy script for each AI tool that types its resume command (claude --continue, codex resume --last and the others listed above), and for the programs the port would otherwise re-run by name (python, python3, node, npm, ssh, wsl) a script that types only a comment. Roamd's scripts are named <program>_roamd.ps1 and are removed on "off"; yours are never touched. A tool started through Node shows as node and isn't resumed.
  • Your own setup: a config that names either psmux plugin, or a plugin folder PPM unpacked, is left alone, as on Linux.
  • Once per logon: Windows fast start-up keeps its boot time across a shutdown, so roamd keeps a heartbeat instead. A gap of more than three minutes means roamd wasn't running, so psmux wasn't either. A restart of roamd for an update leaves only seconds, so it never starts psmux or brings back sessions closed on purpose.
  • Limits: not yet run on a real Windows PC; changes since the last save are missed; restoring needs tar (built into Windows 10 and later).

Audit and control

  • access.log: UTC timestamp, event (login accepted or refused, connection closed or refused, key replaced, guest added or removed, file received or fetched), key fingerprint(s) or the reason. Owner-only, rotated automatically.
  • The approved-keys list is re-read on every attempt, so roamd keys revoke applies to a running agent immediately, including new terminals or commands asked for on a connection that logged in earlier. A damaged list admits nobody. Changes to the list are locked against each other, so a key replacement can't undo a revoke made at the same moment.
  • A command can also run on a terminal (how a browser joins a tmux session), through the login shell, and ends when the command does.
  • Commands run without a terminal pass their output on as it comes and stop when their connection ends; a shell ends with its session, however that ends.
  • Removing a machine (from the web app or roamd logout) deletes it on the hub. Its token stops working and roamd run exits.

Known gap: revoking an owner's key doesn't yet close terminals already open (it stops new ones at once). Removing a machine from the web app ends the terminals open on it, and a guest's open terminals and commands are cut as soon as their key is removed or their time ends.

Live conversation access

Conversation reads and input require a currently authorised owner key, checked again after the request arrives. The daemon binds requests to the exact tmux pane, agent process start and linked conversation. Hook associations stay private on the machine; Claude process registration and Linux Codex transcript descriptors can also identify the current record. The view never chooses by folder or recency. Hooks store only the association, not another transcript or the user's message. An explicit Codex selection reads loaded root-thread metadata through the existing local app-server connection, without starting or resuming threads. The association records the selected thread and terminal process start identity. Selected input uses that thread's protocol methods rather than typing into a potentially different TUI conversation. Active-turn steering and interruption include the expected turn identity. Closed threads are rejected, and approvals are not answered by this client. Codex hooks from a detached shared server are ignored because its inherited terminal environment can identify a different client. Automatic hook linking checks process ancestry. Native Mac and Windows runtime verification remains outstanding.

owner browser -> encrypted SSH -> daemon checks authority
  read  -> exact process -> transcript or tmux capture
  input -> check identity -> pane or selected Codex thread

Transcript reads are bounded and return visible message/tool records, excluding hidden instructions and reasoning. Unlike the History archive they do not mask secrets in visible content. The browser renders safe Markdown and fetches no embedded images. Text and drafts remain in page memory and are cleared on close or lock. Input travels in the encrypted channel and into tmux through standard input, never process arguments. The daemon checks identity before paste and again before submitting Enter. An exit racing input delivery remains possible; terminal input is not an atomic agent API. Input is never automatically retried, and approval prompts stay in the terminal. The hub has neither the transcript nor credentials that decrypt the connection. Linux is tested; physical macOS and Windows verification remains outstanding.

Project text access

authenticated owner's browser -> existing SSH file channel
  -> resolve path -> OS permissions -> bounded UTF-8 read
  -> explicit edit + expected revision -> checked replacement

Project document operations check the owner's authorisation independently of general file transfers. The named tmux session supplies the base directory for relative paths. Absolute paths, parent paths and symbolic links can reach other directories under the daemon account's OS permissions. The daemon resolves the target, then opens its parent directory for reads and atomic saves; saving through a link updates the target and preserves the link. There is no session-directory access boundary or privilege elevation. Saves check write access to the existing file as well as requiring write access to its parent directory. Save authorisation is checked again after receiving the draft. Reads are bounded and regular-file-only; edits also reject unsupported ownership and hard-link arrangements on Unix.

A revision combines SHA-256 of the text with file metadata. Saves compare it before and after preparing a replacement. A daemon-wide lock orders its own saves, but external writers can still race the final replacement. Unix owner, group and mode bits are preserved; custom ACLs and extended attributes are not guaranteed to survive. The access log records the reader/editor key and action, not paths or contents. Browser rendering ignores raw HTML and embedded images and retains the strict CSP. Reader state and asynchronous results are isolated by originating session tab; only the active tab's reader is displayed. Unsaved edits remain in page memory when hidden, never browser storage. User tab closes protect drafts even when ordinary confirmation is disabled. Closing a tab clears its reader; locking clears all readers and prevents late file responses from restoring them. The hub receives encrypted SSH traffic, not document plaintext. Linux is tested; native macOS and Windows verification remains outstanding.

Suspected Mac permission stalls

The Mac daemon combines recognised agent activity with a local fingerprint of meaningful terminal output. Busy-footer timers and spinners are excluded from progress. The resulting stall marker uses the existing signed, end-to-end encrypted owner status channel. Guest reports omit it. It adds no screenshots or permission-specific paths to the report, and no readable status for the hub.

Activity + screen fingerprint -> local suspected stall
  -> signed, encrypted owner report -> banner and tab marker
Progress / task end -> clear marker in next report

The detector does not read the TCC database, inspect system dialogs, ask for Accessibility or Screen Recording, or attempt to approve privacy requests. It cannot reliably distinguish a pending privacy dialog from a long tool call. Known terminal input requests retain the existing waiting state. No extra push is generated for a suspected stall. Tests cover controlled activity, report boundaries and browser behaviour; native macOS prompt validation is outstanding.