# TensorPM Security & End-to-End Encryption

**Version 1.2** — Last updated: 2026-05-03
**Canonical:** https://tensorpm.com/SECURITY.md
**HTML:** https://tensorpm.com/en/legal/security · https://tensorpm.com/de/legal/security

This is the agent- and crawler-readable summary of TensorPM's end-to-end encryption (E2EE) design. It is suitable for indexing, security review, and procurement. The HTML rendering above is the canonical user-facing version.

## Summary

TensorPM is local-first with opt-in cloud sync. With Cloud or Pro, project content is encrypted on the user's device with keys that never leave their devices in plaintext. Our servers see only encrypted blobs and the structural metadata required to route updates between devices.

If our database is dumped, an attacker gets ciphertext only. They cannot read projects, action items, descriptions, deadlines, budgets, or AI-generated summaries without the user's device key.

This is meaningfully different from typical SaaS project management tools, where the vendor's database holds project content in plaintext and any administrator, support engineer, or compromised credential at the vendor can read it. With TensorPM's E2EE, that path is closed by design — not by policy.

## What is encrypted (client-side, before upload)

- Projects: name, description, goal, scope, dependencies, timeframe, budget, health
- Project arrays: success criteria, milestones, requirements
- Project settings: view sorting, priorities, guidance, wizard state
- Categories: name
- Action items: text, description, all priority/complexity/impact/urgency fields, dates, status, color
- Effort tracking: planned, actual, estimated values and units
- Budget tracking: amounts (planned and actual)
- Timer tracking: active state, start/end times, durations, history
- Recurring items: text, description, all scheduling/priority fields
- Files: AI-generated distilled summaries

## What is plaintext on the server (and why)

- Workspace name and description (dashboard, billing, invitations)
- Workspace membership and role assignments
- Stable identifiers: workspace ID, project ID, action item ID, display ID
- Server-set timestamps (used for last-write-wins conflict resolution)
- Enum-style routing values (e.g. `effort_type=actual` vs `planned`) used as composite keys

This is metadata, not content. The server can count items and observe structure; it cannot read what those items are about.

## Hosting and data residency

- Servers: Hetzner Online GmbH, Germany / EU (Nuremberg). Account and sync processing happens within the EU.
- Transport: TLS 1.2+, certificates issued by Let's Encrypt. TLS is additional to, not a replacement for, E2EE.
- Subprocessors: Stripe (billing), SendGrid (transactional email), Cloudflare Workers AI (proxy AI inference, Pro only). All sit outside the E2EE boundary; none see encrypted project content.
- AI inference via the TensorPM Proxy uses Cloudflare Workers AI on a global network; data centre selection is automatic and not guaranteed within the EU. See https://tensorpm.com/legal/privacy.
- Full subprocessor list: https://tensorpm.com/legal/dpa.

## On the user's device

