Privacy architecture

Collect almost nothing, but keep enough structure to operate safely.

The privacy model is not a slogan. It is a set of architectural constraints: no Nocturne ID document storage, no behavioral analytics by default, no persistent raw identifying logs by default, no NPM frontend dependency chain, and clear separation between payment identity, account identity, moderation evidence, and content access.

Invariant

The platform can enforce rules, revoke access, process payments, and respond to abuse without building identity dossiers on lawful users.

Technical stance

Preferred stack

LayerDirectionReason
BackendRust + AxumSmall, efficient, explicit, and a strong fit for server-side sessions and service boundaries.
RenderingServer-rendered templates, likely Askama or MaudPremium HTML/CSS experience without a large frontend framework dependency surface.
Frontend dependenciesNo NPM by defaultReduces supply-chain exposure and makes the browser payload easier to audit.
StylingHandwritten CSSSupports bespoke luxury UI without framework lock-in.
JavaScriptSmall audited vanilla JS modules onlyEnough for cinematic motion, scroll state, video affordances, and progressive enhancement.
DatabasePostgreSQLDurable source for accounts, entitlements, content metadata, payments, moderation, and audit events.
Ephemeral stateRedis later if neededRate limits, slow-mode counters, short-lived tokens, and temporary locks; the current foundation persists session and CSRF token digests in PostgreSQL.
DeploymentSingle-purpose VPS with Docker isolation behind HTTPS reverse proxyStandalone deployment boundary; explicit production-edge profile, secure-cookie/HSTS posture, backup posture, production preflight, VPS compose skeleton, Caddy/nginx edge templates, external edge smoke, and local-only diagnostics before scaling complexity.
Account model

Pseudonymous membership and self-custody recovery

Default account fields

  • member_id: pseudonymous public/member identifier.
  • invite_code_digest: SHA-256 digest of an owner-issued one-time invite; the raw invite code is shown once and never stored.
  • credential_hash: Argon2id hash of the recovery/login secret; the plaintext key is shown once only.
  • credential_version: supports rotation and revocation.
  • recovery_confirmed_at: timestamp only; set after the member proves possession of the recovery key.
  • membership_state: active, expired, locked, banned, pending payment, etc.
  • entitlement_state: current access rights without storing rich identity.

Recovery policy

  • Recovery key shown once during onboarding.
  • Member-only library and media routes remain locked until the member pastes the saved recovery key and acknowledges the no-default-email recovery tradeoff.
  • Nocturne verifies the submitted key against the Argon2id credential hash and stores only recovery_confirmed_at.
  • Recovery-key rotation requires an active confirmed session, CSRF proof, explicit acknowledgement, and the current recovery key.
  • Rotation revokes old active credential rows, inserts the next credential version, closes active sessions, deletes CSRF-token digests for those sessions, opens a fresh local session, and resets recovery_confirmed_at until the replacement key is confirmed.
  • No default email recovery.
  • If the member loses the recovery key, Nocturne cannot recover the account by default.
  • Optional email recovery can be considered later, but must be explicitly opt-in.
UX requirement: the recovery tradeoff must be impossible to miss. Privacy-first means more user responsibility.
Age assurance

Pluggable age-gate abstraction

StageMechanismStored by Nocturne
Private/internal testSelf-attestation: adult-only acknowledgement and terms acceptance.Acceptance timestamp, terms version, and account state.
Public launch candidateProvider- or wallet-based proof of adult status if legally or commercially required.Only pass/fail state, provider key, verification timestamp, expiry, and audit reference. No ID documents.
Future idealAnonymous over-18 attestation where identity stays with the user or verifier.Minimal verification claim and expiry only.
Invariant: Nocturne does not store ID documents. If a verifier is introduced, provider selection must prioritize deletion, minimization, auditability, and no marketing reuse.
Sessions

Server-side session model

Privacy ledger

Data classes and retention posture

