Remote access
Run the agents where the code and the compute are, and drive them from wherever you are. The daemon on the server holds the workspaces, tabs, agents and jobs as always. Every other machine is a client that pairs once and then connects like a local one: the full-screen terminal UI, the line mode, one-off prompts, a browser, or a phone.
# on the servermalac daemon start --remotemalac remote pair # a one-time code, and a QR code for a phone
# on your laptopmalac remote add devbox http://devbox.local:7700 --pair ABCDE-FGHJKmalac -C devbox # the full-screen UI, on the server's daemonmalac -C devbox --plain # the line modemalac -C devbox "fix the flaky test" # a promptOn a phone, scan the QR code: it opens the web UI at the server’s address with the code filled in. The tutorial walks through a Linux server driven from a Mac.
Turning it on
Section titled “Turning it on”Remote access is off by default. The daemon then serves only local requests: from a loopback address, naming a loopback host (127.0.0.1, localhost, [::1]), with no proxy headers. Everything else gets 403 before it reaches the API, the web page included.
web.remote = true, or --remote on malac daemon start, restart or run, lets paired devices in. The daemon then listens on every interface, unless web.host or --host names an address. --host 127.0.0.1 with --remote keeps the daemon off the network and allows only tunnels and SSH.
A restart keeps all of this. malac daemon restart, and the terminal UI’s restart after an update, bring the daemon back with the same address, remote access and tunnel, so paired phones carry on. Flags on restart change them; malac daemon stop and then start apply your configuration afresh.
malac remote (or malac remote status) shows whether remote access is on, where the daemon listens and how to reach it:
$ malac remoteremote access allowed · listening on 0.0.0.0:7700 · 1 paired devices network http://devbox.local:7700 SSH tunnel http://127.0.0.1:7700
How to connect same network malac remote add <name> http://devbox.local:7700 --pair <code> SSH tunnel ssh -N -L 7700:127.0.0.1:7700 <this host>, then malac remote add <name> http://127.0.0.1:7700 --pair <code> Cloudflare malac tunnel cloudflare ngrok malac tunnel ngrok Tailscale malac tunnel tailscale, or tailscale-funnel to go publicPairing
Section titled “Pairing”The daemon’s master token, in ~/.malac/daemon.json, belongs to the daemon’s machine: it works on local requests only, never through the network or a tunnel. Every other device pairs for a token of its own:
- On the daemon’s machine, create a code:
malac remote pair, Connection → Pair a device in the terminal UI, or Settings → Remote access → Show a pairing code in the web UI. A code looks likeABCDE-FGHJKand is valid for ten minutes, for one device. It comes with a QR code of the best address (a running tunnel,remote.public_url, the network address, then the loopback one) and amalac remote addline for each address. - On the device, trade the code for a token:
malac remote add <name> <url> --pair <code>on a computer, the pairing form in a browser (the QR code’s link fills it in), or the pairing screen of an app. - The device uses that token from then on.
Typing a code is forgiving about case, dashes, and O/0 or I/L/1. After ten wrong codes, every open code is withdrawn until a new one is made, so guessing is out of reach even on a public address.
Pairing works with remote access off too, for SSH tunnels and other local clients.
Devices
Section titled “Devices”malac remote devices lists the paired devices: name, kind, when it paired, when and from where it was last seen. Connection → Remote access in the terminal UI and Settings → Remote access in the web UI show the same list.
malac remote revoke <id>, or Revoke in either UI, removes a device: its token stops working at once, and its open connections close. A revoked browser returns to the pairing form. Tokens are stored as SHA-256 hashes in ~/.malac/devices.json (mode 0600).
Saved connections
Section titled “Saved connections”malac remote add <name> <url> --pair <code> saves the daemon’s address and the device token in ~/.malac/connections.json (mode 0600):
$ malac remote add devbox http://devbox.local:7700 --pair ABCDE-FGHJKpaired as macbook (3f2a9c1e) with malac 1.1.1 at http://devbox.local:7700saved `devbox`: malac 1.1.1 with 2 workspaces · `malac -C devbox` connectsThe URL may be a whole pairing link (…/#pair=ABCDE-FGHJK), which carries the code. --token saves a token you already have, and --device-name sets what the other daemon calls this machine (its host name by default). add with an existing name and neither --pair nor --token changes only the address, for example after a quick tunnel restarted with a new one.
| Command | What it does |
|---|---|
malac remote list [--json] |
Saved connections, and whether each answers |
malac remote remove <name> |
Forget one, revoking its token when the daemon answers |
malac -C <name>, --connect <name> |
Use that daemon for the full-screen UI, --plain, -p and malac <prompt>; local is this machine |
On a saved connection, paths name the daemon’s machine. --cwd is a folder there, and without it a new tab opens in the server’s home folder; /cd ~/src/app in the terminal UI works the same way. Models, keys, MCP servers and plugins are the server’s configuration, so the client needs none. Pasted images travel with the prompt; files do not yet: @ and dropped files work only with this machine’s daemon, and the folder browser points to /cd <folder> instead.
In the terminal UI
Section titled “In the terminal UI”The Connection menu lists this computer and every saved connection. Choosing one switches the whole interface to that daemon, in the workspace it used last. The choice is remembered: a plain malac reopens it next time, or falls back to this computer with a note when it does not answer. -C local goes back to this machine.
On another daemon, the header shows devbox › workspace, the status line starts with @devbox, and the workspace switcher (Session → Switch workspace) lists the workspaces of every daemon under its name.
Connection → Remote access shows the daemon you are on: whether remote access is allowed, its addresses, the tunnel, keep-awake, the paired devices (↑ ↓ choose, x revokes) and how to connect. p shows a pairing code with its QR code.
In a browser
Section titled “In a browser”Open the daemon’s address. Without a token, the web UI shows a pairing form; the QR code’s link fills in the code. The browser keeps its device token in local storage and stays paired until you revoke it. On the daemon’s own machine, malac web still prints the local address with the master token.
Settings → Remote access in the web UI holds the switch to allow other devices, the bind address, port and public address, the tunnel to start with the daemon, pairing codes, the paired devices, keep-awake, and how to connect.
Ways to connect
Section titled “Ways to connect”| Way | How | Notes |
|---|---|---|
| Same network | malac daemon start --remote, then http://<host>:7700 |
Plain HTTP: fine at home, not on a café network |
| SSH tunnel | ssh -N -L 7700:127.0.0.1:7700 devbox, then http://127.0.0.1:7700 |
Encrypted and nothing exposed. Works with remote access off, since the request looks local to the daemon. |
| Cloudflare Tunnel | malac tunnel cloudflare |
A public https://….trycloudflare.com address, no account; a new address on every start |
| ngrok | malac tunnel ngrok |
Needs a free account and ngrok config add-authtoken; free addresses show a warning page in browsers first |
| Tailscale | malac tunnel tailscale, or tailscale-funnel |
tailscale reaches only your tailnet, over WireGuard; tailscale-funnel is public |
| Your own proxy | A TLS reverse proxy in front, and remote.public_url set to its address |
The address is offered first in pairing links |
Tunnels
Section titled “Tunnels”malac tunnel <provider> asks the daemon to run the tool as its child, waits for the public address and prints it with what to know about that kind of tunnel. The daemon stops the tunnel when it stops, or on malac tunnel stop, and starts it again after a restart; malac tunnel status shows it.
Some tools wait for you before they give an address. tailscale serve on a tailnet where Serve is off prints a link to turn it on, then waits: malac shows that message and link (in the terminal, the Connection dialog and Settings → Remote access), and the tunnel comes up as soon as you are done. [remote] tunnel = "cloudflare" starts one with the daemon every time. If the daemon is not running, malac tunnel starts it for tunnels only: remote access on, listening on loopback.
| Provider | Runs |
|---|---|
cloudflare |
cloudflared tunnel --no-autoupdate --url http://127.0.0.1:<port> |
ngrok |
ngrok http <port> |
tailscale |
tailscale serve http://127.0.0.1:<port> |
tailscale-funnel |
tailscale funnel http://127.0.0.1:<port> |
malac never installs these tools. When one is missing, it says how: brew install cloudflared on macOS, or the static cloudflared binary in ~/bin on Linux, and the ngrok and Tailscale installers. It looks on PATH and in ~/bin, ~/.local/bin, /opt/homebrew/bin, /usr/local/bin and the Tailscale app.
A tunnel needs remote access. What comes through one carries a public Host and proxy headers, so the daemon treats it as remote and refuses it otherwise, and only pairing and device tokens work through it.
Cloudflare WARP. If you already use Cloudflare Zero Trust, devices running the WARP client can reach the server’s private address through a cloudflared tunnel of your own, with no public address at all. malac does not set this up. To the daemon it is a request from the network, so turn remote access on and pair as on the same network.
Security
Section titled “Security”- Local or remote. A request is local only when it comes from a loopback address, names a loopback host and carries no proxy header (
Forwarded,X-Forwarded-For,X-Forwarded-Host,X-Real-IP,CF-Connecting-IP). Other machines, tunnels, proxies and pages that resolve their own name to this machine (DNS rebinding) are remote, and refused outright while remote access is off. - Tokens. The master token works on local requests only. Device tokens work wherever remote access allows, can be revoked one by one, are stored hashed and are compared in constant time. Only
GET /api/helloandPOST /api/pairanswer without a token. - Browsers. A WebSocket from a browser must come from the page’s own origin, or from a VS Code webview. REST calls need the token in a header, which pages on other origins cannot send; the daemon never grants CORS.
- Transport. The daemon speaks plain HTTP. On a network you don’t trust, anyone who can watch the traffic can take a device token, so use SSH, Tailscale or a TLS proxy there. Cloudflare and ngrok terminate TLS at their edge and can see the traffic; SSH and Tailscale are end to end.
- A device can do what you can. It runs prompts, answers approvals, changes settings, pairs and revokes devices, and starts tunnels. Pair only devices you own, and revoke the ones you lose.
- Repositories cannot open it. A project’s
.malac/config.tomlcannot setweb,daemonorremote, so cloning a repository never opens remote access or starts a tunnel.
Keeping the server awake
Section titled “Keeping the server awake”While agents or jobs run, the daemon keeps the machine from sleeping, and lets it sleep a minute after the last one ends. That matters most for a laptop you leave running at home. See Keeping the machine awake.
Settings
Section titled “Settings”# ~/.malac/config.toml on the server[web]remote = true# host = "127.0.0.1" # tunnels and SSH only
[remote]tunnel = "cloudflare" # start a tunnel with the daemon# public_url = "https://malac.example.com" # an address in front of the daemon, offered in pairing links| Key | Default | Meaning |
|---|---|---|
web.remote |
false |
Let paired devices on other machines, and through tunnels, connect |
web.host |
127.0.0.1, or 0.0.0.0 with web.remote |
Bind address |
web.port |
7700 |
Port; any free port when it is taken |
remote.tunnel |
none | cloudflare, ngrok, tailscale or tailscale-funnel, started and stopped with the daemon |
remote.public_url |
none | An address in front of the daemon, such as a TLS reverse proxy; offered first in pairing links after a running tunnel |
The daemon reads these from your user configuration when it starts.
| Path | What it is |
|---|---|
~/.malac/devices.json |
Devices paired with this machine’s daemon, their tokens hashed (mode 0600) |
~/.malac/connections.json |
Saved connections to daemons on other machines, with their device tokens (mode 0600) |
malac is created by lobirus and released under the MIT license. About malac