Security model — secrets at rest, threat model, operator discipline
What spl serve protects against (default-secure auth + filesystem permissions) and what it does NOT (host compromise, cloud-sync of identity dirs). Read before deploying to a multi-tenant or shared-storage environment.
Identity keypair + provider secrets are stored in plaintext on disk. Filesystem permissions (0600, owner-only) protect against casual file enumeration. They do NOT protect against host compromise, cloud-sync to Dropbox / Time Machine / rsync, forensic disk imaging, or accidental commit to a dotfiles repo. Operators must maintain the discipline below.
TL;DR
~/.syncro/keys/and~/.syncro/secrets/contain plaintext identity + provider keys. Permissions are0600(owner-only).~/.syncro/hub.dbis an unencrypted SQLite carrying every record. Bearer-token plaintext is NOT stored — onlysha256(secret)for validation.- Excluded from auto-backup:
~/.local/share/syncropel/backups/hub.db.bakdoes NOT include the keys/ or secrets/ dirs. - What you must do: exclude
~/.syncro/keys/and~/.syncro/secrets/from cloud-sync (Dropbox, iCloud, OneDrive, rsync, dotfiles repos). On hosted instances: rely on the platform's secret manager rather than mounting~/.syncro/. - What's coming: OS-keyring integration (macOS Keychain, Linux Secret Service, Windows Credential Manager) is post-v0.31 work tracked as SKL-0641. Until it ships, file-permission discipline is the only at-rest defense.
What's stored where
| Asset | Location | Format | Permission | Encrypted at rest |
|---|---|---|---|---|
| Ed25519 identity seed (the DID's signing key) | ~/.syncro/keys/primary | raw 32-byte seed | 0600 | No |
| Provider API keys (Anthropic, OpenAI, etc.) | ~/.syncro/secrets/<provider>.key | plaintext | 0600 | No |
| Bearer tokens (issued + cached server-side) | ~/.syncro/hub.db (tokens table) | sha256(secret) only — plaintext NEVER persisted | n/a (db) | n/a |
| Operator session token (CLI) | ~/.syncro/token | plaintext | 0600 | No |
| Federation pair tokens (peer-issued) | ~/.syncro/hub.db (tokens table) | as above (sha256) | n/a | n/a |
What's protected
Filesystem permissions
All sensitive files land at 0600 (owner-only). spl doctor audits permissions on every run and warns if a file is world-readable:
spl doctorThe audit runs against ~/.syncro/secrets/ and ~/.syncro/keys/. A failed audit returns exit code 1 and prints the offending paths.
Token-at-rest format (ADR-035 D6)
Bearer tokens are NEVER stored in plaintext in hub.db. Only sha256(secret) lands; the plaintext is shown ONCE at mint time and never persisted in the daemon. If you misplace a token, mint a new one — no recovery from hub.db.
Per-daemon keypair scope
The identity keypair is per-daemon, content-addressed via did:key. Rotating the keypair (regenerating the DID) invalidates all federation pairs — peers cache the DID and reject pair traffic from a different keypair. Recovery: re-run spl federation pair after rotation.
Bootstrap discipline
spl serve --daemon writes hub.db to ~/.local/share/syncropel/backups/hub.db.bak automatically on every startup. This backup deliberately does NOT include ~/.syncro/keys/ or ~/.syncro/secrets/ so a cp of the backup (e.g., to a different host for inspection) doesn't accidentally include identity material.
What's NOT protected (operator must mitigate)
Host compromise
Root or the daemon's user account can read every file in ~/.syncro/. The plaintext identity seed lets an attacker:
- Forge records under your DID
- Decrypt federation pair traffic addressed to you
- Issue bearer tokens that pass the daemon's auth gate (after replacing
hub.db)
Mitigation: standard host hardening (disk encryption at the filesystem layer, restrict shell access, monitor for unauthorized SSH). For high-stakes tenants, run spl serve inside a dedicated VM or container with the keys/secrets dir on a separate filesystem volume.
Cloud-sync of ~/.syncro/
Dropbox, iCloud, OneDrive, Time Machine, rsync-to-NAS — all happily pick up ~/.syncro/keys/ and ~/.syncro/secrets/ by default. Once the contents are on a sync provider's servers:
- The provider's logs may retain them indefinitely.
- A subpoena to the provider yields your plaintext keys.
- A breach of the provider's storage exposes them.
Mitigation: explicitly exclude these paths from any sync tool you use. Examples:
# rsync — exclude pattern
rsync --exclude='.syncro/keys/' --exclude='.syncro/secrets/' ...
# Time Machine (macOS)
sudo tmutil addexclusion ~/.syncro/keys
sudo tmutil addexclusion ~/.syncro/secrets
# Dropbox / iCloud / OneDrive — install spl OUTSIDE the sync root,
# or use a non-default data dir:
SYNCROPEL_HOME=/var/lib/syncropel spl serve --daemonAccidental commit to a dotfiles repo
Some operators commit ~/.syncro/config.toml to a dotfiles repo. The default .gitignore may NOT exclude keys/ and secrets/ — re-check yours.
Mitigation: explicitly add to .gitignore:
.syncro/keys/
.syncro/secrets/
.syncro/tokenForensic disk imaging
A stolen laptop without full-disk encryption yields plaintext keys. A discarded SSD without secure-erase yields plaintext keys.
Mitigation: full-disk encryption (FileVault on macOS, LUKS on Linux, BitLocker on Windows). Secure-erase before disposal.
Hosted instance multi-tenancy
A *.syncropel.com Fly Machine runs spl serve with the daemon's identity in the container's filesystem. The Cloudflare provisioning Worker (per ADR-067) provisions a fresh keypair per tenant, but the container's host sees the keys in plaintext while the container is running.
Mitigation: hosted-instance operators rely on Fly's volume encryption + Cloudflare's auth-proxy isolation per the production architecture. Self-hosting users on shared compute (e.g., a VM where another tenant has root) should treat the deployment as compromised by default.
Audit-emission completeness for portability
Per SEC-007 (resolved 2026-05-04 via SKL-0660): every export/import operation emits a core.portability.event.v1 LEARN record on th_audit_portability. Operators auditing "who exported actor X when" or "who initiated instance import" fold this thread:
spl thread records th_audit_portability -o json | jq '.[] | .body'This audit thread does NOT prevent the export from happening — auth scope (Admin for instance-level export, RecordsRead for GET /v1/actors/*/export) is what gates access. The audit makes consequential ops forensically traceable.
Threat model summary
| Threat | Protected? | By |
|---|---|---|
| Casual file enumeration on multi-user host | ✓ | 0600 file permissions |
| Daemon-user-only attacker (no root) | ✓ | 0600 file permissions |
| Network-only attacker (over HTTP) | ✓ | Bearer-token auth (default-secure since v0.16) |
| Replay of leaked bearer token | ✓ | Server-side validation against sha256(secret) lookup |
| Host root / daemon-user compromise | ✗ | (mitigation: host hardening, full-disk encryption) |
| Cloud-sync of identity dir | ✗ | (mitigation: explicit exclusion) |
| Forensic disk imaging | ✗ | (mitigation: full-disk encryption) |
| Accidental git commit | ✗ | (mitigation: .gitignore) |
| Cross-tenant on shared compute | ✗ | (mitigation: hosted-instance platform isolation) |
| Audit gap on portability ops | ✓ | th_audit_portability (since v0.31) |
What's coming post-v0.31
OS-keyring integration (SKL-0641, post-v0.31): integrate the keyring Rust crate to optionally store identity keypair + provider keys in OS keyring services:
- macOS: Keychain
- Linux (with DBus): Secret Service (
gnome-keyring,kwallet) - Windows: Credential Manager
The integration will be opt-in and backwards-compatible — file-based fallback continues to work for headless/server contexts where no keyring service is available.
Decision criteria for v0.32 ship vs later: spec implications around key rotation semantics, backup/restore round-trip behavior, and federation pair re-establishment after key rotation are non-trivial. v0.31 ships with this documentation; SKL-0641 ships when the spec questions resolve.
See also
- Authentication & Service Accounts — bearer-token model + scope hierarchy
- Operator runbook — backup discipline — what's backed up + restore procedure
- Actor portability —
spl export/spl importmechanics spl doctor— automated permission audit- ADR-035 (auth substrate, frozen since v0.15)
- ADR-062 (secrets architecture)
- SKL-0641 (OS-keyring integration, post-v0.31)
syncropel-research/docs/v0.31-security-audit/— full audit findings
Authentication & Service Accounts
Enable bearer-token authentication, create service accounts, pair devices, and manage token lifecycle. Bearer-token auth is enforced by default on every spl serve daemon.
Federation Pairing
Pair two Syncropel daemons so they can sync threads, exchange records, and converge on shared state. Covers when to pair, the pair lifecycle, debug recipes for the common failure modes, soak-test results, and capacity planning.