- Local store: SQLite database on each device (TensorPM is local-first).
- At-rest protection on device: provided by OS full-disk encryption (FileVault / BitLocker / LUKS). The application database is not separately encrypted at the file level. Users are advised to enable full-disk encryption.
- Secrets (device private key, AI provider API keys, encryption-related material): stored in the OS keychain via Electron `safeStorage` (macOS Keychain, Windows DPAPI, Linux libsecret), separate from the application database. When recovery backup is saved, E2E key material may also be included in the encrypted recovery payload; it is not sent to our servers in plaintext.
- The sync engine encrypts data before upload. Local readers see plaintext (it's where the user works); network and server-storage readers do not.

## Cryptographic primitives

Library: [libsodium](https://libsodium.org). No custom crypto.

| Purpose | Primitive |
| --- | --- |
| Symmetric authenticated encryption of records | XChaCha20-Poly1305 (AEAD, 192-bit nonce) |
| Asymmetric key transport between devices | X25519 Sealed Box (Curve25519 + XSalsa20-Poly1305) |
| Password-based key derivation for recovery | Argon2id (m=64 MiB, t=3, p=4) |
| Random material | OS CSPRNG via libsodium `randombytes_buf` |

Each record uses a fresh random nonce. AAD binds ciphertexts to `{workspaceId, table, primaryKey}`, preventing cross-row or cross-workspace tampering by a server-side adversary.

## Key hierarchy

1. **Device keypair (X25519)** — generated on first launch on each device. Stored in OS-level keychain via Electron `safeStorage`. Private key never leaves the device in plaintext. Current recovery backups include encrypted device key material in the opaque payload, but recovery imports workspace keys and creates a fresh device keypair for the new device.
2. **Workspace key (XChaCha20-Poly1305)** — symmetric. Generated by workspace owner. Distributed to other devices and members by sealing it to the recipient's device public key (an "envelope"). Server stores envelopes; each envelope can only be opened by the device whose private key matches.
3. **Recovery backup key** — derived locally via Argon2id from the password available to the desktop app during password login or manual Key Backup save. It wraps the recovery payload. Server stores only the encrypted recovery payload, salt, and key-derivation parameters; it cannot read the workspace keys or device key material inside.

## Account password and recovery backup

- Account password authenticates user → server (login, billing, workspaces). Stored as a strong server-side hash. Password reset does NOT by itself decrypt or re-encrypt encrypted content.
- Recovery backup uses Argon2id locally in the desktop app to derive a backup-encryption key from the password available during password login or manual Key Backup save. Password logins automatically create or refresh this backup when E2E keys exist; magic-link and passkey logins cannot refresh it because the app does not have the password in those flows.
- Forgetting the password that encrypted the current recovery backup + losing all devices = unrecoverable encrypted content. By design.

## Threat model

Account sign-in status: TensorPM supports password login, email magic links, and passkeys/WebAuthn on supported platforms. Dedicated TOTP-style 2FA is not currently available.

**Protected against:**
- Full database dump of our sync server (PostgreSQL + PowerSync) — yields ciphertext only for content fields.
- Compromised network path between device and server — content is end-to-end encrypted; transport is additionally TLS-protected.
- Malicious or compromised cloud provider with read access to our storage.
- Compromised TensorPM operator account (support, ops, infrastructure) — operators have no decryption capability.
- Cross-workspace or cross-row tampering — AAD binding causes substituted ciphertexts to fail authentication.

**Not protected against:**
- Compromise of the device itself (malware, unlocked machine, OS keychain exfiltration).
- Weak password if a recovery backup exists. Argon2id raises brute-force cost substantially; a guessable password is still guessable.
- Traffic-analysis on metadata. Server can observe *that* a workspace was updated, *when*, and by *which user* — not *what* changed.
- Forward-compromise of historical records by an ex-member. Removal revokes new-envelope receipt; workspace-key rotation on revocation is not currently implemented.

## AI providers

- BYOK: prompts go directly from device to chosen provider (OpenAI, Anthropic, local Ollama, etc.) using user's own API key. The TensorPM proxy is not in this path.
- TensorPM Proxy (Pro): prompts route through Cloudflare Workers AI to Moonshot Kimi K2.6. Models require plaintext for inference — true of every AI provider, by physics. Prompts/completions are not retained. See https://tensorpm.com/legal/privacy.
- E2EE protects the project graph at rest and in transit. It does not (and cannot) encrypt prompts being sent for inference.

## Data portability and account deletion

- Project export: decrypted project data export is available from the desktop app. The primary project export format is JSON (`.json`). Export contains plaintext under user control.
- Diagnostics export: support bundles are separate ZIP files for troubleshooting. They can include app/version information, sanitized workspace and sync status, sync telemetry, recent app logs, and error context. Detailed AI logs are excluded by default and included only if explicitly selected, because they may contain project content.
- Deletion: account deletion via info@tensorpm.com. Removes account, encrypted content blobs, envelopes, recovery payload, and workspace metadata from sync infrastructure. Backup retention windows: see https://tensorpm.com/legal/privacy and https://tensorpm.com/legal/dpa (GDPR Art. 17).
- Local copy: server-side deletion does not touch the local database on user devices. Uninstall + remove application-data directory to wipe locally.

## What users should do

1. Enable full-disk encryption on every device running TensorPM.
2. Use a strong, unique account password (≥12 characters, ideally a passphrase). Store it in a password manager.
3. Revoke unused devices in the app's security settings (stolen, sold, wiped).
4. Keep the recovery backup current. Password login refreshes it automatically; magic-link and passkey login do not.
5. Use passkeys/WebAuthn where supported.
6. Lock the machine when away from it.
7. Keep TensorPM updated.

## Vulnerability reporting

Email: **info@tensorpm.com**
Acknowledgement target: 72 hours.
Coordinated disclosure in good faith. No paid bug bounty currently; reporters credited publicly with consent.
Do not file security issues as public bug reports or GitHub issues.

## Related

- Privacy Statement — https://tensorpm.com/legal/privacy
- Data Processing Agreement (GDPR Art. 28) — https://tensorpm.com/legal/dpa
- Terms of Service — https://tensorpm.com/legal/terms
- Third-Party Notices — https://tensorpm.com/legal/third-party-notices
