Trust and privacy
What's stored, what isn't, what the relay can see, and the cryptographic model. The auditable version of the marketing pitch.
Viewport's privacy claim is specific. This page is the auditable version of the pitch, with citations into open-source code where it matters.
The four invariants
- Session transcripts are never persisted server-side. The daemon owns them. The platform has no table to leak from.
- Context Vault entries are HPKE-encrypted on the client. The platform stores ciphertext. Server staff cannot read context.
- Relay frames are E2EE between daemon and client. The relay sees envelopes (workspace, runtime target) but cannot decrypt payloads.
- Per-org daemon identity prevents cross-tenant correlation. Each org binding has its own keypair + machineId; admins in org A cannot identify a machine as also belonging to org B.
Each invariant is implemented in code in the open-source daemon and relay.
What's stored, what isn't
| Object | Stored at rest? | Where? |
|---|---|---|
| Session frames (prompts, tool calls, transcript) | No. | Daemon only, under ~/.viewport/. |
| Plans (the reviewable artifact) | Yes | Platform DB. They're meant to be shared. |
| Inbox items | Yes | Platform DB. They're decision queues. |
| Context Vault entries | Ciphertext only. | Platform DB. Keys never sent. |
| Context Vault candidates (proposed) | Ciphertext only. | Same. |
| Workflow definitions | Yes | Platform DB + mirrored to repo files. |
| Workflow run records | Yes | Platform DB. The run is auditable; the transcript is not. |
| Audit events | Yes | Platform DB. That's the point. |
| Daemon identity keypair | Local only | ~/.viewport/relay-identities/{workspaceId}.json, mode 0o600. |
| Per-binding install/runtime/machine ids | Yes (per org) | Platform DB. Different per org. |
| Relay frame payloads | No. | In-flight only. Lost on relay restart. |
| Pairing codes | Yes (until consumed or expired) | Short-lived, ULID, expires in 15 min. |
| API tokens | Hashed | Argon2 hash; raw token shown once on creation. |
| Pre-shared keys (PSK) for pair-time auth | Yes | Hashed. |
The daemon ↔ relay path
- Handshake: Noise IK or IKpsk2 (selected per workspace).
- Session crypto: AES-256-GCM with per-direction nonces.
- Envelope: workspace id, runtime target id, frame type. Visible to relay for routing.
- Payload: opaque ciphertext. Relay cannot read.
Conformance vectors for replay-testing the handshake: packages/daemon/docs/relay-noise-v3-conformance-vectors.json. Run npm run test:conformance in the daemon repo to replay them.
What the relay sees
The relay is operationally stateless. Per-process state only:
- In-memory connection registry (workspace → connections).
- IP-level rate limit counters.
- Recent log buffer for
/logsadmin endpoint. - Prometheus metrics counters.
All of this is lost on relay restart. No payloads are persisted. The relay can identify the envelope of every frame. Which workspace, which runtime target. But not the content.
If you self-host the relay, the wire path stays in your network. You control TLS, rotation, and any IP allow-listing. See Self-host: Security posture.
What the control plane sees
The control plane (Laravel API + Postgres + web app) sees:
- Plaintext: members, teams, machine metadata (name, OS, last seen), workflow definitions, plan artifacts, inbox items, audit events, run records, pairings, settings, integrations.
- Ciphertext only: context vault entries.
- Never: session frames (no table), context plaintext (no key material).
In a worst-case scenario where the platform is compromised:
- The attacker can read members, teams, workflow definitions, plans, inbox items, audit history.
- The attacker cannot read context vault content. Decryption requires private keys held only on devices granted access via the vault key-share machinery.
- The attacker cannot read session content. There's no table to read from.
- The attacker can revoke pairings (effectively a DoS). The attacker cannot impersonate a daemon to a relay without the daemon's private key.
The cross-tenant story
Each tenant (workspace) is isolated through:
- Row-level scope at the DB layer. Every tenant-owned table carries
workspace_id. A global Eloquent scope filters every query by the active workspace. Forgetting a filter no longer leaks because the scope makes unscoped queries return nothing (or throw, in tests). - Per-org daemon identity. Different
machineId, different keypair, differentinstallId. A machine in org A is cryptographically indistinguishable from any other org A machine when viewed from org A's data, and is invisible from org B. - Workspace-scoped teams and share groups. A team or share group from org A cannot grant access to an org B resource. Enforced at write time (ACL entry validation) and read time (the
ResourceAccessservice double-checks). - Audit logs are tenant-scoped. Cross-org queries return nothing for non-Viewport-staff actors.
What's not yet enforced
- Schema-per-tenant or DB-per-tenant isolation. Single shared DB with row-level scope. Sufficient for SMB and mid-market. Some enterprise compliance contexts require harder isolation.
- HSM-backed crypto. Daemon keys are file-based with
0o600perms. HSM/TPM-backed signing is on the roadmap. - Customer-managed encryption keys (CMEK). Context vault keys are managed client-side today; bringing your own root key to wrap them is roadmap.
- Air-gapped operation. The daemon needs to reach the relay. Fully offline operation isn't yet supported.
How to verify the claims yourself
- Daemon is open source: github.com/viewportai/viewport/tree/main/packages/daemon. Read
src/server/for the relay bridge,src/core/for identity,src/session-manager.tsfor what gets emitted. - Relay is open source: github.com/viewportai/viewport/tree/main/services/relay. Read
relay-frame-validation.tsfor what's accepted,relay-state.tsfor what's stored in memory. - Wire protocol is documented:
packages/daemon/src/server/ws-protocol.tsis the source of truth for frame types. - Conformance vectors: run
npm run test:conformanceagainst the daemon to prove the handshake matches spec. - Penetration test reports: available under NDA. Email us.
Reporting a vulnerability
We run a coordinated disclosure program. Email security@getviewport.com with details, expect a reply within 48 hours, and 90-day public disclosure unless we agree on a longer timeline. See Security policy.
Where to go next
- Self-host: Security posture. What self-hosting the relay changes.
- Concepts: Machines and pairing. The per-org identity story.
- Concepts: Context Vault. The encryption details.
- Audit log. What's recorded.