Sterister f301b391a5
Some checks failed
Test / test (push) Has been cancelled
docs(archive): close out Status fields on V2.x backlog + V3.12 design notat
V4.0 acceptance §"All docs/V*.md arkivert med DONE-status" requires
every archived plan to carry an explicit Status field. V2.1 / V2.2 /
V2.3 inherited their pre-status format; V3.12-DESIGN was still
"Approved". Mark all four as Done with a one-line pointer to where
the work actually landed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:04:47 +02:00
2026-04-09 20:12:01 +02:00

Shade

End-to-end encryption library implementing the Signal Protocol (X3DH + Double Ratchet) for TypeScript/Bun. Drop into any project — frontend, backend, mobile — to get forward secrecy, post-compromise recovery, and self-healing security.

4.0.0 — General Availability. All V3.1 → V3.12 work is merged, the cross-platform vector suite is green on TS + Kotlin (1000 / 1000

  • 11 / 11), the threat model has been refreshed for every new surface, and the core stack (X3DH, ratchet, storage encryption, recovery, WebRTC P2P, Key Transparency) has been packaged for external review. The wire format is unchanged from 0.4.x — 4.0 peers interoperate with 0.4.x peers byte-for-byte. See MIGRATION.md § 0.3.x → 4.0 for the upgrade path and CHANGELOG.md § 4.0.0 for the consolidated release notes. Voice / Video have been moved to V5.0, to be built on top of the frozen 4.0 baseline.

Status

Area 4.0 status Pointers
Protocol core (X3DH + ratchet + sender keys) Done — frozen packages/shade-core
Storage encryption (V3.2) Done — opt-in EncryptedSQLiteStorage / EncryptedPostgresStorage, key sources: passphrase / OS keychain / app-injected docs/storage-encryption.md
Fingerprint gates & trust UX (V3.3) Done — Shade.beforeFirstLargeFile / beforeBackupImport / beforeNewDeviceTrust docs/trust-ux.md
Observability v2 (V3.4) Done — OpenTelemetry-shaped events, /metrics, observer dashboard docs/observability.md
Android parity & cross-platform CI (V3.5) Done — TS + Kotlin vector-gate live; Android KeystoreStorage is post-GA android/shade-android/README.md, docs/cross-platform.md
Async store-and-forward (V3.6) Done — @shade/inbox + @shade/inbox-server docs/inbox.md
Transport bridge (V3.7) Done — SSE / long-poll / WS adapters docs/transport.md
Web Workers crypto (V3.8) Done — lane keys never cross the thread boundary docs/web-workers.md
Rich file metadata + thumbnails (V3.9) Done — in @shade/files docs/files.md
Social key recovery (V3.10) Done — Shamir + AEAD-gated reconstruction + guardian widgets docs/recovery.md
WebRTC P2P transport (V3.11) Done — RTCDataChannel with MultiTransportFallback([webrtc, http]) docs/webrtc.md
Key Transparency (V3.12) Done — opt-in Merkle log, signed STH, witness gossip docs/key-transparency.md
External crypto review 🟡 Bundle ready — review window open after tag docs/audit/REVIEW-BUNDLE.md
Soak (≥ 2 weeks under load) 🟡 Harness shipped — operator runs it scripts/soak.ts (bun run soak --hours 336)
Voice / Video / Broadcast 🔜 V5.0 — built on top of frozen 4.0 stack docs/V5.0.md

What you get

Protocol core

  • X3DH initial key agreement (works asynchronously via prekey bundles)
  • Double Ratchet for per-message forward secrecy and post-compromise security
  • Sender keys for group ratchet (1:N broadcast key derivation)
  • Identity rotation with grace period for old sessions
  • Safety numbers (Signal-style fingerprints) for out-of-band verification
  • Constant-time comparisons and memory zeroization for hardened operation
  • Binary wire format (@shade/proto) — significantly smaller than JSON

Storage

  • Persistent backends: SQLite (zero-config, bun:sqlite) and PostgreSQL (Drizzle, FOR UPDATE SKIP LOCKED)
  • Crash-safe — sessions survive container restarts, power outages, SIGKILL
  • At-rest encryption (V3.2, opt-in) — AES-256-GCM under per-(table,column) DEKs; key sources: passphrase (scrypt), OS keychain (@shade/keychain), or app-injected. Online re-key, no downtime.

