Sterister 518dc68c4f
Some checks failed
Test / test (push) Has been cancelled
feat(cli): M-Tool 1-3 — CLI, templates, Gitea publishing pipeline
Phase B complete: Shade now has a full developer tooling story.

@shade/cli
- shade init with project scaffolding from templates
- shade fingerprint (own or peer)
- shade publish (re-upload bundle)
- shade rotate (--identity for full rotation, otherwise signed prekey)
- shade peer add/list/verify/remove
- shade dashboard (opens observer in browser)
- shade doctor (diagnose config, storage, prekey server reachability)
- Config from .shaderc.json or SHADE_* env vars

Templates (in packages/shade-cli/templates/)
- bun-server — Bun + Hono backend with /send + /receive endpoints
- chat-demo — Two-process Alice/Bob chat over HTTP

Publishing pipeline (Gitea npm registry)
- .gitea/workflows/test.yml — CI on push/PR with PostgreSQL service
- .gitea/workflows/publish.yml — publish on git tag v*
- scripts/publish-all.ts — local publish helper with DRY_RUN support
- scripts/bump-version.ts — lockstep version bump across all packages
- Root package.json scripts: version, publish:dry, publish:all

Also: /health endpoint now lives in createPrekeyRoutes so doctor can
probe it without needing the full standalone setup.

Dry-run verified: all 11 packages pack cleanly.
246 tests passing, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:38:00 +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.

What you get

  • X3DH initial key agreement (works asynchronously via prekey bundles)
  • Double Ratchet for per-message forward secrecy and post-compromise security
  • Self-authenticated prekey server (Hono, Docker-ready) with rate limiting, metrics, health checks
  • Persistent storage backends: SQLite (zero-config) and PostgreSQL (Drizzle)
  • 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 that's significantly smaller than JSON
  • Crash-safe — sessions survive container restarts, power outages, SIGKILL
  • Live observability — bundled dashboard SPA + embeddable React widgets to see what's happening between every step

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);

Or use the lower-level packages directly if you need full control:

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();

Architecture

                     Shade Prekey Server (Hono)
                              │
              POST /v1/keys/register (signed)
              GET  /v1/keys/bundle/:address
              POST /v1/keys/replenish (signed)
              DELETE /v1/keys/:address (signed)
                              │
        ┌─────────────────────┴─────────────────────┐
        │                                           │
    [Client A]                                  [Client B]
    ShadeSessionManager                         ShadeSessionManager
        │                                           │
        ├──── X3DH ────────────────────────────────►│
        │                                           │
        │◄──── Double Ratchet messages ────────────►│
        │                                           │
    SQLiteStorage / PostgresStorage             SQLiteStorage / PostgresStorage

Packages

Package Purpose
@shade/core Protocol logic (X3DH, Double Ratchet, session manager, errors, events)
@shade/crypto-web SubtleCrypto + @noble/curves provider, in-memory storage
@shade/storage-sqlite Persistent SQLite storage (zero-config, bun:sqlite)
@shade/storage-postgres PostgreSQL storage with Drizzle for shared databases
@shade/server Prekey server (Hono routes, auth, rate limit, health, metrics)
@shade/transport HTTP + WebSocket transport wrappers with auto-encryption
@shade/proto Compact binary wire format (smaller than JSON)
@shade/observer Live debugger backend (snapshot, SSE, dashboard) — see README
@shade/widgets Embeddable React widgets — 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/cli shade init scaffolder + utilities (fingerprint, rotate, peer, dashboard, doctor)

Publishing

All packages publish to a self-hosted Gitea npm registry on gt.zyon.no.

# Bump all packages in lockstep
bun run version 1.1.0

# Dry-run (pack all tarballs without publishing)
bun run publish:dry

# Real publish (requires GITEA_TOKEN env var)
bun run publish:all

# Or via CI: push a git tag v1.1.0 and .gitea/workflows/publish.yml runs

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

Documentation

Deployment

For containerized deployment (Docker/Dokploy):

services:
  shade-prekey:
    image: shade-prekey-server:latest
    ports:
      - "3900:3900"
    volumes:
      - shade-data:/data
    environment:
      - SHADE_PREKEY_DB_PATH=/data/shade-prekeys.db
volumes:
  shade-data:

The SQLite database persists to a Docker volume so all keys and prekey bundles survive restarts.

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%