feat(credential-sharing): end-to-end-encrypted team credential sharing

Adds a `team` credential provider + team/member management so teammates can
share secrets by email instead of passing .env files over chat. Fully E2E:
the server only ever stores ciphertext, per-member sealed vault keys, and
public keys — it never sees a plaintext value or the vault DEK.

Plugin (@logicsrc/plugin-credential-sharing)
- crypto.ts: X25519 identity keys, per-vault DEK (secretbox), DEK sealed to
  each member's pubkey (crypto_box_seal), value encrypt/decrypt (libsodium)
- identity.ts: local ~/.logicsrc/identity.json (0600) holding the device key
  + API token; never uploads the secret key
- client.ts: typed /api/credshare client
- providers/team.ts: `team:<slug>/<vault>` CredentialProvider (inspect,
  readValues=decrypt, write=encrypt, rollback); fingerprints match env so
  env<->team diffs line up
- fixes latent libsodium-wrappers ESM load bug (createRequire) here + in
  github-secrets

Server (commandboard-api /api/credshare)
- zero-knowledge router: email-code auth, keys, teams, members, invites,
  vaults, sealed grants, ciphertext secrets, audit; membership authz in app
- CredShareStore abstraction: in-memory (dev/tests) + Supabase (prod)
- Resend email transport for login codes + invites (no-op -> echoes locally)
- supabase migration: credshare_* tables, deny-by-default RLS

CLI
- real `logicsrc login` (email code -> token + key upload)
- `logicsrc teams create/list/invite/accept/members/vaults/grant/push/pull`

Web (logicsrc.com/teams + /teams/accept)
- management surface only (browser holds no private key, never decrypts):
  login, view teams/members/vaults, invite, accept

Tests: crypto round-trip, server contract (invite->accept->push->grant->pull
+ authz boundaries), and a real HTTP+client+crypto E2E asserting the server
never holds plaintext. Full workspace build + tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-07-13 13:09:11 +00:00
parent 257d581331
commit f057589d66
28 changed files with 2869 additions and 17 deletions

View file

@ -152,3 +152,66 @@ provider.audit()
```
The adapter boundary lets tools such as a PWA, TUI, CI workflow, or external CLI consume the same open standard without making LogicSRC depend on any specific product.
## Team Sharing (end-to-end encrypted)
The `team` provider adds a fifth endpoint type — a hosted, **end-to-end-encrypted**
team vault — so you can share credentials with teammates by email instead of
passing `.env` files over chat. It is addressed as `team:<team-slug>/<vault-name>`
(`endpoint.project` = team slug, `endpoint.config` = vault name).
### Trust model
The server (`commandboard-api`, routes under `/api/credshare`) is a **zero-knowledge
relay for secret values**. It stores only:
- member identity **public keys** (X25519),
- the vault **data-encryption key (DEK) sealed to each member's public key**
(`crypto_box_seal`), one wrapped copy per member,
- secret **ciphertext + nonce** (`crypto_secretbox`), plus a salted fingerprint
for redacted diffs.
Plaintext secret values and the raw DEK never leave a member's machine. Granting a
teammate access = an existing member unwraps the DEK with their private key and
re-wraps (seals) it to the new member's public key. The private key lives only in
`~/.logicsrc/identity.json` (mode 0600) and is never uploaded.
### CLI
```bash
# One-time: log in by email (registers this device's identity key).
logicsrc login --email you@example.com
# Owner: create a team, push a local .env into an encrypted vault, invite people.
logicsrc teams create acme --name "Acme Inc"
logicsrc teams push acme prod --env .env # encrypt + upload
logicsrc teams invite acme teammate@example.com # emails an accept link
# Teammate: accept, then get granted, then pull + decrypt locally.
logicsrc login --email teammate@example.com
logicsrc teams accept <token-from-email>
# …an existing member runs: logicsrc teams grant acme prod teammate@example.com
logicsrc teams pull acme prod --env .env # download + decrypt
# Inspect / manage
logicsrc teams list
logicsrc teams members acme
logicsrc teams vaults acme
```
Because `team` is a normal provider, the generic sync surface works too — e.g.
`logicsrc credentials plan --from env --from-path .env --to team --to-project acme
--to-config prod`, then `diff`, `sync`, `audit`, and `rollback` behave exactly as
with the other providers.
### Server + web
- Server storage is behind a `CredShareStore` interface: an in-memory store for
local dev/tests, and a Supabase-backed store (`SUPABASE_URL` +
`SUPABASE_SERVICE_ROLE_KEY`) for production. Migration:
`supabase/migrations/*_credshare.sql` (deny-by-default RLS).
- Email (login codes + invites) uses Resend when `RESEND_API_KEY` is set; without
it, codes/tokens are returned in the API response for local use.
- `logicsrc.com/teams` is a management surface only: log in by email, view
teams/members/vaults and invite/accept. The browser holds no private key, so it
never decrypts — decryption happens only in the CLI.