Security and privacy

This page explains how shenas stores your data, what leaves your device and under what conditions, how keys are managed, and what shenas does and does not protect against. These are technical claims, not marketing copy. Client-side claims are verifiable in the open-source code; server-side behavior can be verified by inspecting network traffic.

Read the source →

What this page said before. Until 2026-10-01, this page said every device generates an Ed25519 keypair and registers with the coordination server, and that each device holds its identity key inside its encrypted database. It also stated device custody the same way for every build, and linked several claims to source paths. This page was updated on 2026-10-01 to state what each build does today, scoped per build, with each claim citing the module that keeps it true.

Where your data lives

Raw personal data is never uploaded to shenas servers. It lives in a DuckDB database on your own device, under your own filesystem.

Device-local DuckDB files

shenas stores data in two DuckDB files per device. A device-wide registry at ~/.shenas/data/shenas.duckdbholds user accounts, sessions, plugin install state, and system settings. A separate per-user database holds your personal data (health metrics, finance records, journal entries, and everything else synced by your sources).

Both files live under your home directory. They are never uploaded to shenas servers. shenas.ai does not operate a database of your personal records (health, finance, journal, or source data). The coordination server holds device identities — not records.

Encryption at rest

In the desktop release, both local DuckDB databases — the device-wide registry and your per-user data — are encrypted on disk with AES-256-GCM. The encryption key is applied when the database is opened, so plaintext records are never written to durable storage.

Source:plugins/core/shenas_plugins/core/db.py (ENCRYPTION_KEY), app/persistence/manager.py (key=self.get_db_key()) at commit 4489ecef3e; the source at this commit is not yet published.

In the mobile build, unreleased, the local database is encrypted on disk with AES-256-GCM the same way.

Source:shenas-core/src/connection.rs (const CLAUSE: &str = " (ENCRYPTION_KEY ";) at commit 1ac5c332b0; the source at this commit is not yet published.

In the desktop release, the device-wide database key is stored in your operating system's native secret store — Keychain on macOS, the Data Protection API on Windows, or Secret Service (libsecret) on Linux — bound to this device. On macOS and Windows that binding excludes the entry from iCloud Keychain sync, from Windows Credential Roaming, and from a backup restored onto a different device. Secret Service has no sync of its own, so an ordinary reinstall does not hand your key to a new machine, but a home-directory backup restored under the same login password is outside what this build can detect or prevent. The desktop release does not take the key from the SHENAS_DB_KEY environment variable: it refuses to start while that variable is set, so this custody cannot be swapped for a key held in the environment.

Source:shenas-core/src/keyslot/keyring/apple.rs (kSecAttrAccessibleWhenUnlockedThisDeviceOnly), shenas-core/src/keyslot/keyring/windows.rs (credential.Persist = CRED_PERSIST_LOCAL_MACHINE;), shenas-core/src/keyslot/keyring/secret_service.rs (const SESSION_COLLECTION: &str), app/persistence/device_keyring.py (class DeviceBoundKeyring:), app/config.py (the desktop profile refuses a database key from the environment) at commit 22de9d6a2b; the source at this commit is not yet published.

In the mobile build, unreleased, the store key is a file in the app's private directory, in software custody.

Source:app/mobile/src-tauri/host/src/store_key.rs (const KEK_FILE: &str = "store.kek";), shenas-core/src/custody.rs (options.mode(0o600);) at commit 1ae33eef7a; the source at this commit is not yet published.

In development builds, a sync node's store key is held in your operating system's keyring when the node runs on a desktop. A headless node keeps it as a plaintext file only when that trade-off has been accepted explicitly: for a node linked to your account, in its setup record, which is checked again every time it starts; for a node started by hand, with a flag given each time it starts. Otherwise the node refuses to start.