Servers

  • Self-authenticated prekey server (@shade/server, Hono, Docker-ready) with rate limiting, metrics, health checks
  • Async store-and-forward relay (@shade/inbox-server) — TTL-bound ciphertext blobs, signed PUT/FETCH/ACK, idempotent on (address, msgId), per-recipient quota
  • Bridge transports (@shade/transport-bridge) — WS → SSE → long-poll fallback chain for clients that can't keep a WebSocket open. Same IncomingMessage shape across all three.
  • Standalone container — one image bundles prekey + inbox + bridge + transfer + KT + observer

Trust UX

  • Fingerprint gates (V3.3)Shade.beforeFirstLargeFile(threshold, handler), beforeBackupImport, beforeNewDeviceTrust. Gates raise FingerprintNotVerifiedError on the operations that matter, default-warn TOFU otherwise.
  • <FingerprintCompare /> / <FingerprintGate /> widgets for the matching UI side.

File transfer & filesystem

  • E2EE file transfers (@shade/streams + @shade/transfer) — multi-lane chunked uploads/downloads with resume, integrity checks (per-lane sha256 + overall sha256), HTTP/WS fallback, MultiTransportFallback for N-ary demotion
  • WebRTC P2P transport (V3.11, opt-in)RTCDataChannel chunk path with public-STUN defaults, TURN-relay support, glare-safe peer pool, automatic fallback to HTTP when NAT traversal fails (@shade/transport-webrtc)
  • Web Workers crypto (V3.8, opt-in) — AEAD, HKDF, HMAC, X25519, Ed25519 and per-lane stream state run in a dedicated worker. 100 MB+ uploads stay smooth without frame drops; lane keys never cross the thread boundary (@shade/crypto-web/worker)
  • E2EE filesystem RPC (@shade/files) — typed list/stat/mkdir/delete/move/read/write/getThumbnail + custom ops, with rate-limit, retention, fingerprint-gate, and metrics hooks. React hooks under @shade/files/react.

Recovery

  • Social key recovery (V3.10) — Shamir-split your identity to n guardians; any threshold-many k together restore it on a new device. No centralized recovery agent; OOB-fingerprint gate per guardian release; AES-GCM-authenticated reconstruction (@shade/recovery + <RecoverySetup /> / <RecoveryRequest /> / <RecoveryApprove />)

Verifiable distribution

  • Key Transparency (V3.12, opt-in) — append-only Merkle log over the prekey server. Every register / delete becomes a signed leaf; every bundle-fetch carries an inclusion proof; an Ed25519-signed Tree Head ties roots to a fixed log_id. A LightWitness cross-checks STHs across clients so a malicious server that splits its view or rewrites history is caught (@shade/key-transparency).

Observability

  • Live observability — OpenTelemetry-shaped events, bundled dashboard SPA + embeddable React widgets to see what's happening between every step (@shade/observability + @shade/observer + @shade/dashboard)

Tooling

  • CLIshade init scaffolder, shade migrate-storage (V3.2), shade rotate-storage-key, shade fingerprint, shade rotate, shade peer, shade dashboard, shade doctor, shade backup (@shade/cli)
  • Soak harnessbun run soak --hours 336 for the 2-week GA-stable window (scripts/soak.ts)

Quick start

Add the Gitea npm registry to your project's .npmrc:

@shade:registry=https://gt.zyon.no/api/packages/Stian/npm/

Then install the SDK (one-liner for most use cases):

bun add @shade/sdk

Or install specific packages if you need fine-grained control:

bun add @shade/core @shade/crypto-web @shade/storage-sqlite

Even faster — scaffold a new project with the CLI:

bun add -g @shade/cli
shade init my-app --template bun-server
cd my-app && bun install && bun run start

Magic one-liner with the SDK:

import { createShade } from '@shade/sdk';

const shade = await createShade({
  prekeyServer: 'https://shade.example.com',
  storage: 'sqlite:/data/shade.db',
  address: 'alice@example.com',
});

// Send (auto-establishes session if none exists)
const envelope = await shade.send('bob@example.com', 'Hello, encrypted world!');

// Receive
const plaintext = await shade.receive('alice@example.com', incomingEnvelope);

// Your safety number for out-of-band verification
console.log(await shade.fingerprint);

Opt-in surfaces (V3.x → 4.0 GA)

All of these are off by default. Wire them only where you need them.

