# TensorPM Security & End-to-End Encryption

**Version 1.4** — Last updated: 2026-08-20
**Canonical:** https://tensorpm.com/SECURITY.md
**HTML:** https://tensorpm.com/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, which is available on every plan, including the free Trial plan. In a cloud workspace, 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
- A cost entry's status (`estimate` / `committed` / `settled`) and the ID of the action item or material item it pays for, kept readable so an older desktop version cannot drop them when it re-saves the entry; its name and amounts stay encrypted

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), Fireworks AI (proxy AI inference on Trial and Pro — DeepSeek V4.1 Flash for compatible text requests), Google Cloud / Vertex AI Gemini 3.7 Flash (image and vision requests plus availability fallback). All sit outside the E2EE boundary. AI providers receive the plaintext prompt and project context supplied for inference; they do not receive Cloud Sync ciphertext as such.
- AI inference via the TensorPM Proxy is served by Fireworks AI / DeepSeek V4.1 Flash by default for compatible text requests. Fireworks serves inference from US/global regions on its serverless tier and does not guarantee EU-only data residency. For open models, prompt and generation data is not written to persistent storage without explicit opt-in; volatile prompt caches may remain for several minutes, and service metadata is logged. Image and vision requests are processed by Vertex AI Gemini 3.7 Flash, which also serves as the fallback for availability or capacity failures affecting compatible text requests. The production Vertex path uses Google's global endpoint, for which Google does not provide an EU ML-processing-location guarantee. Published Gemini models use project-isolated in-memory caching by default for up to 24 hours; limited abuse-monitoring logs may apply depending on the applicable terms and configuration. Google does not use customer data to train or fine-tune AI/ML models without prior permission or instruction. Because both managed paths are global and image requests go directly to Vertex, users requiring strict EU residency for every AI request should use Business BYOK with an EU provider or a local model (Ollama, vLLM, LM Studio). 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.
- 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.

## AI providers

- BYOK: Business-only API-key management lets prompts go directly from the device to the chosen provider (OpenAI, Anthropic, Google, Mistral, etc.) using the user's own API key. The TensorPM proxy is not in this path.
- TensorPM Proxy (Trial/Pro): compatible text requests route to Fireworks AI / DeepSeek V4.1 Flash by default; image and vision requests route to Vertex AI Gemini 3.7 Flash, which also serves as the availability/capacity fallback. Models require plaintext for inference. TensorPM Proxy does not persist prompt or completion content; provider-side volatile caching and limited logging may apply as described above. Customer data is not used for model training without authorization. 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
