release(v4.0.0): Shade GA — V3.x consolidation + audit prep
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
V3.1 → V3.12 consolidated and tagged for the first GA release. Wire format unchanged from 0.4.x — 4.0 peers interoperate with 0.4.x peers byte-for-byte. The version bump is semantic: audit-cycle complete, opt-in surface fully exposed, threat model refreshed for every new surface. Highlights: - All 24 @shade/* packages bumped to 4.0.0 in lockstep. - CHANGELOG 4.0.0 section is the canonical manifest of what landed. - THREAT-MODEL extended (§10 fingerprint gates, §11 WebRTC P2P, §12 Web-Worker boundary) + residual-risks table refreshed. - OpenAPI now covers all 27 routes: prekey, transfer, KT, inbox, bridge, observer, /metrics, /healthz, /ready. - MIGRATION 0.3.x → 4.0 documented + smoke-tested against shade migrate-storage on a real SQLite DB. - docs/audit/REVIEW-BUNDLE.md + SCOPE.md ready for external reviewer. - scripts/soak.ts harness for the GA-stable 2-week soak window. - All V*.md plans archived under docs/archive/ with Status: Done. - Voice/Video carved out into V5.0; 4.0 audit focuses on the frozen non-realtime stack. Tests: TS 1000/1000 + Kotlin 11/11 cross-platform vectors green. Docker: gt.zyon.no/stian/shade-prekey:4.0.0 builds and reports version 4.0.0 on /health. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
156
docs/trust-ux.md
Normal file
156
docs/trust-ux.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Trust UX — Fingerprint Gates (V3.3)
|
||||
|
||||
> Status: shipped in 0.4.0, GA-frozen in 4.0 — see [V3.3 plan](./archive/V3.3.md).
|
||||
|
||||
Shade ships with a small number of **blocking** verification gates that
|
||||
fire automatically before the operations where MITM risk is highest.
|
||||
Each gate calls a handler you register on the SDK; until the user (or
|
||||
your handler) approves, the operation aborts with
|
||||
`FingerprintNotVerifiedError`.
|
||||
|
||||
The point of the gate model is to be alert-fatigue-free: you don't see
|
||||
a prompt before every chat message, just before the handful of moments
|
||||
that genuinely matter.
|
||||
|
||||
---
|
||||
|
||||
## What the gates protect
|
||||
|
||||
| Gate | Fires when | Default policy |
|
||||
|------|------------|----------------|
|
||||
| `first-large-file` | `Shade.upload(...)` for an unverified peer with a known size at or above the configured threshold. | Threshold `10 MiB`. Below = no gate. |
|
||||
| `backup-import` | `Shade.importBackup(...)` before any state is written. Handler receives the fingerprint of the identity *embedded in the backup*. | Always fires. |
|
||||
| `new-device-trust` | `Shade.acceptIdentityChange(...)` after a peer rotates identity. The peer's `identity_version` is bumped first so any prior verification is automatically stale. | Always fires. |
|
||||
| `inbox-fanout` | Reserved for V3.6 (`@shade/inbox`). Per-recipient hook is wired today so apps can register it now. | Always fires. |
|
||||
|
||||
---
|
||||
|
||||
## Registering handlers
|
||||
|
||||
```ts
|
||||
const shade = await createShade({
|
||||
prekeyServer: 'https://prekeys.example.com',
|
||||
storage: 'sqlite:/data/shade.db',
|
||||
});
|
||||
|
||||
shade.beforeFirstLargeFile(10 * 1024 * 1024, async (ctx) => {
|
||||
// ctx.peerAddress, ctx.fingerprint, ctx.fileSize
|
||||
return await ui.confirmFingerprintModal(ctx);
|
||||
});
|
||||
|
||||
shade.beforeBackupImport(async (ctx) => {
|
||||
// ctx.fingerprint = fingerprint of the identity in the backup blob
|
||||
return await ui.confirmBackupOwner(ctx);
|
||||
});
|
||||
|
||||
shade.beforeNewDeviceTrust(async (ctx) => {
|
||||
// ctx.fingerprint = fingerprint of the rotated identity
|
||||
return await ui.confirmDeviceRotation(ctx);
|
||||
});
|
||||
```
|
||||
|
||||
Return `true` to allow the operation and persist a `'user'` verification.
|
||||
Return `false` (or throw) to abort with `FingerprintNotVerifiedError`.
|
||||
|
||||
If you don't register a handler, the gate **logs a one-time warning per
|
||||
peer and proceeds on TOFU**, persisting a `'tofu-after-warning'`
|
||||
verification. This satisfies the V3.3 acceptance criterion that apps
|
||||
without registered gates get sane defaults instead of hard-failing — but
|
||||
it does mean the gate is informational, not a hard wall, in that
|
||||
configuration. Always register handlers in production.
|
||||
|
||||
---
|
||||
|
||||
## Manual verification
|
||||
|
||||
The handler model assumes your app drives the OOB compare/confirm
|
||||
flow. If the user verifies through some other path (QR code scan, audio
|
||||
read-aloud, transitive trust from V3.10), call:
|
||||
|
||||
```ts
|
||||
await shade.markPeerVerified('bob'); // pin current fingerprint
|
||||
await shade.unmarkPeerVerified('bob'); // revoke
|
||||
const ok = await shade.isPeerVerified('bob'); // check status
|
||||
```
|
||||
|
||||
`markPeerVerified` reads the peer's *current* fingerprint and pins it
|
||||
together with the per-peer `identity_version`. When the peer rotates
|
||||
(`acceptIdentityChange`), the version bumps and the saved verification
|
||||
goes stale automatically — `isPeerVerified` will return `false` until
|
||||
the user re-verifies.
|
||||
|
||||
---
|
||||
|
||||
## Tuning thresholds
|
||||
|
||||
The `first-large-file` threshold is the only knob that's customer-tunable
|
||||
without code changes. The defaults are conservative:
|
||||
|
||||
- **Default:** `10 MiB`. Big enough that ordinary chat attachments don't
|
||||
trigger; small enough that obvious "exfil candidates" do.
|
||||
- **Lower** (e.g. `1 MiB`) for high-sensitivity deployments — every
|
||||
document goes through the gate.
|
||||
- **Raise** (e.g. `100 MiB`) only for use cases where small uploads are
|
||||
routine and large transfers are deliberate / pre-arranged.
|
||||
|
||||
`backup-import` and `new-device-trust` have no threshold by design — the
|
||||
spec mandates an irremovable minimum gate for both, since each one
|
||||
either trusts a fresh identity or overwrites pinned trust wholesale.
|
||||
|
||||
---
|
||||
|
||||
## React widget
|
||||
|
||||
Use `<FingerprintGate />` from `@shade/widgets` to block UI on
|
||||
verification status:
|
||||
|
||||
```tsx
|
||||
import { FingerprintGate } from '@shade/widgets';
|
||||
|
||||
<FingerprintGate peerAddress="bob">
|
||||
<ChatThread peer="bob" />
|
||||
</FingerprintGate>
|
||||
```
|
||||
|
||||
The default fallback shows the safety number, a "Copy OOB text" button,
|
||||
and an "I have verified" button that calls `Shade.markPeerVerified`.
|
||||
Pass a `fallback` render prop to use your own UI, or `onVerified` to
|
||||
react to the unverified → verified transition.
|
||||
|
||||
`<FingerprintCompare />` is the existing observer-dashboard widget; it
|
||||
now exposes the same Copy-OOB / verify actions when an `onVerified`
|
||||
prop is wired.
|
||||
|
||||
---
|
||||
|
||||
## Errors
|
||||
|
||||
`FingerprintNotVerifiedError` carries:
|
||||
|
||||
- `peerAddress` — the address the gate was protecting.
|
||||
- `gate` — `'first-large-file' | 'backup-import' | 'new-device-trust' | 'inbox-fanout'`.
|
||||
- `code = 'SHADE_FINGERPRINT_NOT_VERIFIED'` — maps to HTTP 403.
|
||||
|
||||
Catch it explicitly when wrapping `upload`, `importBackup`, and
|
||||
`acceptIdentityChange`:
|
||||
|
||||
```ts
|
||||
try {
|
||||
await shade.upload({ to: 'bob', input: bytes });
|
||||
} catch (err) {
|
||||
if (err instanceof FingerprintNotVerifiedError) {
|
||||
showVerifyFirst(err.peerAddress);
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration from 0.3.x
|
||||
|
||||
No breaking changes: existing apps gain warning-mode gates automatically
|
||||
(see the no-handler note above). To upgrade to hard gates, register
|
||||
handlers for the operations you use. Your existing `FingerprintCompare`
|
||||
calls keep working; pass `onVerified` to enable the new actions.
|
||||
Reference in New Issue
Block a user