Files
DodoSSH/README.md
T
jaap-jan ce43f397a6 Add ADRs 0001-0006 and README (M0)
Records the decisions the milestone plan already made, with their costs stated rather
than only their benefits:

- 0001 e2ee-trust-model: key hierarchy, the AAD-to-row binding that stops the server
  moving ciphertext between rows, and the four-layer public-key trust story. States
  plainly that revocation is not retroactive, that Connect cannot be a security
  boundary, and that the IdP becomes a key-distribution trust root.
- 0002 minimal-apis: feature modules with explicit registration; capability negotiation
  instead of Asp.Versioning, since client and server upgrade independently when
  self-hosted.
- 0003 sync-protocol: single write path, revision cursors, and the bigserial
  pre-commit sequence gap that silently corrupts sync — plus the per-vault advisory
  lock that fixes it and the test that must prove it.
- 0004 relay-authorization: relay forwards bytes rather than terminating SSH, so
  zero-knowledge survives; server-resolved target IPs in the ticket to defeat DNS
  rebinding; why host addresses must be plaintext when relay is enabled.
- 0005 no-application-layer: why the usual Application/mediator layer earns nothing
  here, with the trigger that would make us revisit it.
- 0006 observability-stack: OTel plus built-in ILogger; liveness excludes dependencies
  so a database blip cannot restart the container and kill live SSH sessions.

Also adds a README covering layout, build, enforced conventions and milestones.
2026-07-28 12:28:44 +02:00

3.7 KiB

DodoSSH

A self-hosted, team-oriented SSH client with an end-to-end encrypted vault.

Manage hosts, credentials and keys in a desktop app; sync them across your devices and share them with teammates through a server you run yourself. The server stores ciphertext and never holds a key — the operator cannot read the credentials it stores.

Status: early development. See the milestone plan for what exists today.

Why

Teams either scatter SSH credentials across individual ~/.ssh directories with no sharing story, or pay per-seat for a hosted product that holds their infrastructure credentials. DodoSSH keeps the convenience of a synced, shareable vault while remaining self-hostable and zero-knowledge.

Architecture

Component Choice
Backend ASP.NET Core on .NET 10, PostgreSQL + EF Core
Client Avalonia (C#) for Windows/Linux/macOS; terminal pane is a WebView running xterm.js
Auth OIDC, provider-agnostic (Entra ID, Keycloak, Auth0, Authentik)
Vault End-to-end encrypted; X25519 + Ed25519 + XChaCha20-Poly1305, Argon2id unlock
Connections Client-direct SSH by default, with an optional raw-TCP server relay

Two consequences worth knowing before you read further:

  • Revocation is not retroactive. A removed member keeps what they already downloaded. The real remediation is rotating the SSH credential, so offboarding is built around a rotation checklist rather than a button that implies more than it delivers.
  • No session recording in relay mode. The relay forwards SSH ciphertext, so it cannot see commands. That is the cost of the relay not being able to read your traffic.

The reasoning behind each major decision is recorded in docs/adr/, starting with the E2EE trust model.

Repository layout

src/
  DodoSSH.Contracts       DTOs shared with the client — the real API contract
  DodoSSH.Crypto          DSH1 envelope, AAD derivation, key wrapping
  DodoSSH.Domain          entities and invariants, no EF
  DodoSSH.Infrastructure  DbContext, configurations, migrations
  DodoSSH.Api             the host
tests/                    one test project per source project
docs/adr/                 architecture decision records

Building

Requires the .NET SDK pinned in global.json (10.0.x).

dotnet build DodoSSH.slnx
dotnet test DodoSSH.slnx

Run the API locally:

dotnet run --project src/DodoSSH.Api

It listens on http://localhost:5233, serving /healthz/live, /healthz/ready and — in Development — /openapi/v1.json.

Conventions the build enforces

  • Warnings are errors. dotnet format --verify-no-changes gates CI.
  • Package versions are centralised in Directory.Packages.props; packages.lock.json is committed and CI restores in locked mode.
  • BannedSymbols.txt bans DateTime.UtcNow (use TimeProvider), Guid.NewGuid (use CreateVersion7), sync-over-async, MD5/SHA1 and PBKDF2.
  • Public members of DodoSSH.Contracts must be declared in PublicAPI.Unshipped.txt, so a contract change is a build error rather than a client-side surprise.

Milestones

  • M0 — foundation. Repo structure, build conventions, CI, ADRs. Done.
  • M1 — vertical slice. OIDC login → enroll → create a host → open a shell. Gated on freezing DodoSSH.Contracts and the crypto AAD, plus two client spikes (Linux WebView, SSH.NET window-change).
  • M2 — full personal vault, robust sync, relay.
  • M3 — teams, sharing, ACLs.
  • M4 — hardening and ops, packaging, self-hosting guide.
  • M5 — multi-provider OIDC, key rotation, per-item content keys.

Licence

Not yet chosen.