Org Sharing

Personal vault sync uses one wrapping key per machine. Org sharing is more interesting: multiple users need to decrypt the same blob, but no shared key exists. LPM uses an X25519 + AES scheme similar to ECIES to wrap the vault's AES key once per member.

Org sharing requires an Org plan.

How sharing works

lpm env share --org <org-slug>             # share the current vault with an org

The flow when you first share:

  1. Each org member has already registered an X25519 public key with lpm.dev (the server stores public keys in user_public_keys; private keys live on member machines).
  2. The vault has a single AES key that encrypts the blob (same as personal sync).
  3. The AES key is wrapped per member — once for each member's X25519 public key, producing one row in vault_org_keys per member.
  4. On pull: each member uses their X25519 private key to unwrap their copy of the AES key, then decrypts the blob.
[shared encryptedBlob]   ← one ciphertext, AES-256-GCM
        ▲
        │ decrypted with the per-vault AES key
        │
[per-vault AES key]
   ┌────┼────┬────┬────┐
   ▼    ▼    ▼    ▼    ▼
 alice bob carol dave eve            ← one wrapped copy per member
 (X25519 wrap)                          stored in vault_org_keys

Wire format for each wrapped entry: base64(ephemeral_public):base64(iv):base64(ciphertext+tag).

Adding a member

When an admin (or anyone with the rotate permission) wants to add a new member:

  1. Pull the AES key from your own wrapped copy (you already have decrypt access).
  2. Re-wrap it with the new member's X25519 public key.
  3. Push the new wrapped-key entry to POST /api/orgs/{slug}/vaults/....

This happens automatically when a member joins an org that owns a vault you've shared, but it can also be triggered manually after the fact. The new member can lpm env pull immediately.

Removing a member

Removing a member is two steps, both of which matter:

  1. Drop the member's wrapped-key entry. RLS lets org admins do this from the dashboard or via API. This makes the server stop returning a wrapped key for that user on pull.
  2. Rotate the AES key. The leaver's copy of the old key still works against the old encrypted blob — they could still decrypt anything they downloaded before being removed, and could still decrypt blobs encrypted under that AES key if they ever obtained one. Rotation regenerates the AES key, re-encrypts the blob, and re-wraps for every remaining member:
lpm env rotate-key                         # rotates AES, re-wraps, re-encrypts, pushes

After rotation, the leaver's old wrapped key cannot decrypt the new blob, and the new blob is the only one the server returns.

If you skip the rotation step, you've revoked server-side access but not cryptographic access to data the leaver already downloaded. For real revocation, always rotate after removing.

Personal AND org rows for the same vault-id

Vault rows are keyed by (vaultId, userId) for personal entries and (vaultId, orgId) for org entries — both indexes coexist. The same vaultId can have both a personal row (for the owner) and an org row (shared). The CLI picks which side to read based on context:

  • lpm env push against a project owned by a user → personal row
  • lpm env push --org acme against the same project → org row
  • lpm env pull defaults to whichever side the project is associated with

This lets a user "promote" a personal vault to an org vault without losing the personal copy as a fallback during the transition.

Audit visibility

For org vaults:

  • Members with role owner / admin / maintainer see the full audit log (every member's actions).
  • Members with lower roles cannot read other members' actions — RLS-enforced.
lpm env log                                # last 50 entries (filtered by your role)

The dashboard shows the same data with filters and time-range queries.

What's not supported

  • Per-key permissions. Org members either have read access to the whole vault or no access. Per-key ACLs are a future ask.
  • Per-environment permissions. A member with read access to a vault sees every environment in it. If you need to gate production separately, use OIDC policies for CI (which do scope per-environment) or split into separate projects/vaults.

Plan

Org sharing requires an Org plan. Pro plans get personal cloud sync but not multi-member sharing.

Next

  • Cloud Sync — the personal foundation org sharing extends
  • Dashboard Pairing — how each member's other devices unlock
  • OIDC for CI — per-environment policies that work alongside org sharing