Source:shenas-core/src/custody.rs (pub fn keyslot_keyring_declining_recovery(), shenas-core/python/src/node.rs ("keyring" => {), app/sovm/linked.py (record.store_key_rung.value), shenas-node/shenas-node/src/main.rs (if !invocation.rung_zero_acknowledged {) at commit 58d615333b; the source at this commit is not yet published.

Direction of travel (P-0116)

The mobile build's store key is planned to move from a software-custody file to a hardware-backed keystore — the Secure Enclave on iOS, or Android Keystore / StrongBox on Android — as those backends are built and wired in. None is wired today; software custody, described above, is what ships. Each rung lands as its own register row, once it exists.

Source:app/mobile/src-tauri/host/src/store_key.rs (Closing that second gap is platform-vault work) at commit 1ae33eef7a; the source at this commit is not yet published.

No cloud copy of your records

There is no background upload of your source data to shenas infrastructure. The coordination server used for device sync and federated learning does not receive, store, or process your raw records. If a shenas server were breached, the attacker would find no personal health, finance, or lifestyle data belonging to any user. The servers do hold device-identity metadata (a public key per device, and a one-way hash of its root key — never a device name); see the compelled-disclosure item in the threat model for what that means under lawful process.

What crosses the wire

This is the complete list of network calls the shenas application makes. The first three are part of normal use; the rest only happen when you take a specific action or enable an optional feature. No personal records are sent to shenas servers in any of these calls.

Device registration

The desktop release ships no sync node and registers no device with the coordination server.

Source:scripts/build/pyinstaller_build.py (TARGETS = {) at commit 4489ecef3e; the source at this commit is not yet published.

In development builds, a sync node announces an Ed25519 channel key and a site identifier to the coordination server; it does not send a device name.

Source:server/api/shenas_ai_api/peers.py (ENDPOINT_ID_PATTERN), server/api/shenas_ai_api/peers.py (SITE_ID_PATTERN) at commit 4489ecef3e; the source at this commit is not yet published.

Multi-device sync

Multi-device sync runs between sync nodes, which are development builds only and not part of the desktop release. When two nodes pair, they exchange new data events directly, over a mutually authenticated connection that does not pass through our servers. Each node is configured with the peers it will talk to and the channel key that identifies each one, so a node dials only peers you have paired it with, and a connection presenting an unknown key is refused rather than trusted. There is no relay in this path today: nothing is buffered on a server, so there is no stored message for us to read, hold or lose. Two nodes that cannot reach each other directly do not sync until one of them can — the changes wait locally. A relay that would carry such a connection, and a mailbox that would hold changes for a node that is never online at the same time, are both built and neither is deployed; we will say so here before either is.

Federated learning gradients

Federated learning is not yet available in this release; the description below covers what will happen when it ships and you opt in. If you opt in to federated learning, your device trains a model locally on your data using PyTorch. After training, the updated model weights (not your records) are transmitted to the coordination server. The server aggregates weights from all participants using secure aggregation (SecAgg+) and differential privacy (DP-SGD) before producing an updated model. The server never receives individual user records.

Data source sync

If you install a source plugin and configure it (for example, a fitness tracker, a calendar, or a code host), shenas contacts that service's API directly from your device, using the credentials you provided, to fetch your data. Token refreshes happen automatically against the same OAuth endpoints during background sync. No data transits through shenas servers — the source and your device talk directly.

Optional shenas.ai features

If you use the LLM analysis or literature review features with a shenas.ai account, category-level queries (for example, topic names like "sleep" or "stress") are sent to shenas.ai. No personal records are transmitted. These features are off by default and only run when you ask for them; running shenas without a shenas.ai account skips them entirely.

Plugin install

When you install a plugin, shenas downloads a signed wheel from repo.shenas.net and its Python dependencies from pypi.org. The signature is verified before install. No personal data is transmitted — the only thing either server learns is which packages you asked for.

Local AI model download

If you run shenasctl model download to install a local AI model, the GGUF model file is fetched from HuggingFace. No personal data is sent — the request is just for the file. Once downloaded, the model runs entirely on your device.

Single-device installs

If you run shenas on one device only and do not participate in federated learning, the shenas application contacts these external servers during normal use:

  • The coordination server (account authentication, and publishing where your devices can be reached).
  • The third-party API of each connected data source when its scheduler is due (for example, the API endpoint of a fitness tracker you connected). These calls go directly from your device to the source; no data routes through shenas servers.

These additional servers are contacted only when you take a specific action: shenas.ai for an LLM or literature-findings query you triggered;repo.shenas.net and pypi.orgwhen you install a plugin; huggingface.cowhen you download a local AI model. Your configured LLM provider — a local Ollama server or a third-party API — is also contacted only when you run an LLM-assisted analysis, and that call goes to the provider you chose, not to shenas.

Local sync logic and analysis run entirely on your device.

Key management

Database encryption keys

In the desktop release, your per-user database key is wrapped with AES-256-GCM under a key derived from your local password with scrypt (N=16384, r=8, p=1), and the stored record that checks your password does not reveal it. A new account's database key is random; an account created on an earlier release keeps the key it was created with, and it is wrapped the next time you sign in. Until then, that earlier release's stored record still reveals the key to anyone who can read the device-wide database. If you choose to stay signed in for background sync, a copy of the database key is kept in your OS keyring; it is removed when you sign out. That removal call does not report failure, so on the rare occasion it fails, the copy can be left behind without a visible warning. shenas does not hold a copy of this key.

Source:app/auth/local_users.py (class StoreKeyEnvelope:), app/auth/local_users.py (def remember_key), app/auth/local_users.py (def forget_key), app/api/users.py (@router.post("/logout")) at commit a04eede7d6; the source at this commit is not yet published.

In the desktop release, setup creates a recovery key that only you hold, unless you choose to skip it; shenas keeps no copy of your recovery key, your device-wide key, or your per-user key — there is no key escrow, no key-recovery service, and no server route that receives any of them.

Source:shenas-core/src/keyslot/recovery.rs (pub struct RecoverySecret {), shenas-core/src/keyslot.rs (the recovery secret in any encoding (clause 3)), app/persistence/manager.py (class RecoveryKeySetup:), server/api/shenas_ai_api/peers.py (A record carries no ``root_public_key``.) at commit c29340519a; the source at this commit is not yet published.

In the mobile build, unreleased, shenas holds no copy of the store key: there is no key escrow, no key-recovery service, and no server route that receives it.

Source:server/api/shenas_ai_api/peers.py (A record carries no ``root_public_key``.) at commit 4489ecef3e; the source at this commit is not yet published.

Device identity keys

In development builds of the sync node, unreleased, the root key and the channel key are each a file in the node's state directory, in software custody.

Source:sovm/sovm-run/src/state.rs (pub const ROOT_KEY: &str = "root.key";), sovm/sovm-run/src/state.rs (pub const CHANNEL_KEY: &str = "channel.key";) at commit 4489ecef3e; the source at this commit is not yet published.

Direction of travel (P-0116)

The sync node's root and channel keys are planned to move from plaintext files to a hardware-backed keystore — TPM 2.0 through the Platform Crypto Provider, where available — as that backend is built and wired in, alongside a release of the node itself. None is wired today; software custody, described above, is what exists in development builds. Each rung lands as its own register row, once it exists.

Source:sovm/sovm-run/src/identity.rs (impl RootIdentityKeystore for RootIdentity), sovm/sovm-run/src/identity.rs (`tss-esapi`) at commit 4489ecef3e; the source at this commit is not yet published.

What device loss means

In the desktop release using OS-keyring-backed encryption, losing a device does not expose your data: reading the database without the OS keyring or your password is not feasible with current hardware, and because that keyring entry is bound to this device, restoring your other files to a new device does not also hand it a working copy of your key. On macOS and Windows the binding excludes the entry from sync and from a backup restored onto a different device; on Linux, a home-directory backup restored under the same login password carries the Secret Service entry with it, which this build cannot detect or prevent. One further exception: this does not hold on the mobile build, where an image of the app's private directory carries both the store key and the encrypted data together.

Source:shenas-core/src/keyslot/keyring/device.rs (pub const DEVICE_KEYRING_SERVICE), app/persistence/manager.py (def get_db_key), app/mobile/src-tauri/host/src/store_key.rs (defend against an attacker holding the whole) at commit 1ae33eef7a; the source at this commit is not yet published.

shenas cannot restore data from a device you no longer have. There is no cloud backup of your personal records. This is a deliberate property of the architecture, not a missing feature.

Threat model

What shenas protects against, and what it does not claim to protect against. Honest security documentation names both.

What shenas protects against

  • Server-side breach. A compromise of shenas infrastructure would not expose the data on your shenas node — that never reaches our servers. What the servers hold is limited: your account, a public key and a one-way hash of the root key for each device you have registered, and aggregated model weights. There is no relay and no buffered payload for a breach to expose.
  • Network interception. All data in transit is encrypted with TLS, so an eavesdropper on the network between your device and our servers sees only ciphertext. Sync between your own devices does not pass through our servers at all. Federated learning gradients are protected by secure aggregation before the server sees individual contributions.
  • Unattended device access.

    Databases in the desktop release are encrypted at rest with AES-256-GCM. In OS-keyring-backed mode, reading the database files without the OS keyring or your password is not feasible with current hardware, and that keyring entry is bound to this device, so a copy of your files alone is not a copy of a working key. On macOS and Windows the binding excludes the entry from sync and from a backup restored onto a different device; on Linux, a home-directory backup restored under the same login password includes the Secret Service entry, and so is a copy of a working key. This does not hold on the mobile build.

    Source:plugins/core/shenas_plugins/core/db.py (ENCRYPTION_KEY), shenas-core/src/keyslot/keyring/device.rs (pub const DEVICE_KEYRING_SERVICE), app/persistence/manager.py (def get_db_key) at commit 22de9d6a2b; the source at this commit is not yet published.

  • Supply-chain compromise via plugins. Every plugin distributed through the official package repository is signed with an Ed25519 key. The CLI verifies the signature before installation. A tampered plugin will not install.
  • Compelled disclosure to shenas of your records.shenas does not hold the contents of your records, so a subpoena or court order served on shenas the company for those contents has no records to produce. We can still be compelled, under lawful process, to disclose the metadata we hold (a public key and a one-way hash of the root key for each device — we hold no device names, no record of which of your devices sync with each other, and no message contents), and to disclose the existence of process where the law requires us to do so.

What shenas does not protect against

  • A local attacker with both device access and your credentials.If someone has your unlocked device and your local shenas password, they can read your data. The database encryption assumes the attacker cannot authenticate to the OS or guess your password.
  • OS-level or hypervisor-level compromise.If the operating system itself is compromised (malware with root access, a malicious hypervisor), all bets are off regardless of application-level encryption.
  • Compelled self-disclosure by you. shenas cannot protect you from being legally compelled to hand over your own device or decrypt your data. It can only ensure that shenas the company cannot be compelled to hand over what it does not hold.
  • Malicious plugins you choose to install from untrusted sources.Plugins have access to your local data by design. Installing an unsigned or third-party plugin from an untrusted source is equivalent to running untrusted software with access to your data directory.
  • Source API credentials.

    Source API credentials — OAuth tokens and API keys for the services you connect — are stored in your local database. That database's encryption key is their only protection; a successful attack on an unlocked device can expose them.

    Source:plugins/sources/core/shenas_sources/core/base_auth.py (class SourceAuth(SingletonTable)) at commit 4489ecef3e; the source at this commit is not yet published.

Telemetry and logging

No transmitted telemetry

shenas does not transmit usage telemetry. The application maintains a local diagnostic log written to your own database for in-app trace and log views; nothing in that log leaves your device. No analytics calls, crash reports, feature-usage beacons, or identifiers are sent to shenas servers in the course of normal use.

You can verify this by running shenas behind a local network proxy and inspecting outbound traffic.

The code is open source; the relevant paths aresovm/ and shenas-node/ for sync and node identity, app/fl/ for federated learning, andapp/telemetry/ for the local-only diagnostic exporters. The source at these paths is not yet published at a current commit; see Open source, below.

The network calls shenas makes during normal use — excluding data source sync, plugin install, model download, and optional features you explicitly enable — are the ones described in "What crosses the wire" above.

How to verify these claims

Client-side claims are grounded in code you can read and run. Server-side behavior can be verified by inspecting network traffic.

Device-to-device sync

See shenas-node/for the sync process: which peers a node will dial, how a connection is authenticated against the keys the node itself holds, and what it refuses. You can confirm there that a peer it was not configured with is refused, and that no server sits in the path.

Plugin signing

See the CLI source inshenasctl/for how Ed25519 signatures are verified before a plugin wheel is installed.

Open source

The core platform and CLI are published under an open-source licence atgithub.com/shenas-org/shenas. That mirror does not currently carry the sync layer (sovm/, shenas-node/) or the federated-learning client (app/fl/) at a current commit — see Telemetry, above, for those paths. The server-side coordination and aggregation code (the federated-learning coordinator) is not open source. Claims about server behavior can be verified by inspecting network traffic rather than server source.

Security disclosures

Reporting a vulnerability

If you find a security issue in shenas, please report it privately. Do not open a public GitHub issue for unpatched vulnerabilities. Use GitHub's private security advisory feature atgithub.com/shenas-org/shenas/security/advisories/newor email [email protected].

We aim to acknowledge reports within two business days and to publish a fix or mitigation within 90 days of confirmation.

v0.19.2