Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions BETA.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Miranda beta

Miranda lets you leave a desk and keep the same live terminal. Pair a phone or
laptop to a machine with a passkey, and `tmux` sessions on that machine stay
reachable through a blind relay that never sees plaintext, over an
end-to-end-encrypted connection.
Miranda is the reach layer for persistent terminals: `tmux` keeps your session
alive on the machine, and Miranda gets you to that machine from the device in
your hand. Pair a phone or laptop with a passkey, and the machine's `tmux`
sessions stay reachable through a blind relay that never sees plaintext, over an
end-to-end-encrypted connection. Leave your desk. Keep your terminal.

This is a beta. Read the gaps below before you rely on it.

Expand Down Expand Up @@ -42,10 +43,10 @@ This is a beta. Read the gaps below before you rely on it.
on) are tested and supported. Most combinations work; none are certified
yet.
- **Real-network numbers.** Lab measurements exist — a Docker NAT matrix
([netsim/results/results.md](netsim/results/results.md)) shows attach in
0.2–1.3 s and resume after a network flip in ~2.6 s direct / ~3.0 s over
TURN — but nothing is published yet from real home, office, or cellular
networks.
([netsim/results/results.md](netsim/results/results.md)) shows attach from
12 ms on a LAN to ~1.0 s over TURN, and resume after a network flip in
~2.4 s direct / ~2.8 s over TURN — but nothing is published yet from real
home, office, or cellular networks.
- **External audit.** Miranda has not had an independent security audit. A
scope document exists ([docs/audit-scope.md](docs/audit-scope.md)), but no
audit has been commissioned or completed.
Expand Down
104 changes: 66 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@

**Leave your desk. Keep your terminal.**

Miranda is passkey-native terminal continuity for long-running development and
AI sessions. Start work in `tmux` on one of your machines, walk away, and continue
the same live terminal from a laptop or phone. No inbound port forwarding, copied
SSH keys, or blanket network access.
Miranda is the reach layer for persistent terminals. A multiplexer keeps your
session alive **on the machine**. Miranda gets you **to that machine** — from
whatever device is in your hand, with a passkey. No VPN, no copied SSH key, no
open inbound port, no account.

It is deliberately narrow: Miranda connects **you to persistent terminals on
machines you own**. It is not a general VPN, network overlay, remote desktop, or
multi-user access platform.
Start an agent or a build in `tmux` on your workstation, walk away, and pick up
the same live terminal on a laptop or a phone.

We do not build a multiplexer; we make yours reachable. Miranda connects **you to
persistent terminals on machines you own** — not a VPN, network overlay, remote
desktop, or multi-user access platform.

<p align="center">
<img src="assets/miranda-demo.gif" width="900"
Expand All @@ -27,23 +30,28 @@ Miranda is in public beta. Read [BETA.md](BETA.md) for what works today, the
known gaps (passkey/browser matrix, NAT numbers, external audit — none
published yet), and how to report a problem.

## Is it “P2P tmux”?
## Is it “P2P tmux”? How is it different from a multiplexer?

Close, but the useful boundary is:
The useful boundary is the layer:

- `tmux` owns persistence, panes, windows, and processes **inside one machine**.
- Miranda owns passkey identity, machine pairing, discovery, reachability, and
encrypted continuity **between your devices and machines**.
- A multiplexer — `tmux`, or an agent-aware one like
[herdr](https://github.com/herdrdev/herdr) — keeps sessions, panes, windows,
and the processes inside them alive **on one machine**.
- Miranda keeps that machine **reachable from the device in your hand**: passkey
identity, pairing, discovery, NAT traversal, encrypted resume, and time-boxed
sharing.

So the exact niche is a **private terminal-continuity mesh**. Miranda does not
replace tmux; it makes your tmux sessions securely follow you.
So Miranda does not replace your multiplexer. It makes the sessions you already
run follow you. tmux is the engine it drives today; carrying other engines is
the direction, tracked in [#107](https://github.com/srcfl/miranda/issues/107),
not something that works yet.

| Tool | Its job | What Miranda adds |
|---|---|---|
| `tmux` | Keep a local terminal session alive | Reach and resume it from another device |
| SSH | Log in to a host | Passkey-first pairing, discovery, browser access, no exposed SSH service |
| VPN/overlay | Put devices on one private network | Expose only a terminal, not the rest of the network |
| Miranda | Continue your own live terminals | The focused product |
| `tmux`, herdr | Keep a session alive on one machine | Reach and resume it from another device |
| SSH | Log in to a host you can already route to | Passkey-first pairing, discovery, browser and phone access, no exposed SSH service |
| VPN/overlay | Put devices on one private network | Expose one terminal, not the rest of the network |
| Miranda | Get you to your own live terminals | The focused product |

## The one-minute flow

Expand All @@ -69,24 +77,42 @@ to the same owner appear by name from an end-to-end-encrypted registry.
Attach several machines at once with `mir attach a b c`; press `Ctrl-O`, then
`1`–`9` or `n`, to switch focus.

## Why it feels simpler

- **One terminal-shaped capability.** Miranda does not grant subnet access or
expose unrelated services.
- **Passkey-first browser identity.** WebAuthn PRF derives the owner identity for
the current ceremony. The owner private key is not stored by the agent or relay.
- **One visual trust step.** Pair once, compare one safety number, then reconnect
without managing `authorized_keys`.
- **Persistent by default.** Network changes, browser sleep, or closing the lid do
not kill the work running inside tmux.
- **Direct when possible.** One connection, two modes: ICE pairs peers directly —
on the same LAN or hole-punched across the internet — and an optional TURN
server forwards ciphertext where direct NAT traversal fails.
- **Blind discovery.** The relay stores only owner-encrypted machine records while
agents are online.
- **Shareable, for a while.** `mir share` hands someone a time-boxed,
read-only view of one terminal — no account, no key exchange, and it revokes
or expires on its own.
## Five claims you can check

- **Your passkey is the identity, and there is no account.** No signup, no
password, no user record. The owner identity is derived from the passkey PRF
output, so the browser re-derives it for each session and stores no private
key; the relay and every machine you pair hold public IDs only. Pair once,
Comment on lines +82 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope the passkey identity claim to browser clients

For users following the documented native mir pair/mir attach flow, this claim is false: client.LoadOrCreateIdentity generates a random 32-byte root and SaveIdentity stores it in the OS keychain, without any passkey ceremony. The previous wording correctly limited WebAuthn PRF derivation to browser identity; please retain that qualification so CLI users are not given incorrect identity and recovery expectations.

Useful? React with 👍 / 👎.

compare one six-group safety number, and never touch `authorized_keys` again.
→ [`go/internal/identity/owner.go`](go/internal/identity/owner.go),
[B1 identity spec](docs/superpowers/specs/2026-06-11-b1-wallet-identity.md)
- **The relay cannot read, and keeps next to nothing.** Terminal bytes run
through Noise `KK` inside the WebRTC DataChannel, so the relay sees `owner_id`,
`machine_id`, and routing metadata. Discovery records are owner-encrypted blobs
it never opens, held only while your agent is online — a restart loses every
one. The single thing it persists is an owner-signed revocation.
Comment on lines +91 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify revocation persistence as configuration-dependent

When a self-hoster starts the relay with the command shown later in this README, no --revocations-file is supplied, and mir-signal explicitly reports that revocations are memory-only; a restart therefore loses the tombstones as well as the registry. This categorical durability claim can cause operators to assume revoked machines remain revoked across restarts, so state that revocations are persisted only when durable storage is configured or update the self-hosting command accordingly.

Useful? React with 👍 / 👎.

→ [`go/internal/signal/server.go`](go/internal/signal/server.go),
[SECURITY.md](SECURITY.md#components-and-trust)
- **Direct by default, and measured.** One connection, two ICE modes: peers pair
directly on a LAN or hole-punch across the internet, and TURN forwards
ciphertext only where that fails. In the Docker NAT matrix, attach takes 12 ms
on a LAN host pair, 17 ms to an openly reachable agent, and ~1.0 s when only
TURN can work; a Wi-Fi→cellular flip resumes in 2.35 s direct, 2.77 s over
TURN. `make netsim` reruns the whole matrix on your laptop.
→ [`netsim/results/results.md`](netsim/results/results.md)
- **Sharing with an expiry date.** `mir share` mints an owner-signed grant naming
one machine, one guest key, and a window — 1 h by default, 24 h hard cap. It is
read-only unless you say otherwise, and the agent enforces that by dropping
guest input, with no tmux client for a guest to escape. Revocable, no accounts,
no third party.
→ [SECURITY.md](SECURITY.md#session-sharing),
[G1 sharing design](docs/superpowers/specs/2026-08-30-g1-guest-sharing-design.md)
- **The gaps are written down too.** Release binaries are reproducible and
cosign-signed, the installer fails closed without a valid signature, the threat
model is published, and so is the list of what Miranda does not do and has not
proved.
→ [`docs/release.md`](docs/release.md), [SECURITY.md](SECURITY.md),
[audit scope](docs/audit-scope.md)

## Security in one screen

Expand Down Expand Up @@ -175,6 +201,7 @@ each NAT approximates are in [`netsim/README.md`](netsim/README.md).

## What Miranda intentionally does not do

- replace your terminal multiplexer, or ship one of its own;
- route IP packets, subnets, databases, or arbitrary TCP services;
- act as an SSH server or support the SSH wire protocol;
- transfer or synchronize files;
Expand All @@ -183,8 +210,9 @@ each NAT approximates are in [`netsim/README.md`](netsim/README.md).
- protect a compromised endpoint, browser origin, or passkey account;
- claim independent security validation yet.

Those constraints are the product strategy. A small capability is easier to
understand, safer to grant, and easier to make feel magical.
The first one is the whole position, not an apology: keeping a session alive is a
solved job, and Miranda is the layer that gets you to it. A small capability is
easier to understand, safer to grant, and easier to make feel magical.
The longer positioning decision is in [`docs/product.md`](docs/product.md).

## Status
Expand Down
4 changes: 2 additions & 2 deletions docs/naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,5 @@ backbone.
binaries.
- The Go module path and historical cryptographic wire-domain strings remain
`github.com/srcful/terminal-relay/go` / `terminal-relay/...` for compatibility.
- Product positioning now lives in [`product.md`](product.md): passkey-native
terminal continuity, not a general VPN or SSH reimplementation.
- Product positioning now lives in [`product.md`](product.md): the passkey-native
reach layer for persistent terminals, not a general VPN or SSH reimplementation.
51 changes: 32 additions & 19 deletions docs/product.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
# Product thesis: terminal continuity
# Product thesis: the reach layer

## One sentence

**Miranda lets one person leave a device and continue the same live terminal on
another, using a passkey and an end-to-end-encrypted connection to machines they
own.**
**Miranda is the reach layer for persistent terminals: one passkey and an
end-to-end-encrypted path from whatever device is in hand to the live sessions
already running on machines its owner controls.**

## The niche

Miranda is not “SSH with different crypto” and not “a smaller VPN.” Its category is
**private terminal continuity**.
**reach**: getting a person to a terminal that is already alive.

Multiplexers own the other half, and own it well. `tmux` — and agent-aware ones
like herdr — keep sessions, panes, and the processes inside them running on one
machine. That job is solved, so Miranda does not compete for it. What a
multiplexer does not do is get you to the machine: its remote answer is “SSH into
the box,” which needs a route, a key, and something listening.

The initial user is a developer who has long-running terminal work — especially AI
coding agents, builds, data jobs, or debugging sessions — distributed across a
coding agents, builds, data jobs, or debugging sessions — spread across a
laptop, workstation, home server, or cloud box. The pain is not merely logging in.
It is finding the right live session and resuming it safely from the device currently
in hand.

`tmux` solves process and terminal persistence on one host. Miranda supplies the
missing cross-device layer:
The layers stack, and only the last one is someone else's:

```text
passkey identity → paired machines → encrypted discovery → safe reachability → tmux continuity
passkey identity → paired machines → encrypted discovery → safe reachability → the engine holds the session
```

That makes “P2P tmux” a useful shorthand but an incomplete product definition.
Miranda is the secure continuity fabric around tmux.
Miranda is the reach layer around whichever engine holds the session. tmux is the
engine it drives today; the seam for carrying others is issue
[#107](https://github.com/srcfl/miranda/issues/107).

## The product promise

Expand All @@ -40,26 +47,32 @@ remembering a hostname, moving a key, joining a subnet, or exposing other servic
The first excellent product does five things:

1. pairs one owner to one machine with an understandable visual trust ceremony;
2. keeps a persistent tmux terminal reachable without inbound port forwarding;
2. keeps a running tmux terminal reachable without inbound port forwarding;
3. discovers the owner's online machines without revealing their records to the
relay;
4. reconnects cleanly across sleep and network changes;
5. makes switching among several live machines fast on desktop and mobile.

Everything else must justify the security and UX cost.
Everything else must justify the security and UX cost. Whatever belongs to the
engine — panes, layouts, scrollback, agent state — stays the engine's. Miranda's
work there is to carry it, never to rebuild it.

## Explicit non-goals

- **building a terminal multiplexer.** tmux and herdr keep the session alive;
Miranda makes theirs reachable. This one is the position, not a gap;
- general network or subnet access;
- arbitrary TCP forwarding;
- remote desktop;
- file synchronization;
- shared organizational bastions, RBAC, or session recording;
- SSH protocol compatibility;
- building a new terminal multiplexer instead of using tmux.
- SSH protocol compatibility.

These may be adjacent markets, but absorbing them would destroy the narrow
capability users can understand and safely grant.
The first is different in kind from the rest. Multiplexers are not an adjacent
market to absorb; they are the engines Miranda serves, and every one it can carry
widens the market instead of splitting it. The others are adjacent markets, and
absorbing them would destroy the narrow capability users can understand and safely
grant.

## Why it can spread

Expand All @@ -70,7 +83,7 @@ The shareable story is a visible before/after, not a cryptography diagram:

The security story supports that magic: one passkey, one pairing comparison, no
network-wide access, and a relay that cannot read terminal content. The product
should lead with continuity and prove security immediately behind it.
should lead with reach and prove security immediately behind it.

## Product principles

Expand All @@ -81,8 +94,8 @@ should lead with continuity and prove security immediately behind it.
or authorize other machines.
- **The relay is coordination, never trust.** It may know where; it must never know
what was typed.
- **Use proven engines.** tmux owns persistence; standard Noise patterns and mature
primitives own cryptography.
- **Use proven engines.** The multiplexer owns persistence — tmux today; standard
Noise patterns and mature primitives own cryptography.
- **Honest failure beats invisible downgrade.** Missing signatures, entropy,
capabilities, or release verification fail closed with a useful explanation.

Expand Down
4 changes: 2 additions & 2 deletions go/internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ func (a *app) guide() {
if freshSetup() {
p("👋 Welcome to " + b + ". Looks like a fresh setup.")
p("")
p(b + " keeps your live terminal and AI sessions available on every device.")
p(b + " reaches the terminals already running on your machines, from any device.")
p("No inbound ports or SSH keys. Your passkey identity and terminal data stay")
p("end-to-end encrypted; targets hold only their own machine keys.")
p("")
}
p(b + " — terminal continuity for long-running development and AI sessions.")
p(b + " — tmux keeps your session alive; " + b + " gets you to it from any device.")
p("")
p(" Serve a machine (on the box you want to reach):")
p(" " + b + " up keep its tmux sessions reachable — first run shows a pairing QR")
Expand Down
2 changes: 1 addition & 1 deletion web/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Miranda",
"short_name": "Miranda",
"description": "Leave your desk. Keep your terminal. Passkey-native terminal continuity.",
"description": "Leave your desk. Keep your terminal. Passkey-native reach to the terminals already running on your machines.",
"id": "/",
"start_url": "/",
"scope": "/",
Expand Down
Loading