Data classPurposeRetention stanceAccess boundary
Account credentialLogin, recovery, one-time custody confirmation, and self-custody rotation.While account exists; rotated/revoked when changed. Confirmation keeps only a timestamp and is reset after rotation until the new key is confirmed.System only; never plaintext after initial display, rotation display, or confirmation form handling.
Invite codeControlled private-test entry before public acquisition is widened.One-time owner-issued codes store only SHA-256 digests, optional non-identifying owner labels, timestamps, status, and pseudonymous redemption member IDs. Raw invite codes are printed once and never listed later.Application redemption path and local owner CLI only; no email or identity binding.
Membership entitlementGate catalog and downloads.While subscription/pass state is needed; historical events minimized. Current foundation supports preview plus local owner grant/revoke lifecycle events.Application service and local owner CLI.
Payment referenceSubscription/pass state, checkout state, refunds, disputes, chargebacks.As required by processor, tax, dispute, and accounting needs. Current foundation records only pseudonymous member ID, plan, provider label, provider checkout/order/event references, amount/currency, checkout/payment status, entitlement link, replay attempts/errors, timestamps, optional note, and optional payload digest. Browser checkout success/cancel returns are non-authoritative and never grant access by themselves; stale pending/success-returned checkout sessions can be expired, and expired sessions cannot be completed. Reversal commands and webhook replay record refund/chargeback/void events and revoke only the linked entitlement. No raw webhook payload, card data, email, IP address, user-agent, or processor customer identity is stored by default. The payment provider matrix requires written provider approval before any adapter expands this data shape.Limited local owner CLI and member checkout skeleton now; most identity remains with processor.
Age-gate proofAdult-only access evidence.Minimal proof state and expiry; no ID documents.System/admin audit only.
Operational logsSecurity, rate limits, availability, abuse response.No persistent raw identifying access logs by default; current media limits are in-memory, short-lived, and keyed by hashed session IDs. Public report abuse controls use form-body size limits, a hidden honeypot, a stateless boot-secret token, dwell/expiry checks, and an active-case queue cap instead of raw IP/user-agent tracking. Production-edge doctor/preflight checks, the current proxy templates, and external smoke preserve readiness checks without expanding app-side identity logging.Operator-only; no analytics profiling.
Backup artifactsOperational recovery and restore drills.Backups inherit production sensitivity even though the live schema avoids email, ID documents, raw session IDs, and raw IP/user-agent logs. Store PostgreSQL dumps and private media archives encrypted/offsite, keep local artifacts short-lived, and never commit backup output to git.Owner/operator only; restore into isolated targets before live use.
Moderation evidenceIncident review, bans, takedowns, required reporting.Case-based and restricted. Current foundation stores report metadata, optional contact references/digests, optional SHA-256 evidence digests, target quarantine timestamps, and owner notes without raw IP/user-agent/email/attachments. Public /report submissions become external cases with no member ID, no public-controlled quarantine, no file upload, and non-identifying intake guardrails: 8 KiB body cap, hidden honeypot, boot-secret token, three-second dwell, 30-minute expiry, and 200 active-case queue cap; the gated /owner dashboard can review these cases without adding identity fields.Restricted trust/safety path, public minimal intake, and gated owner dashboard.
Participation signalsOwner-granted voting/request allowance, polls, private requests, predefined catalog/media reactions, and aggregate unlock goals.Account-local or aggregate only; current foundation stores pseudonymous member IDs, Signal counts, owner poll options, private request text/review notes, local Hermes screening state/rule/reason/version, aggregate reaction counts, and owner-authored unlock-goal thresholds. No public sexual behavior metrics, raw IP addresses, user-agent strings, email addresses, attachments, public profiles, leaderboards, or activity feeds.Application, local owner CLI, and gated /owner dashboard; aggregate analytics only.
Participation privacy

Controlled agency without a social graph

