msb-remotes

msbctl on a remote machine: host_serve() and restricted_problem() deciding the verb, sandbox-name and exec-flag allowlist with a 126 refusal, host authorize and the restrict,pty forced command, MSB_RE

SKILL.md

GitHub

Remotes

Detail carved out of AGENTS.md, which keeps only what has to hold in every session. Repo-specific throughout; the facts here are verified against msb 0.7.6 and re-checked on 0.7.7.

A remote host

A remote host is an ordinary install driven over ssh. The laptop's everyday key gets restrict,pty,command="<msbctl> host serve" (written by msbctl host authorize; --admin writes an unrestricted line, meant for an sk- key). host_serve() shlex-splits SSH_ORIGINAL_COMMAND, requires msbctl first, and restricted_problem() decides with build_parser() itself (and split_cli(), the same exec/shell flag split main() uses): explicit sandbox names only (never . or omitted), msb exec flags only from MSB_EXEC_VALUE_FLAGS/MSB_EXEC_BARE_FLAGS, and a verb/argument allowlist commented per refusal. Refused: one stderr line, exit 126. Allowed: MSB_RESTRICTED=1, ~/.local/bin and msbctl's own dir prepended to PATH (sshd's PATH has neither), os.execv of the original args. A new verb is refused by host serve until it is added there deliberately — and a test row added to tests/test_host_serve.py. Anything that approves (check_policy(), policy approve/update) refuses under restricted(); add --repo there skips the global token and refuses a policy binding a variable already stored for that name. doctor (doctor_checks(), {id, status, message, fix}, exit 1 on a fail; host check is the same report in its older {check, ok, detail} shape and never fails; remote_test() asks for doctor --json and falls back to host check --json on an exit 2 or 126), host autostart install (a systemd user unit running start --autostart; lingering is root's, once) and start --autostart (the picker uses it too) complete the host side. host verbs skip the preflight.

Remotes, on the laptop

[remotes.<name>] ssh = "user@host" in config.toml (msbctl remote add|ls|test|rm). host:name is recognised in main() AFTER parsing, from args.name/args.names only — never by scanning argv, since port web 3000:5173 and allow web mirror:443 have the same shape. A single-name verb os.execvps ssh_argv() + shlex.join(["msbctl", *original argv with that token made bare]); a multi-name verb runs one ssh per remote name inside the names loop (run_remote_verb(), name right after the verb so -c a b cannot swallow it). -t only when stdin and stdout are TTYs. ssh_argv() shares a ControlMaster per host under STATE_DIR/ssh/. preflight() skips msb/KVM for remote names, the remote verb and ls with remotes. ls queries remotes in threads (ls --json, batch, 15 s), caches answers in STATE_DIR/remotes/, serves a failed host's rows as stale and does not retry it for REMOTE_RETRY_AFTER unless --refresh; with no remotes its output is byte-for-byte unchanged. ls --json stays local. status (the menu's sidebar) adds each remote's sandboxes after this machine's, from the same cache (--quick: the cache alone, no ssh). msbctl remote setup (and remote add at a terminal, and the menu's "remote hosts", remotes_menu()) brings a host up from here through a key that still has a shell: remote_probe() runs REMOTE_PROBE with remote_sh() (sh -c, ~/.local/bin on PATH), a 126 from host serve meaning "already restricted"; each install/fix is asked separately; it stops if msbctl is not there; remote_share_settings() (before restricting, since host serve takes only a Claude token) copies identities without their signing keys via setup --identity … [--default] and sends Claude tokens with setup --claude-token NAME --stdin [--default], the value only on stdin, retrying without --default for a 0.3.0 host; remote_restrict_key() defaults to not now, and uses host authorize --replace (and warns) when the key is already in authorized_keys, then ssh_close()s the shared connection, which still has the shell. With remotes configured, add asks where first (add_where(); a folder argument means here) and add_on_remote() asks repo and name and runs the host's add --repo over ssh; preflight() lets ls, status, add and the menu through without msb, and local_problem() is the msb/KVM check add applies when the answer is this machine.