A token Spelunk's token endpoint returns must begin with spelunkapp_, the prefix Spelunk reads a bearer
token as an app session's by; anything else is refused as an answer this app does not understand. Neither
the sign-in's client nor the tunnel's follows redirects, so the code, its verifier and the token go to the
address signed in to and nowhere else, and signing out sends the token only to an address that still
normalises to itself, within ten seconds of its own.
Opening the browser and waiting for it now always end in a SpelunkSignInException rather than whatever
the platform threw: a browser that will not open, a phone's browser closed without an answer (the
OidcException AndroidRedirectCallback.Abandon raises, which escaped the Security page and the editor
before), another sign-in taking over the phone's redirect, and the ten-minute timeout are each said. A
refused code says it in Spelunk's own words, and a rate limit says to wait.
Tests: a refused code's detail, a non-app token, a browser that will not open, an abandoned phone sign-in,
the timeout, the user cancelling, a phone redirect sent byte for byte both times, the real loopback
listener on 127.0.0.1 taking one answer and closing, and a sign-out that will not send the token to an
address it could not have come from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>