Summary
Vault sync currently encrypts at the transport layer (WireGuard) but transmits file contents as plaintext in KOI-net event payloads. The relay operator routing WireGuard packets can read all synced file contents, paths, sizes, and timestamps.
This issue tracks adding application-level end-to-end encryption so only the intended peer can decrypt vault sync events.
Current Security Model
| Aspect |
Status |
| Transport encryption |
✅ WireGuard (IP layer) |
| Peer authentication |
✅ ECDSA signatures on events |
| File integrity |
✅ Content hashing |
| App-level encryption |
❌ File contents plaintext in event payloads |
| Metadata privacy |
❌ Paths, sizes, timestamps visible to relay |
Root Cause
KOI-net events carry file content as plaintext JSON. Any node or relay operator with access to the network traffic can read the full event payload — including file content.
Proposed Solution
Phase 1: Symmetric E2EE (content only)
- During peer edge establishment (handshake), perform ECDH to derive a shared secret per peer pair
- Add
encrypted_vault_sync: true flag to vault sync peer config
- Before queuing a vault sync event, encrypt file content with AES-256-GCM using key derived from shared secret
- On apply, detect encrypted payload and decrypt before writing to disk
- Backward-compatible: support plaintext events during migration
Key derivation:
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
def derive_vault_sync_key(shared_secret: bytes, peer_rid: str) -> bytes:
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"koi-vault-sync:" + peer_rid.encode()
)
return hkdf.derive(shared_secret)
Encryption:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def encrypt_payload(payload: dict, key: bytes) -> bytes:
nonce = os.urandom(12)
cipher = AESGCM(key)
ciphertext = cipher.encrypt(nonce, json.dumps(payload).encode(), None)
return nonce + ciphertext # prepend nonce
def decrypt_payload(data: bytes, key: bytes) -> dict:
nonce, ciphertext = data[:12], data[12:]
return json.loads(AESGCM(key).decrypt(nonce, ciphertext, None))
Phase 2: Metadata encryption (optional)
Extend encryption to wrap file paths, sizes, and timestamps alongside content — so relay sees only an opaque encrypted blob per event.
Files to Modify
api/vault_sync.py — encrypt on queue, decrypt on apply
api/vault_sync_models.py (or equivalent) — add encrypted_payload field to event schema
api/koi_net_handler.py / handshake — store derived shared secret per peer
scripts/federation/personal-env.template — add VAULT_SYNC_E2EE=true option
Acceptance Criteria
References
- VAULT-SYNC-ARCHITECTURE.md — full architecture notes and implementation sketch shared between peers
- WireGuard handles transport; this issue is purely application layer
cryptography package (already a dep via koi-net) provides ECDH + AES-256-GCM
Priority
Medium — current setup is acceptable for non-sensitive collaboration, but should be addressed before using vault sync for private notes or sensitive organizational data.
Summary
Vault sync currently encrypts at the transport layer (WireGuard) but transmits file contents as plaintext in KOI-net event payloads. The relay operator routing WireGuard packets can read all synced file contents, paths, sizes, and timestamps.
This issue tracks adding application-level end-to-end encryption so only the intended peer can decrypt vault sync events.
Current Security Model
Root Cause
KOI-net events carry file content as plaintext JSON. Any node or relay operator with access to the network traffic can read the full event payload — including file content.
Proposed Solution
Phase 1: Symmetric E2EE (content only)
encrypted_vault_sync: trueflag to vault sync peer configKey derivation:
Encryption:
Phase 2: Metadata encryption (optional)
Extend encryption to wrap file paths, sizes, and timestamps alongside content — so relay sees only an opaque encrypted blob per event.
Files to Modify
api/vault_sync.py— encrypt on queue, decrypt on applyapi/vault_sync_models.py(or equivalent) — addencrypted_payloadfield to event schemaapi/koi_net_handler.py/ handshake — store derived shared secret per peerscripts/federation/personal-env.template— addVAULT_SYNC_E2EE=trueoptionAcceptance Criteria
encrypted_payloadare decryptable only by the intended peerVAULT_SYNC_E2EE=trueenv var enables encryption for all vault sync eventsReferences
cryptographypackage (already a dep via koi-net) provides ECDH + AES-256-GCMPriority
Medium — current setup is acceptable for non-sensitive collaboration, but should be addressed before using vault sync for private notes or sensitive organizational data.