Spelunk: sign each device in through the browser instead of keeping Spelunk API keys in the vault

A Spelunk API key reaches one organisation, so a server elsewhere - oc-linux in Orange Climate - was
refused before it reached the agent. Each device now signs in to a Spelunk once, under Settings ->
Security -> Spelunk or from a Spelunk host's editor: the OAuth authorization code flow with PKCE for a
native app (RFC 8252), with Spelunk as the authorization server. The browser opens Spelunk's
/api/app-sign-in/authorize with a loopback redirect (the app's own scheme on Android), the person signs
in there however they normally do and allows "DodoSSH on <device>", and the code is redeemed at
/api/app-sign-in/token for an app session token. That token opens terminals in every organisation its
person is in, with the role they have on each server at the time; tunnel tickets send no organisation.

The sign-in stays on the device: sealed in the local cache under the LocalCacheKey (AAD resource type
18, bound to its row), never synced, wiped with the cache, and only ever sent to the Spelunk it came
from. A 401 from Spelunk forgets it; SIGN OUT ends it there and here, and so does signing out of
DodoSSH. Before every Spelunk connection DodoSSH asks its own identity provider whether its sign-in
still stands (a forced refresh, at most once a minute), and an account it refuses is signed out of
DodoSSH and of every Spelunk on the device.

A route names no key any more: Spelunk hosts are written at host schema 11, which an older build shows
read-only and asks to be updated for, and a payload that still names a key credential decodes with the
id skipped. The SpelunkKey item type is gone: SyncEntityType 14, ChangeEntityType 14 and AAD type 17
are retired, the server drops spelunk_key and its change-log rows, and the client cache drops its
cached items, outbox entries and conflicts of that type. A link still never starts a sign-in: from a
Spelunk this device is not signed in to it opens the editor, which offers to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
laurensandClaude Opus 5.5 committed 2026-10-20 16:26:25 +02:00
1 parent 902761d37b
commit ef365f462e
62 files changed
+4327 -1945

No files matched your search

+36 -27
View File
@@ -456,30 +456,40 @@ phone runs forwards too, listed behind a `⇄` in its terminal's bar, but cannot
A server managed by Spelunk runs Spelunk's agent, which keeps a connection open to Spelunk. DodoSSH can use
that connection instead of dialling: tick **Connect through Spelunk** in the host editor and fill in
Spelunk's API address, the server's id in Spelunk, and your Spelunk API key. A connection then asks Spelunk
for a ticket, opens a WebSocket, and the agent connects to the SSH server on the machine's own loopback — so
the server needs no open SSH port at all. Shells and file transfers both go this way; the relay and Spelunk
are one choice, and ticking one unticks the other.
Spelunk's API address and the server's id in Spelunk. A connection then asks Spelunk for a ticket, opens a
WebSocket, and the agent connects to the SSH server on the machine's own loopback — so the server needs no
open SSH port at all. Shells and file transfers both go this way; the relay and Spelunk are one choice, and
ticking one unticks the other.
- **Fill in the API key once, under Settings → Security → Spelunk.** Give Spelunk's API address and paste
the key (`spelunk_…`, with the Admin role). It is saved as a *Spelunk key* in your personal vault —
encrypted, synced to your other devices, never shown again — together with the address, and **it is only
ever sent to that address**: a host that names the key with any other address is refused at connect. One
key serves every server in that Spelunk organisation; saving a key for an address that already has one
replaces it, and every host using it connects with the new key. The host editor offers saved keys first
and picks the one for the address you type. The API address must be `https://`, except for a Spelunk on
this machine.
- A key kept the older way, as a credential's password, still works and is still offered in the editor;
its password goes to whatever address the host names.
- **Sign this device in to Spelunk once, under Settings → Security → Spelunk** (or with the button in a
Spelunk host's editor). Give Spelunk's API address and press SIGN IN: your browser opens at Spelunk, you
sign in there however you normally do — password, passkey, your company's single sign-on, second factor —
and allow *DodoSSH on "<this device>"*. No key is pasted anywhere, and DodoSSH never sees your Spelunk
password.
- **One sign-in reaches every organisation you are in** on that Spelunk, as you, with the role you have on
each server at the time: Admin opens a terminal, anything less is refused for that server alone. DodoSSH
sends no organisation; Spelunk takes it from the server.
- **It stays on this device.** Nothing about it is synced: each device signs in on its own, and the host
itself carries only the address and the server id, so the same host opens on every device signed in to its
Spelunk. The sign-in is sealed in the local cache like the remembered DodoSSH sign-in, and only ever sent to
the Spelunk it came from. The API address must be `https://`, except for a Spelunk on this machine.
- **It has no expiry, and ends with you.** Spelunk refuses it once you are removed, change your password or
second factors, or sign it out under *Your account → Signed-in apps* there; DodoSSH then forgets it and asks
you to sign in again. SIGN OUT beside it under Settings → Security ends it in both places, and so does
signing out of DodoSSH.
- **DodoSSH has to be online and signed in.** Before every connection through Spelunk it asks its identity
provider whether your DodoSSH sign-in still stands (at most once a minute). An account the provider no longer
signs in is signed out of DodoSSH and of every Spelunk on this device; a provider that cannot be reached
holds the connection until it can.
- **The hostname and port still matter**, though nothing dials them: the host key is pinned under them as it
is for every host, and the port is what the agent connects to on the server.
- **There is no falling back to a direct dial**, and a refusal is said in Spelunk's terms: a key Spelunk will
not take, a key whose role is too low, a server outside the key's organisation, an agent that is not
connected, an agent that could not reach the SSH port or is too old to open terminals.
- **There is no falling back to a direct dial**, and a refusal is said in Spelunk's terms: signed out, not
allowed on that server, a server Spelunk does not know, an agent that is not connected, an agent that could
not reach the SSH port or is too old to open terminals.
**Sign in through Spelunk (no SSH key needed)**, the box under the key in the same card, lets Spelunk log
you in instead of an SSH key or password of the host's own. For each connection DodoSSH makes a throwaway
Ed25519 key pair in memory and sends the public half with the ticket request, which the API key authorises;
Ed25519 key pair in memory and sends the public half with the ticket request, which your sign-in authorises;
the agent runs a one-connection `sshd -i` on the server whose only authorised key is that one, and says
which account it logs in as (`root`). It is real SSH to the server's real host key, so shells, file
transfers and port forwards all work, and the key is gone with the connection — it is never stored,
@@ -493,15 +503,14 @@ link, or hands the link to the copy already running, and **connects straight awa
carries a credential. The hosts links make live in your personal vault, tagged `spelunk`:
- If your personal vault has a host tagged `spelunk` for that server on that Spelunk, DodoSSH connects to it.
- Otherwise, when you have saved a Spelunk key for that Spelunk under Settings → Security — or a saved host
already sends an API key there — DodoSSH saves a new host in your personal vault — named as the link
names it, at the link's address (or `spelunk-<server id>`), port 22, signing in through Spelunk with that
key, tagged `spelunk` (the tag is made if the vault has none) — and connects to it. It syncs like any host
you saved yourself.
- **A link from a Spelunk with no key** opens the host editor instead, filled in the same way with sign-in
ticked and the tag on, and you choose that Spelunk's API key once and save. Every later link from that
Spelunk connects straight away. A link can be sent by anything on this machine, so an API key is never
sent to an address you have not sent it to — saving the key under Security is how you say you have.
- Otherwise, when this device is signed in to that Spelunk, DodoSSH saves a new host in your personal vault —
named as the link names it, at the link's address (or `spelunk-<server id>`), port 22, signing in through
Spelunk, tagged `spelunk` (the tag is made if the vault has none) — and connects to it. It syncs like any
host you saved yourself.
- **A link from a Spelunk this device is not signed in to** opens the host editor instead, filled in the same
way with sign-in ticked and the tag on, and offers to sign in to that Spelunk. Sign in, save, and every later
link from that Spelunk connects straight away. A link can be sent by anything on this machine, so it never
starts a sign-in by itself.
A host you set up by hand, or one in a team's vault, is left as it is: a link for its server adds a host of
its own. A link that arrives while the vault is locked waits for the unlock.