Files
relicario/docs/SECURITY.md
adlee-was-taken ed50735e91 docs(org): pre-stage A5 living-docs for merged core+server+CLI-admin (item-CRUD/extension TODO)
Pre-stages the A5 living-docs sweep for the already-merged A (relicario-core org
module) + C (relicario-server pre-receive hook) + CLI admin/rotate/status-audit
work, so the final A5 sweep (after Dev-B B9-B14 merges) is fast.

Adds org sections to docs/FORMATS.md (org repo wire formats + wrapped-key blob
layout), docs/CRYPTO.md (ECIES X25519 wrap/unwrap, no-Argon2id contrast, rotate
re-encryption), docs/SECURITY.md (signature-verifying hook, owner-only elevation,
audit vocabulary, honest limitations), DESIGN.md (org-master-key secrets row +
server org mode + deps), core/cli ARCHITECTURE.md (org module + org_session), and
an Unreleased CHANGELOG entry.

B item-CRUD (org add/get/list/edit/rm/restore/purge + main.rs wiring) and extension
parity are left as explicit TODO. STATUS/ROADMAP mark-shipped and
extension/ARCHITECTURE are deferred to the full A5 (track not yet landed; Dev-D
deferred). All cited code constants pinned with file:line per living-docs discipline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-20 14:39:08 -04:00

10 KiB
Raw Blame History

Relicario Security Model

Audience: auditors and curious users. This doc owns the threat model, attacker-scenarios table, device-authentication model, env-var trust surface, and known limitations. Does NOT own: crypto primitive details (see CRYPTO.md), wire formats (see FORMATS.md), or implementation (see ../crates/relicario-core/ARCHITECTURE.md and ../crates/relicario-cli/ARCHITECTURE.md).

Cryptographic Protection

Relicario uses two-factor vault decryption:

  1. Passphrase — user-memorized, zxcvbn score ≥3 required
  2. Reference image — JPEG carrying 256-bit secret via DCT steganography

Key derivation: Argon2id (64 MiB memory, 3 iterations, 4 parallelism) Encryption: XChaCha20-Poly1305 (192-bit nonce, 256-bit key)

Manifest Integrity

The manifest (manifest.enc) is encrypted with AEAD, which provides:

  • Confidentiality: Contents unreadable without master key
  • Integrity: Any modification detected and rejected on decrypt
  • Authenticity: Only master key holders can create valid ciphertexts

What AEAD Does NOT Protect

  • Item deletion: An attacker with write access can delete .enc files or git-revert commits. The manifest decrypts successfully but won't contain the deleted items.

  • Rollback attacks: An attacker can replace manifest.enc with an older valid version. AEAD accepts any ciphertext created with the key.

Mitigation

Item deletion and rollback are detectable via git history:

git log --oneline items/

For environments where git history could be rewritten (force-push):

  1. Enable device authentication (commit signing + pre-receive hook)
  2. Use a git server that rejects non-fast-forward pushes
  3. Regular backups with relicario backup export

Device Authentication

When enabled, device authentication provides:

  • Commit authorship: All commits signed by registered device keys
  • Push access control: Deploy keys managed via Gitea API
  • Instant revocation: One command cuts off both signing and push

Enforcement requires deploying the relicario-server pre-receive hook on the vault remote. The crate provides two subcommands:

  • relicario-server generate-hook — emits the hook script to install at <repo>/hooks/pre-receive
  • relicario-server verify-commit <sha> — checks one commit's signature against .relicario/devices.json and .relicario/revoked.json as of that commit; the hook calls this for every pushed ref

Without the server hook, signed commits provide authorship metadata only — any process with push access can land an unsigned commit, since verification is otherwise advisory.

See docs/superpowers/specs/2026-05-02-device-authentication-design.md.

Access Control

Without device authentication, access control is transport-layer only:

  • CLI: SSH key authentication to git remote
  • Extension: Git credentials in browser storage

Device registration is optional but recommended for shared vaults.

Org vault security

An org vault is a separate git repository alongside the personal vault. It uses ed25519 commit-signing and a server-side pre-receive hook to make least-privilege access control server-enforced, not advisory.

Org device-key authentication

Every org member registers an ed25519 device key. The key appears in members.json as an OpenSSH public-key string alongside the member's role and collection grants. Fingerprint matching is done via relicario_core::fingerprint, which normalises the OpenSSH format so that whitespace and comment differences do not create phantom mismatches.

Org access requires two things at once: a wrapped key blob (keys/<member-id>.enc) and the device private key that can unwrap it. There is no org passphrase — removing a member's blob and rotating the org master key is sufficient to revoke access (see Key rotation below). Device keys are completely separate from the personal vault's KDF inputs; revoking org access does not affect the member's personal vault.

Pre-receive hook enforcement

relicario-server generate-org-hook (crates/relicario-server/src/main.rs:511) emits a hook script that calls relicario-server verify-org-commit for every pushed commit. Unsigned or structurally invalid commits are rejected before they land.