// V3.3 — Fingerprint gates: enforce verification on the operations that matter
shade.beforeFirstLargeFile(10 * 1024 * 1024, async ({ peer, fingerprint }) => {
  return await ui.confirmSafetyNumberMatches(peer, fingerprint);
});
shade.beforeBackupImport(async ({ embeddedFingerprint }) => { /* ... */ });
shade.beforeNewDeviceTrust(async ({ peer, oldFp, newFp }) => { /* ... */ });

// V3.8 — Web Workers crypto: opt-in, lane keys stay off the main thread
shade.configureWorkerCrypto({
  workerUrl: new URL('@shade/crypto-web/worker', import.meta.url),
});

// V3.11 — WebRTC P2P transport: file transfers ride RTCDataChannel where NAT allows
import { nativeRtcFactory } from '@shade/transport-webrtc';
shade.configureWebRTC({
  factory: nativeRtcFactory(),
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

// V3.12 — Key Transparency: detect server-side bundle swaps
const shade = await createShade({
  prekeyServer: 'https://shade.example.com',
  keyTransparency: { mode: 'observe-strict', logPublicKey: PINNED_KEY_BYTES_32 },
});

Files RPC (@shade/files)

// Server side — Bob exposes a virtual filesystem
const stop = await shade.files.serve({
  list:  async (ctx) => ({ entries: await readdirAt(ctx.path), hasMore: false }),
  read:  async (ctx) => readAt(ctx.path),    // returns inline ≤ 256 KiB or streams
  write: async (ctx) => writeAt(ctx.args),   // receives inline or streams
  // + stat, mkdir, delete, move, getThumbnail, plus typed custom ops
});

// Client side — Alice consumes Bob's filesystem
const fs = await shade.files.client('bob');
await fs.write('/photos/cover.png', new Uint8Array([/* ... */]));   // auto inline/streams
const result = await fs.read('/photos/cover.png');

Files ≤ 256 KiB ride inline in the RPC envelope; larger files automatically promote to multi-lane @shade/transfer streams with sha256 integrity. See docs/files.md for the full API.

Lower-level access

import { ShadeSessionManager } from '@shade/core';
import { SubtleCryptoProvider } from '@shade/crypto-web';
import { SQLiteStorage } from '@shade/storage-sqlite';

const manager = new ShadeSessionManager(
  new SubtleCryptoProvider(),
  new SQLiteStorage('/data/shade.db'),
);
await manager.initialize();

At-rest encryption (V3.2)

import { KeyManager, EncryptedSQLiteStorage } from '@shade/storage-encrypted';

const km = await KeyManager.open({
  kind: 'passphrase',
  passphrase: process.env.SHADE_STORAGE_PASSPHRASE!,
  salt: loadSaltFromDisk(),
});
const storage = await EncryptedSQLiteStorage.open({
  dbPath: '/data/shade-client.db',
  keyManager: km,
});

To migrate an existing 0.3.x SQLite DB in place:

shade migrate-storage \
  --key-source passphrase \
  --passphrase "$SHADE_STORAGE_PASSPHRASE" \
  --salt-file /data/shade-client.db.salt

Architecture — keys vs. payloads

Shade splits the network into a public-key plane (the prekey server) and an encrypted plane (everything else). The prekey server only sees public key material. If you remember nothing else from this README, remember this picture:

              Shade Prekey Container (Hono — public keys only)
                           │
        /v1/keys/*     /v1/inbox/*     /v1/bridge/*     /v1/transfer/*
        /v1/kt/*       /metrics       /healthz   /ready
                           │
        ┌──────────────────┴──────────────────┐
        │                                     │
    [Client A]                            [Client B]
    ShadeSessionManager                   ShadeSessionManager
        │                                     │
        ├── X3DH (handshake via prekey srv) ─►│
        │                                     │
        │◄── Double Ratchet messages ────────►│   ← end-to-end,
        │   (ratchet 0x02 / chunks 0x11)      │     never on the
        │                                     │     prekey server
        │
        │◄── @shade/transfer chunks ─────────►│   ← peer-to-peer
        │   POST /v1/transfer/:id/chunk        │     HTTP, opaque
        │   GET  /v1/transfer/:id/state        │     ciphertext
        │
        │◄── @shade/inbox blobs (offline) ───►│   ← TTL-bound
        │   POST /v1/inbox/:address            │     ciphertext-only
        │   POST /v1/inbox/:address/fetch      │     relay
        │
        │◄── @shade/transport-webrtc ────────►│   ← optional P2P
        │   RTCDataChannel `shade-transfer/v1` │     `MultiTransportFallback`
        │                                     │     auto-demotes to HTTP
    SQLiteStorage / PostgresStorage       SQLiteStorage / PostgresStorage
    + EncryptedSQLiteStorage (V3.2)       + EncryptedSQLiteStorage (V3.2)
        (private keys + sessions)             (private keys + sessions)

What goes via the prekey server

  • Identity public keys (Ed25519 + X25519)
  • Signed prekeys + one-time prekey bundles
  • Registration / replenish / delete writes, all Ed25519-signed
  • (V3.6) Inbox ciphertext blobs with TTL — same container, separate routes; the relay only sees address || msgId || ciphertext-bytes
  • (V3.7) Bridge transports (SSE / long-poll / WS) — also delivered by the same Hono app for clients that can't hold a WebSocket
  • (V3.12, opt-in) KT inclusion proofs + signed tree heads on /v1/kt/* — verifiable distribution
  • Operator-only metrics and the optional observer dashboard

What does not go via the prekey server

  • Message plaintext, ever. Encrypted ratchet envelopes flow peer- to-peer over whatever transport you choose (HTTP, WebSocket, your own broker, or the inbox relay above — which carries ciphertext only).
  • File chunks. @shade/transfer POSTs ciphertext directly to the receiver's /v1/transfer/:streamId/chunk route — the prekey server is not involved. With V3.11 + configureWebRTC(), chunks ride RTCDataChannel peer-to-peer; the relay is bypassed entirely.
  • Identity private keys. They never leave the device's storage.
  • Filesystem RPC. @shade/files rides the Double Ratchet for control + small payloads, then promotes to direct @shade/transfer streams for larger blobs.
  • Stream resume secrets. Persisted only on the local device, encrypted under a device-key derived from the identity signing key.

The prekey server is metadata-bearing (see THREAT-MODEL.md § 2): it sees who registers, who fetches whose bundle, and when. It does not see message contents, transfer contents, or session state. V3.12 Key Transparency (opt-in) makes its bundle distribution verifiable so a malicious server that swaps a bundle is caught.

For the full threat model and mitigations, read THREAT-MODEL.md. For deployment-time guarantees, read docs/PRODUCTION-CHECKLIST.md.

Packages

All packages publish in lockstep at 4.0.0.

Package Purpose
@shade/core Protocol logic (X3DH, Double Ratchet, sender keys, session manager, errors, events)
@shade/proto Compact binary wire format (0x01 PreKeyMessage, 0x02 RatchetMessage, 0x11 StreamChunk)
@shade/crypto-web SubtleCrypto + @noble/curves provider, in-memory storage. Includes the V3.8 Web Workers entrypoint (@shade/crypto-web/worker) — drop-in WorkerCryptoProvider plus createEncryptStream / createDecryptStream TransformStream factories
@shade/observability OpenTelemetry-shaped event bus consumed by @shade/observer, server hooks, and the dashboard
@shade/keychain OS keychain bindings (libsecret / Keychain / Credential Manager) used by @shade/storage-encrypted and the CLI
@shade/key-transparency Key Transparency (V3.12) — RFC 6962-style append-only Merkle log, address-index commitment, signed tree heads, and a LightWitness for split-view detection. Opt-in on both server and client.
@shade/storage-sqlite Persistent SQLite storage (zero-config, bun:sqlite); also ships SqliteInboxStore
@shade/storage-postgres PostgreSQL storage with Drizzle for shared databases; also ships PostgresInboxStore + PostgresKTLogStore
@shade/storage-encrypted At-rest encryption (V3.2) — EncryptedSQLiteStorage / EncryptedPostgresStorage, KeyManager, online re-key
@shade/streams Multi-lane chunk encryption — HKDF-derived per-lane keys, deterministic AES-GCM nonces, streaming SHA-256, file metadata + thumbnails (V3.9)
@shade/transport HTTP + WebSocket transport wrappers with auto-encryption; KT-verifying fetchBundleVerified
@shade/transport-bridge WS → SSE → long-poll fallback chain (V3.7) — single IncomingMessage shape across transports for clients that can't keep a WebSocket open
@shade/transport-webrtc V3.11 P2P chunk transport via RTCDataChannel. Plugs into @shade/transfer as an ITransferTransport; signaling rides Shade's own ratchet. Memory factory + native (globalThis.RTCPeerConnection) factory included; MultiTransportFallback([webrtc, http]) wired automatically when shade.configureWebRTC() is called.
@shade/server Prekey server (Hono routes, auth, rate limit, health, metrics). createPrekeyServerWithKT(...) opts into V3.12 KT mode
@shade/inbox-server Async store-and-forward relay (V3.6) — Hono routes, signed PUT/FETCH/DELETE, per-recipient TTL + quota, idempotent on (address, msgId). Bundles into the same standalone container as the prekey server
@shade/inbox Inbox client + durable outgoing queue + receive cursor + push-trigger hook (onMessageQueued); composes on top of Shade.send/Shade.receive for offline-recipient delivery
@shade/transfer Transfer engine on top of streams: parallel lanes, resume, HTTP + WS transport with auto-fallback, MultiTransportFallback (N-ary demotion), integrity verification
@shade/files Typed E2EE filesystem RPC — list/stat/mkdir/delete/move/read/write/getThumbnail + custom ops, auto inline/streams routing, production hooks (rate limit, retention, fingerprint gate, metrics), React hooks under @shade/files/react
@shade/recovery Social key recovery (V3.10) — Shamir-split identity to n guardians; threshold-many k reconstruct on a new device. AES-GCM-authenticated reconstruction; OOB-fingerprint gate per guardian release
@shade/observer Live debugger backend (snapshot, SSE, dashboard) — see README
@shade/dashboard Standalone dashboard SPA bundled into the observer
@shade/sdk High-level wrapper with createShade() one-liner, auto-publish, auto-establish, auto-replenish, Shade.files namespace, fingerprint gates, KT integration, WebRTC opt-in
@shade/widgets Embeddable React widgets — fingerprint compare/gate, recovery setup/request/approve, transfer uploader/downloader, observer panels
@shade/cli shade init scaffolder + utilities (fingerprint, rotate, peer, dashboard, doctor, backup, migrate-storage, rotate-storage-key)

Shade as a modular toolkit

Shade is split into packages so each project can depend on only what it needs—encrypted messaging, file transfer, prekey hosting, social recovery, KT verification, or lower-level building blocks. You do not need one giant stack for every use case.

For a plain-language map (which packages to add, what the prekey server does vs your own wiring, and where to start in code), see docs/SHADE-BY-SCENARIO.md.

Publishing

All packages publish to a self-hosted Gitea npm registry on gt.zyon.no. The Docker image of the standalone container ships at gt.zyon.no/stian/shade-prekey:<tag>.

# Bump all packages in lockstep
bun run version 4.0.1

# Dry-run (pack all tarballs without publishing) — no token required
bun run publish:dry

# Real publish — interactive (prompts for GITEA_TOKEN, checks
# registry for conflicts, publishes via scripts/publish-all.ts)
bun run publish:all

# Build + push the standalone Docker image
bun run scripts/build-docker.ts -- --tag 4.0.1 --push

The interactive scripts/publish-shade.sh is the human entrypoint; scripts/publish-all.ts is the headless variant used by CI and publish:dry. They share a single PACKAGES list (24 entries at 4.0.0) so the two flows can never drift.

Soak / GA-stable

Before tagging 4.0.0 as latest and recommending production upgrades, run the combined soak harness for ≥ 2 weeks:

# Full GA window (V4.0 §Soak): 14 days × 24 hours
bun run soak --hours 336

# Smoke (~3 minutes — ratchet ping-pong, integrity check)
bun run soak:smoke

The harness fans out N concurrent ratchet pairs, ping-pongs at ~400 ops/sec/pair, and reports cumulative counters every minute. Any exception in any pair is captured and re-raised at shutdown so silent failures cannot hide. Wrap it in systemd-run --user, nohup, or a Gitea scheduled job for the actual 2-week window.

Security properties

Property Description
Forward secrecy Compromising a key cannot decrypt past messages
Post-compromise security Self-heals after key compromise on next DH ratchet
Authentication Ed25519 identity signatures on prekey server writes
Replay protection ±5 minute timestamp window on signed requests
Constant-time comparisons Timing attacks on identity keys are blocked
Memory zeroization Key material is zeroed after use (best-effort in JS)
Identity verification Safety numbers (60 digits) for out-of-band comparison
Identity rotation 7-day grace period for old sessions during rotation
At-rest encryption (V3.2, opt-in) AES-256-GCM under per-(table, column) DEKs; AAD binds (table, column, pk); passphrase / OS keychain / app-injected master key; online re-key
Fingerprint gates (V3.3) Shade.beforeFirstLargeFile / beforeBackupImport / beforeNewDeviceTrust raise FingerprintNotVerifiedError on the operations that matter; defaults to TOFU + warning when no gate is registered
Async store-and-forward (V3.6) Relay only sees `address
Web-Worker key isolation (V3.8) Lane keys, identity keys, and ratchet chain keys live inside a dedicated worker; main thread only ferries plaintext via transferable buffers; idle terminate releases worker memory
Social key recovery (V3.10) Shamir over GF(2^8); AEAD-authenticated reconstruction (forged shares fail); guardian-side fingerprint gate before share release
WebRTC P2P transport (V3.11) Same Double Ratchet authenticates SDP/ICE signaling; chunk frames AEAD-bound to streamId/laneId/seq; deterministic glare resolution; MultiTransportFallback auto-demotes to HTTP
Key Transparency (V3.12, opt-in) Append-only Merkle log + signed tree heads + witness gossip — split-view and history-rewrite are detected by clients

Documentation

Operator + integrator

Per-surface deep-dives

  • docs/files.md@shade/files API + design (filesystem RPC, custom ops, hooks, React)
  • docs/streams.md@shade/streams + @shade/transfer deep dive (incl. hardening + retention)
  • docs/inbox.md@shade/inbox + @shade/inbox-server async store-and-forward relay (V3.6)
  • docs/transport.md@shade/transport-bridge SSE / long-poll / WS bridge layer (V3.7)
  • docs/web-workers.md — V3.8 Web Workers crypto: setup, bundler recipes (Vite/Webpack/Rollup), Safari notes, lifecycle, threat-model
  • docs/recovery.md@shade/recovery social key recovery (V3.10): Shamir setup, guardian-side gates, threshold tuning
  • docs/webrtc.md@shade/transport-webrtc P2P transport (V3.11): NAT-traversal, TURN config, glare resolution, wire format, multi-fallback wiring
  • docs/key-transparency.md@shade/key-transparency (V3.12): operator + client onboarding, witness role, recovery procedures
  • docs/storage-encryption.md — V3.2 at-rest encryption: design, key sources, rotation
  • docs/trust-ux.md — V3.3 fingerprint gates: when each fires, handler patterns, widget integration
  • docs/observability.md — V3.4 event bus + dashboard
  • docs/cross-platform.md — V3.5 Android parity + cross-platform vector regime

Threat model + audit

Migration + history

Examples

Deployment — one container per project

Shade ships as a self-contained Docker image. Deploy one container per project, point your app at it, done. Any stack (Bun, Python, Go, Rust, Kotlin) can use it — the container exposes a plain HTTP API documented in OpenAPI.

docker run -d \
  --name my-project-shade \
  -v my-project-shade:/data \
  -p 3900:3900 \
  -e SHADE_OBSERVER_TOKEN=change-me-to-at-least-16-chars \
  gt.zyon.no/stian/shade-prekey:4.0.0

The container includes:

  • Prekey server/v1/keys/* REST API
  • Inbox relay (V3.6)/v1/inbox/* async store-and-forward; enable with SHADE_INBOX_DB_PATH=/data/inbox.db (or SHADE_INBOX_PG_URL). SHADE_INBOX_PRUNE_INTERVAL_MINUTES controls TTL prune cadence.
  • Bridge transports (V3.7)/v1/bridge/{stream,poll,ws} SSE + long-poll + WS adapters for clients that can't keep a WebSocket open.
  • Transfer routes/v1/transfer/* chunk + state + control routes for @shade/transfer.
  • Key Transparency (V3.12)/v1/kt/* exposes log_id, latest + historical STH, and consistency proofs. Enable with SHADE_KT_* env vars; off by default.
  • Observer dashboard/shade-observer/dashboard/ (off unless SHADE_OBSERVER_TOKEN is set)
  • OpenAPI spec/openapi.yaml and interactive /docs viewer (covers all 27 routes — prekey, inbox, bridge, transfer, KT, observer, /metrics, /healthz, /ready)
  • Prometheus metrics/metrics
  • Health probes/health (full), /healthz (liveness), /ready (readiness)
  • Stale cleanup — purges inactive identities automatically

See docs/DEPLOYMENT.md for the full deployment guide, environment variables, PostgreSQL config, backup strategy, and Dokploy instructions.

License

MIT

Description
E2EE library implementing Signal Protocol (X3DH + Double Ratchet)
Readme MIT 2.8 MiB
Languages
TypeScript 93.1%
Kotlin 6.7%
Shell 0.1%