Current foundation

  • Owners grant finite Signals to pseudonymous member_id values.
  • Owners create polls and fixed options; members cannot publish poll options or public posts.
  • The authenticated /participation page shows only owner-created open polls, aggregate option counts, active aggregate unlock goals, the member's own available Signals, and that member's recent private request statuses.
  • Votes require confirmed recovery custody, active entitlement, an open poll, an available poll_vote Signal, and a valid CSRF token.
  • Requests require confirmed recovery custody, active entitlement, an available content_request Signal, a valid CSRF token, and local Hermes screening before queueing.
  • Request text enters a private owner queue with moderation state/rule/reason/version metadata and can be reviewed from CLI or the gated /owner dashboard, then accepted, declined, or archived; it is never public by default.
  • Reactions are predefined CSRF-protected buttons against visible catalog items or media assets; member pages show local controls only, while owner surfaces show aggregate counts.
  • Unlock goals are owner-authored and compute progress from aggregate reaction counts or aggregate votes for a fixed poll option; active goals are member-visible as count/threshold progress, while owner surfaces show active/completed goal status without member IDs.

Deliberate exclusions

  • No native chat, direct messages, public comments, public profiles, or leaderboards in the MVP foundation.
  • No links or attachments in member request text.
  • No raw IP address, user-agent, email, or device ledger attached to participation records.
  • No behavior scoring that ranks members by sexual consumption or spend.
  • No public display of individual votes, requests, or reactions.
Media access

No DRM, honest access control

Controls to use

  • Private object storage or filesystem path outside public web root.
  • Authenticated media routes or short-lived signed URLs.
  • Current foundation route: invite-gated membership creation under /join, entitlement-gated catalog pages under /library and /media/{asset_id}, CSRF-protected member participation actions under /participation, protected inline file responses under /media/{asset_id}/file, per-asset attachment downloads under /media/{asset_id}/download only when download_policy allows them, provider-neutral checkout session creation under /checkout with success/cancel returns that never grant access plus stale checkout expiry cleanup, owner-only local invite create/list/revoke commands, owner-only local import/catalog commands that copy files into the private media root and control draft/member/archive visibility, owner-only entitlement grant/list/revoke commands, owner-only payment proof record/list/reversal commands plus provider-neutral payment-checkout and payment-webhook record/list/replay commands for payment-neutral access testing, and owner-only unlock-goal commands for aggregate reward progress.
  • No public bucket/object paths; storage keys are relative private paths and URL-like keys are rejected.
  • Image/video playback uses authenticated disk-backed byte-range responses for browser compatibility; download links are explicit and separate from inline viewing.
  • Current foundation uses ephemeral per-session media route limits keyed by SHA-256 session digests rather than IP addresses; production WAF/rate-limit tuning remains separate from the current proxy body-limit templates.
  • Production-edge configuration is explicit: NOCTURNE_DEPLOYMENT_PROFILE=production, NOCTURNE_PUBLIC_ORIGIN=https://..., secure cookies, owner gate, owner-session lifetime, and NOCTURNE_BACKUP_POSTURE are checked by doctor; HSTS is emitted only for an HTTPS public origin.
  • Default form posts are capped at 16 KiB and public report posts at 8 KiB before any storage path is reached; the Caddy/nginx templates mirror those limits at the public edge.
  • Detailed environment, production preflight, backup posture, checkout-lifecycle, database, filesystem, and local smoke/seed diagnostics run through local owner commands rather than public HTTP endpoints, so operational state is not disclosed to unauthenticated visitors. The browser owner dashboard is also disabled until an explicit owner key or owner-key SHA-256 digest is configured, and its cookie is a HMAC-signed timestamped opaque token with server-side expiry.
  • Optional light brand watermark/end-card, not per-user forensic watermarking.

Controls to avoid

  • No invasive DRM.
  • No privacy-hostile per-user hidden watermarks by default.
  • No promise that screenshots or screen recording can be stopped.
  • No public indexing of gated assets.
Browser security

Default hardening