verify_org_commit (main.rs:286) performs four checks in order:

  1. Signature verification — a temporary allowed_signers file is constructed from the current members.json; git verify-commit --raw is run and the resulting SHA-256 fingerprint is matched back to a members.json entry. A commit not signed by a current member is rejected outright.

  2. Path-level write authorisation — each modified path is classified by classify_path (crates/relicario-server/src/lib.rs:19) into ProtectedJson (owner/admin write only), CollectionItem (the items/<slug>/… prefix; write allowed only if the slug appears in the signer's collections grant array), or Unrestricted. The write is authorised if and only if the signer's role and grants satisfy the classification. Item blobs are authorised by the leading path segment alone — the ciphertext is never decrypted by the hook.

  3. Owner-only elevation guard (enforce_owner_only_elevation, main.rs:438) — only a member whose pre-commit (parent) role is Owner may introduce a new member at Owner or Admin level, or promote an existing member to either. Checking the pre-commit role means an Admin cannot self-promote in the same commit that writes the escalated members.json; there is no epoch in which the transition is self-authorised.

  4. Schema monotonicity (enforce_schema_monotonicity, main.rs:521) — schema_version values in org JSON containers may not decrease. Merge commits are rejected. A genesis commit (no parents) is allowed only when it is signed by the sole Owner it introduces.

Key rotation

relicario org rotate-key generates a fresh 256-bit org master key, re-wraps it for every current member, and re-encrypts every items/<slug>/<id>.enc blob and the manifest under the new key in a single signed commit tagged Relicario-Action: key-rotate. A revoked member's wrapped blob is simply not written during rotation, so they hold a blob that decrypts to a stale key — they cannot read items encrypted under the new key.

Audit action vocabulary

The relicario org audit command attributes actions to their verified signer (not to the commit author or trailer value). Each event records two actors: the verified actor resolved from the signing key (authoritative) and the actor claimed by the Relicario-Actor trailer (advisory). When the claimed actor disagrees with the verified signer, the event is flagged TAMPERED. Trailers are advisory metadata; the trustworthy actor is always the cryptographically verified signer.

Actions live in two groups:

  • Live (merged A + C streams): member-add, member-remove, member-role-change, collection-create, collection-grant, collection-revoke, key-rotate, org-init, ownership-transfer, org-delete.
  • TODO (pending Dev-B B9B13): item-create, item-update, item-delete, item-restore, item-purge — the emitter code lands with the item-CRUD command stream.

Honest limitations

The following are deliberate design boundaries, not oversights:

  • Shared org master key — reads are not cryptographically scoped per collection. The pre-receive hook scopes writes by collection path and the CLI filters the manifest to each member's grants, but a single org key opens all collection blobs. A member with any grant can, outside the CLI, decrypt items from collections they are not granted. For true cryptographic separation, use a separate org vault per access boundary. Per-collection subkeys are a phase-2 non-goal.

  • No read audit. Git records writes only. A member who reads blobs directly leaves no server-visible trace.

  • No "hide value." There is no mechanism to show a member that an item exists without revealing its field values on decrypt.

  • delete-org is a local tombstone in phase 1. The schema-monotonicity check causes the hook to reject protected-file deletion, so an org-delete action cannot be pushed to a hook-protected remote. The deletion is recorded locally only until a future phase addresses it.

Configuration env vars

Relicario reads the following environment variables. Each is a trust boundary: an attacker who can set them in the user's environment can influence Relicario's behavior. They are listed here for security reviewers to audit the surface in one place.

User-facing (active in all builds)

Variable Purpose Trust
RELICARIO_IMAGE Override the reference-image JPEG path used during vault unlock. Trusted: filesystem path under the user's control. Read-only; its bytes feed imgsecret::extract_secret.
RELICARIO_GITEA_URL Gitea API base URL for relicario device add. Equivalent to --gitea-url. Trusted: HTTPS URL. Used only in the device-add code path.
RELICARIO_GITEA_TOKEN Gitea personal-access token. Equivalent to --gitea-token. Secret: anyone who can read this env var can manage the user's deploy keys via the Gitea API. The CLI never logs it.
RELICARIO_GITEA_OWNER Gitea repository owner (e.g. alee). Equivalent to --owner. Trusted: opaque string.
RELICARIO_GITEA_REPO Gitea repository name (e.g. vault). Equivalent to --repo. Trusted: opaque string.

Debug-only (compiled out of cargo build --release)

The following variables are gated behind cfg(debug_assertions) and are no-ops in release builds. The env-var lookup is removed by the optimiser from any binary built without debug assertions (i.e. the standard --release profile).

Variable Purpose
RELICARIO_NO_GROUPS_CACHE Suppress the plaintext groups.cache write. Developer debugging tool for the cache logic.
RELICARIO_TEST_PASSPHRASE Bypass the rpassword prompt during integration tests.
RELICARIO_TEST_ITEM_SECRET Bypass the rpassword prompt for item-secret fields during integration tests.
RELICARIO_TEST_BACKUP_PASSPHRASE Bypass the rpassword prompt for backup export/restore passphrases during integration tests.

Next: ../crates/relicario-core/ARCHITECTURE.md — implementation, starting with the platform-agnostic core.