Files
Shade/SECURITY.md
Sterister 037f994572
Some checks failed
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Test / test (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
release(v4.11.0): streaming Double-Ratchet sub-sessions (ShadeStream)
Answers Vyvern FR shade-ws-streaming-ratchet.md with a first-class
streaming-session API rather than the documented-contract fallback.
The Double-Ratchet crypto was already safe for high-frequency
one-directional use; the send/receive wrapper was not (per-frame
saveSession keystore write; shared per-peer mutex + single stored
session row coupling reuse to the HTTP path).

- @shade/core: stream.ts — identity-bound 3-DH seeding (X3DH-minus-
  prekeys, no prekey-server round trip, mutually authenticated against
  the parent session's pinned identities), bootstrapStreamSession
  reusing init{Sender,Receiver}Session verbatim, in-memory-only
  StreamRatchet (own op-mutex, never persisted, zeroized on close).
  beginStream/acceptStream on ShadeSessionManager; Stream{Closed,
  Handshake}Error; stream.opened/closed events.
- @shade/proto: STREAM_OPEN/OPEN_ACK/FRAME wire (0x31/0x32/0x33),
  additive; inspectEnvelopeType extended.
- @shade/sdk: Shade.openStream/acceptStream → ShadeStream
  (handshakeFrame/handleHandshake/seal/open/close), transport-
  agnostic, independent of encrypt/decrypt queues + parent session,
  identical server (sqlite:) and browser (IndexedDB) — touches no
  storage.
- Tests: 5000-frame one-directional burst (bounded skipped keys + FS
  zeroize), parent-session independence, replay/rewind rejection,
  mutual-auth, proto wire round-trips. Full suite green (1159 pass).
- docs/streaming-sessions.md (R1–R7 contract); SECURITY.md matrix rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:29:09 +02:00

9.8 KiB

Security Policy

Review status

Area Status Notes
Internal review Done Every mitigation in THREAT-MODEL.md is cross-linked to at least one automated test (see Threat-/test-matrix below). The matrix is enforced by tests/security/* + the cross-platform vector suite.
Independent code review Pending Targeted for V4.0. No external review has been completed.
Independent crypto review Pending Targeted for V4.0 alongside the audit.
Pen test Pending Targeted for V4.0.

Read this: Shade implements the Signal Protocol primitives (X3DH + Double Ratchet) on top of @noble/curves and SubtleCrypto. The protocol is well-studied; the implementation has not yet been audited externally. Treat the wire format as stable but the implementation as "production-ready in trusted contexts" until V4.0 closes the audit gap. The THREAT-MODEL.md cells with no test linkage are documentary, not enforced.

Reporting a Vulnerability

If you discover a security vulnerability in Shade, please report it privately by emailing the maintainer rather than opening a public issue. We take all reports seriously and will respond within 48 hours.

How to report

  1. Email: the maintainer email listed in the package metadata. For coordinated disclosure, prefer email over GitHub/Gitea so the issue does not become public before a fix ships.
  2. PGP / age: if you need encrypted reporting, ask for a key over the same email — keys are not bound to the repo to avoid key-rotation drift.
  3. Scope: CVE-style severity (CVSS v3.1) is appreciated but not required. A working reproduction is more valuable than a CVSS score.

When reporting, please include:

  • A description of the vulnerability
  • Steps to reproduce (a runnable script or test case)
  • Affected versions
  • Potential impact
  • Any suggested mitigation

We commit to:

  • Acknowledging receipt within 48 hours.
  • A first-pass triage within 7 days.
  • A coordinated disclosure timeline once severity is agreed; for high-severity issues we aim to ship a patched release within 30 days of triage.

What's in scope

Shade aims to provide:

  • Confidentiality of message contents (only sender and intended recipient can read)
  • Forward secrecy (past messages stay safe if a key is compromised later)
  • Post-compromise security (future messages re-secure after compromise)
  • Authentication of identity keys (signed prekey verification, replay protection)

Vulnerabilities in any of these guarantees are in scope and high priority.

What's out of scope

Shade does NOT protect against:

  • A compromised endpoint (if your device is rooted, the attacker can read messages directly)
  • Metadata leakage (the prekey server sees who fetches whose bundle and when)
  • Traffic analysis (encrypted message sizes and timing are visible)
  • A malicious prekey server distributing fake bundles (mitigation: verify safety numbers out-of-band)
  • Loss of user identity verification (if users don't compare fingerprints, MITM is possible at session establishment)

These are documented in THREAT-MODEL.md.

Identity verification recommendation

When using Shade, you should provide users with a way to compare safety numbers out-of-band (in person, over a video call, or through a separate trusted channel) before treating a session as fully verified. The getIdentityFingerprint() API returns a 60-digit number formatted in 12 groups, designed for human comparison.

Cryptographic primitives

Shade uses well-established primitives:

  • X25519 for Diffie-Hellman key agreement (via @noble/curves)
  • Ed25519 for digital signatures (via @noble/curves)
  • AES-256-GCM for symmetric encryption (via Web Crypto SubtleCrypto)
  • HKDF-SHA256 for key derivation (via Web Crypto SubtleCrypto)
  • HMAC-SHA256 for chain key advancement (via Web Crypto SubtleCrypto)

These match the Signal Protocol specification.


Threat-/test-matrix

This is the consolidated index that backs THREAT-MODEL.md. Every threat-model row that claims a mitigation must point to at least one test file here. Pull requests that add a new mitigation must add a matrix row in the same change.

Threat-model row Mitigation Test file(s)
§ 1 Network attacker — signed writes Ed25519 signature on every write packages/shade-server/tests/server.test.ts
§ 1 Network attacker — replay window ±5 min signedAt enforcement packages/shade-server/tests/server.test.ts ("rejects registration with stale signedAt")
§ 1 Network attacker — header AAD Ratchet headers bound to ciphertext packages/shade-core/tests/ratchet.test.ts, packages/shade-streams/tests/tamper.test.ts, packages/shade-streams/tests/aead.test.ts
§ 1 Network attacker — forward secrecy DH ratchet step + chain-key zeroize packages/shade-core/tests/ratchet.test.ts, packages/shade-crypto-web/tests/hardening.test.ts
§ 1 Network attacker — streaming sub-session FS/replay (V4.11) Per-frame Double-Ratchet seal/open; counter-rewind & replay rejected; in-memory-only (never persisted) packages/shade-core/tests/stream.test.ts ("R1: replayed / rewound frame is rejected", "R2/R3: long one-directional burst stays correct and memory-bounded")
§ 1 Network attacker — streaming handshake auth (V4.11) Identity-bound 3-DH against parent-session-pinned identities packages/shade-core/tests/stream.test.ts ("handshake is mutually authenticated against pinned identities")
§ 3 Endpoint compromise — streaming sub-session isolation (V4.11) Stream ratchet derived without touching the stored parent session; zeroized on close packages/shade-core/tests/stream.test.ts ("R5: opening/using/closing a stream never touches the parent session", "close() zeroizes and blocks further use; idempotent")
§ 2 Compromised prekey server — public-only storage Prekey store never accepts a private key packages/shade-server/tests/server.test.ts, packages/shade-storage-sqlite/tests/sqlite-prekey-store.test.ts
§ 2 Compromised prekey server — signed replenish/delete Per-identity Ed25519 signature packages/shade-server/tests/server.test.ts
§ 2 Compromised prekey server — fake-bundle detection Out-of-band fingerprint comparison packages/shade-core/tests/fingerprint-session.test.ts
§ 3 Endpoint compromise — forward secrecy Old keys not recoverable from leak packages/shade-core/tests/ratchet.test.ts, packages/shade-crypto-web/tests/hardening.test.ts
§ 3 Endpoint compromise — post-compromise security First DH ratchet evicts leaked state packages/shade-core/tests/ratchet.test.ts ("alternating messages trigger DH ratchets")
§ 3 Endpoint compromise — memory zeroization Buffers wiped after use packages/shade-crypto-web/tests/hardening.test.ts ("zeroize")
§ 3 Endpoint compromise — identity-rotation invalidates resume Device-key bound to signing key packages/shade-core/tests/identity-rotation.test.ts, packages/shade-transfer/tests/resume.test.ts
§ 4 Compromised device storage — at-rest stream secrets Resume secret AES-GCM under device-key packages/shade-transfer/tests/resume.test.ts
§ 4 Compromised device storage — at-rest session DB Pending V3.2 none yet
§ 5 Timing side-channel — constant-time compare XOR accumulator packages/shade-crypto-web/tests/hardening.test.ts ("timing variance stays bounded across mismatch positions")
§ 5 Timing side-channel — primitives SubtleCrypto + @noble/curves packages/shade-crypto-web/tests/provider.test.ts, packages/shade-streams/tests/aead.test.ts
§ 6 DoS — per-IP register/bundle rate limit Token bucket per IP packages/shade-server/tests/rate-limit.test.ts
§ 6 DoS — per-identity replenish/delete rate limit Token bucket per identity packages/shade-server/tests/rate-limit.test.ts
§ 6 DoS — body size cap (64 KiB) Hono middleware packages/shade-server/tests/server.test.ts
§ 6 DoS — address validation Regex + NFKC + length packages/shade-server/tests/server.test.ts
§ 6 DoS — per-sender ops/byte quota (@shade/files) RateLimiter token bucket packages/shade-files/tests/security/quota.test.ts
§ 6 DoS — replay protection (@shade/files) Idempotency cache packages/shade-files/tests/security/replay.test.ts
§ 6 DoS — fingerprint gate (@shade/files) Per-sender trust check packages/shade-files/tests/security/fingerprint-gate.test.ts
§ 6 DoS — tampered envelope reject (@shade/files) AEAD reject packages/shade-files/tests/security/tampered-envelope.test.ts
§ 8a Recovery — k-1 collusion impossible Shamir Secret Sharing over GF(2^8) packages/shade-recovery/tests/shamir.test.ts, packages/shade-recovery/tests/adversarial.test.ts
§ 8b Recovery — forged share rejected AES-GCM tag on backup blob + subset-search packages/shade-recovery/tests/adversarial.test.ts ("a corrupted share never authenticates against the backup AEAD tag")
§ 8c Recovery — guardian OOB-fingerprint gate Two-checkbox <RecoveryApprove /> + decline propagation packages/shade-recovery/tests/adversarial.test.ts ("approve handler that REJECTS a wrong fingerprint never sends a grant", "throwing approve handler counts as decline with descriptive reason")
§ 9 Cross-sender X3DH state corruption initReceiverSession copies keypair packages/shade-core/tests/ratchet.test.ts ("does not mutate the caller-provided keypair after a DH ratchet step"), packages/shade-recovery/tests/integration.test.ts

If you add a new mitigation, add a row here in the same PR — the threat model is the contract; this matrix is the proof.