ParallelPathdocumentation

Security

A complete description of how ParallelPath protects data - in transit, at rest, in use - and what it can't protect against. Everything here can be checked against the source code running on this server. For how it has been tested and what was found, see the Security testing report.

Summary

Layer Protection
In transit (browser <-> server) HTTPS only: TLS 1.2/1.3, modern ciphers, HSTS, HTTP redirected to HTTPS
In transit (inside the server) Unix sockets / loopback only - nothing crosses a network in clear; remote AI providers must use HTTPS; email uses STARTTLS/TLS; backup copies use SFTP
In transit (calls) WebRTC DTLS-SRTP, end-to-end between the two devices; the relay can't decrypt
At rest (content) AES-256-GCM with a separate key per family; per-file keys; every sensitive field encrypted
At rest (personal details) Usernames, emails, names, IP addresses, devices, notifications - all encrypted columns (keyed blind index for sign-in lookups)
At rest (whole store) Required LUKS2 volume (argon2id) holding the database, file store and backups work area; the app refuses to start without it
Backups Encrypted data inside an encrypted archive; keys only in a passphrase-protected recovery bundle
Administrators Separate portal; can't read family content or personal details; can't take over accounts without the person's consent
Keys systemd encrypted credentials (TPM2-sealed when available); never in the database or on disk in plain form
Accounts argon2id passwords, mandatory two-step verification, lockout, rate limiting, short sessions
Integrity Messages immutable; append-only evidence tables enforced by the database; hash-chained audit trail
Isolation Dedicated system user, systemd sandbox, AppArmor, least-privilege database roles, localhost-only app
Web hardening Strict Content-Security-Policy, cross-site request blocking, secure cookies, no third-party content
Server Firewall, fail2ban, automatic security updates, encrypted backups

1. Encryption in transit

Calls

2. Encryption at rest

Envelope encryption

master key (32 bytes)  -- systemd encrypted credential, TPM2-sealed if available; never in the database
  |-- family data key (one per family, random)  -- stored only in wrapped (encrypted) form
  |     |-- every sensitive database field        AES-256-GCM, bound to table.column:row-id
  |     |-- per-file keys (one per file version)  -- wrapped by the family key
  |           |-- file blobs on disk              AES-256-GCM in authenticated 1 MiB chunks
  |-- system key (random)  -- for data belonging to no family (2FA secrets, settings secrets, feedback)

What is encrypted

Message subjects and bodies, amendments and reasons; event titles, notes and locations; file and folder names and contents; every file version; expense descriptions, notes and payment references; info-bank entries; journal entries; check-in locations and notes; child names and dates of birth; family names; swap notes; restriction notes; audit-trail reasons and details; report files; call recordings; 2FA secrets and recovery codes; AI provider API keys; problem reports.

Personal details are encrypted too

Usernames, email addresses, display names, IP addresses, device/browser strings, notification texts, membership titles, invitation emails and problem-report subjects are stored as AES-256-GCM encrypted columns (system key). Sign-in still works because usernames and emails also have a keyed blind index (HMAC-SHA256 with a key derived from the master key): the database can match an exact value without ever holding it.

An automated test writes known values through the app and then scans the raw database file and the file store byte-by-byte to prove none of them appear in clear.

What is left as plain values - inside the encrypted volume

Timestamps, record types and states (e.g. "expense: approved"), amounts and dates (so balances and calendars can be computed) and category labels are stored as plain values so the database can sort and total them. They are still encrypted at rest by the mandatory LUKS volume below, and hold no names or content.

Full-volume encryption (required)

Every installation (direct or Docker) creates a LUKS2 encrypted volume (argon2id key derivation) that holds the PostgreSQL data and the file store. Its key is a systemd encrypted credential (TPM2-sealed when available) and it unlocks automatically at boot. A separate LUKS recovery passphrase goes into the encrypted recovery bundle. Encryption can't be switched off: on start-up the app checks that its data folder is on a dm-crypt device and refuses to run if it isn't.

Logs

The web server's access log keeps only an anonymised address (last IPv4 octet / IPv6 host part removed), no query strings and no referrers, and URLs that carry secret tokens (invitations, password resets, calendar feeds) are not logged at all. Full IP addresses are only used in memory for rate limiting and briefly in the system journal for fail2ban.

Keys

Backups and recovery keys

Each backup (database dump + encrypted file store) is encrypted again as a whole with AES-256-GCM (chunked, authenticated) using the backup key - so a backup is encrypted data inside an encrypted archive. Backups can be copied to a NAS/USB folder or another server over SFTP, verified and downloaded by admins, and are useless without the keys.

The keys (master, backup, LUKS recovery) are kept in a recovery bundle encrypted with the administrator's passphrase (PBKDF2-SHA256, 600,000 iterations + AES-256-GCM). The installer creates it from keys held only in memory and copies it to the backup destinations. It can be opened offline in the browser at /decrypt-export.

Parents' own exports

Parents can download everything that's theirs (Account > Your data). With a passphrase, the download is encrypted the same way (PBKDF2 + AES-256-GCM, .ppexport), so the copy on their device is encrypted at rest too; it opens in the browser at /decrypt-export - nothing is uploaded.

2b. What administrators can and can't see

Server administrators run the server; they are not part of any family.

Admins can Admins can't
See counts, server health, analytics (anonymised) Open, list or decrypt any message, file, journal, event, expense, info-bank entry, check-in, call recording or report
See accounts as numbers with masked username/email, status and dates See people's names, usernames or emails in clear (only confirm an exact value someone gives them)
Disable/enable accounts (the person is notified, with the reason) Reset someone's 2FA, or receive a password-reset link, unless that person opted in to admin-assisted recovery
See that events happened in the audit trail (for integrity checks) See who in a family did what, to which record, from which IP, or why
Create, schedule, verify, copy and download encrypted backups Read anything inside a backup without the offline recovery bundle and its passphrase
Read problem reports See who sent a report, unless the sender chose to share their username

These rules are enforced in code and covered by automated tests that load every admin page and export and check that no family content, username or email appears.

3. Accounts and sign-in

4. Access control

5. Integrity: nothing disappears

6. Isolation ("jailing")

The web application:

Privileged tasks (metrics, fail2ban, backups) are done by a separate root helper that only accepts small, re-validated JSON requests from a one-way drop folder, never uses a shell, refuses to ban protected addresses and writes its outputs without following symlinks.

7. Web application hardening

8. Server hardening

9. AI Tone Check and privacy

10. Limits - what this can't protect against

Being honest about the threat model matters:

Reporting a vulnerability

Please report security issues privately to the server's administrator and to the project at darkciphersystems.com. Please don't publish details until a fix is available.