Merge branch 'claude/vault-creation-sharing-62c0b6'
ci / build and test (push) Successful in 1m38s
ci / android head (push) Failing after 9s
ci / api image (push) Successful in 28s

# Conflicts:
#	README.md
This commit is contained in:
2026-08-04 10:10:40 +02:00
13 changed files with 1035 additions and 63 deletions
+42 -15
View File
@@ -186,10 +186,15 @@ signing in again brings it all back; the count in the confirmation is the one th
else. Your session at the identity provider is *not* ended — DodoSSH has no way to end it — so on a machine
that is not yours, sign out there too.
Two of M1's known gaps are visible immediately, so they are worth expecting rather than diagnosing: password
authentication asks for the password every time, because nothing in the interface can create a vault
credential yet (they do sync — there is just no editor for one); and unlock asks for the passphrase on every
launch, because no device key is registered.
**Neither a password nor a passphrase has to be typed twice**, and both ways out of that are opt-in. A
password typed to connect is typed once: tick **Remember this password** under the box and it is saved to
your keychain and bound to that host the moment the remote accepts it — or add one outright with **+
PASSWORD** on the Vault screen. And unlock can be a Windows confirmation instead of the passphrase:
Preferences → *This machine***REGISTER** keeps this machine's device key in the TPM, so a later launch
offers **USE WINDOWS HELLO** on the unlock card. A machine with no TPM — and any desktop that is not
Windows — is offered neither button and keeps asking for the passphrase, which Preferences says out loud
rather than leaving you to notice. The passphrase never stops working either way: a declined confirmation
leaves the box exactly where it was.
### Moving files
@@ -369,8 +374,20 @@ design's blue-tinted `#E3E7F4`, and the type scale is a point larger.
File transfer **is** here now, in the shape scoped storage allows: one remote pane and the queue, over
either an SFTP host or a bucket. There is no local pane, because there is no browsable local filesystem to
put in one — moving a file *in* from the phone needs the system document picker and is the next piece of
work rather than a thing the screen pretends to do. Hosts and groups are made and corrected here now, from
put in one. So the way in is **ADD FILES**, which is the system document picker: point at a document
wherever it lives and it goes to the directory showing, rather than choosing on the left and pressing an
arrow. What Android hands back is a `content://` URI with no path behind it and no promise of a seek, so
the document is copied into the app's own cache and the copy is what the queue moves — which is what lets
a stopped upload resume from where it stopped. The copy is deleted when the transfer finishes, kept while
it is stopped so RESUME has something to read, and swept at the next launch.
**SAVE FILE is the way back out**, and it is the system's save picker for the same reason: there is nowhere
this application could put a file that you would then be able to open. You choose where it goes before the
transfer starts, the download runs into the cache, and the finished bytes are copied out to the document you
chose. That order has one visible cost, and the screen says it rather than leaving it to be discovered: the
picker creates the file when you dismiss it, so a download that then fails leaves an empty one there. The
alternative is a picker that appears minutes later over whatever you moved on to — and often while the app
is in the background, where Android will not show one at all. Hosts and groups are made and corrected here now, from
a floating + on the Hosts screen, and both editors are cards in the list's own row rather than dialogs, so
the form never covers the thing it is about. The keychain has no editor of its own: SSH keys and buckets are
created on the desktop and sync down, and the phone will delete an item — behind the same counted
@@ -515,10 +532,15 @@ keychain plus a terminal — and the spike that gates all of it.
host key approved at that shell's prompt reaching the second machine as well. See
[End-to-end verification](#end-to-end-verification).
Known gaps in the client, stated rather than implied by the interface: nothing in the interface can create
a vault credential yet, so password authentication still asks for the password each time — SSH keys *are*
editable, and binding one to a host is the way to connect without typing anything; and no device key is
registered, so the passphrase is needed on every launch until the OS keystore is wired.
The two gaps this milestone shipped with have both closed since. A vault credential can be created — from
the Vault screen, or from the REMEMBER tick beside the connect password, which saves it and binds the host
to it once the remote has accepted — so password authentication asks once rather than every time. And a
device key is registered where the machine can hold one: Windows keeps it in the TPM under a CNG policy
that makes the consent dialog a condition of *using* the key rather than a prompt this application draws,
which is stronger than [ADR 0007](docs/adr/0007-device-key-protection.md) originally described and is why
that ADR was corrected. What is left is the floor rather than a gap: a machine with no TPM, or a desktop
that is not Windows, gets a store that reports itself unavailable and keeps asking for the passphrase —
the honest answer rather than a degraded one.
Host key trust *is* in the vault, which is what makes trust-on-first-use worth having: a fingerprint
approved on one machine is approved on all of them and survives a restart, and the server cannot drop a
@@ -604,11 +626,16 @@ keychain plus a terminal — and the spike that gates all of it.
and you become an admin, in a single transaction — because ownership is sole, so promoting first leaves
the team owned twice and demoting first leaves it owned by nobody, and there is nobody left with the
authority to finish a transfer that stopped in the middle. Nothing else is touched: you keep your vault
key grants, because removing the outgoing owner would revoke them and rotate every team vault, which is a
much larger act than the one being asked for.
- **M4 — hardening and ops**, packaging, self-hosting guide.
- **M5 — multi-provider OIDC**, identity key rotation, re-sealing a rotated vault's stored items,
per-item content keys.
key grants, because removing the outgoing owner would revoke them and flag every team vault for rekey,
which is a much larger act than the one being asked for.
- **M4 — hardening and ops**, packaging, self-hosting guide. *Decided ahead of the work, because the first
release takes it irreversibly:* who signs the client and where it comes from.
[ADR 0010](docs/adr/0010-android-distribution.md) puts the release key with the project rather than with
a store, and rules out the arrangement a self-hosted product reaches for by default — the deployment
serving the client binary, which hands it to the one party the whole trust model is about. An installed
Android app can only ever be updated by a package signed with the same key, so this is the first
release's decision to make and nobody else's afterwards.
- **M5 — multi-provider OIDC**, key rotation, per-item content keys.
## Licence