Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sigyn Architecture

This document describes the high-level architecture of Sigyn, a serverless encrypted peer-to-peer secret manager implemented in Rust.

High-Level Architecture

graph TD
    subgraph CLI ["sigyn-cli (Binary)"]
        CLIMain["Main / Clap"]
        Commands["Commands (vault, secret, etc.)"]
        Inject["Process Injection"]
        TUI["Interactive TUI"]
        Output["Formatting (Table, JSON)"]
    end

    subgraph Engine ["sigyn-engine (I/O Layer)"]
        VaultIO["Vault I/O / Lock"]
        GitSync["Git Sync"]
        AuditIO["Audit Log Persistence"]
        IdentityIO["Identity Store"]
    end

    subgraph Core ["sigyn-core (Pure Library)"]
        Crypto["Crypto (X25519, ChaCha20)"]
        Policy["Policy Engine (RBAC)"]
        CRDT["CRDT / Vector Clocks"]
        Types["Types / Errors"]
    end

    subgraph Storage ["Filesystem (~/.sigyn)"]
        Config["config.toml"]
        Identities["Identities / Private Keys"]
        Vaults["Vaults (CBOR, JSONL)"]
    end

    CLI --> Engine
    Engine --> Core
    Engine --> Storage

Data Flow

A typical secret read follows this path:

sequenceDiagram
    participant User
    participant CLI as sigyn-cli
    participant Engine as sigyn-engine
    participant Core as sigyn-core
    participant Disk as Filesystem

    User->>CLI: sigyn secret get KEY --env dev
    CLI->>Engine: Load config, identity, vault files
    Engine->>Disk: Read Identity & Vault Files
    CLI->>User: Prompt for Passphrase
    CLI->>Core: PolicyEngine::evaluate()
    Core-->>CLI: Access Granted
    CLI->>Core: unseal_header(envelope)
    Core-->>CLI: Vault Key + Env Keys
    CLI->>Core: decrypt_env(vault_file, env_key)
    Core-->>CLI: Plaintext Secrets
    CLI->>Engine: Append Audit Entry
    Engine->>Disk: Write audit log
    CLI->>User: Display Secret Value

Cargo Workspace

Sigyn is organized as a Cargo workspace with four crates and an integration test suite:

sigyn/
  Cargo.toml              # Workspace root (resolver = "2")
  crates/
    sigyn-core/           # Pure library (publishable, zero I/O)
    sigyn-engine/         # I/O layer (filesystem, git, persistence)
    sigyn-cli/            # CLI binary (clap-based)
    sigyn-recovery/       # Standalone recovery binary
  tests/
    integration/          # Integration test crate
CrateRoleKey Trait
sigyn-corePure business logic: cryptography, policy evaluation, CRDT merge, types, and errors. Has zero I/O side effects – no git2, tempfile, fd-lock, or directories dependencies. Publishable to crates.io.thiserror for typed errors
sigyn-engineI/O layer: filesystem operations, git sync, audit log persistence, identity storage, vault locking. Depends on and re-exports sigyn-core.thiserror for typed errors
sigyn-cliThe sigyn binary. Parses arguments (clap 4), performs network I/O, renders output (tables, JSON, TUI).anyhow for ergonomic CLI errors
sigyn-recoveryA standalone binary for disaster recovery using Shamir secret sharing. Intentionally minimal so it can be distributed independently of the main CLI.thiserror in shared code

Dependency Boundary

sigyn-core  (pure, publishable)
     ↑
sigyn-engine  (I/O layer, re-exports sigyn-core)
     ↑
sigyn-cli / sigyn-recovery

sigyn-core has no I/O dependencies and can be tested in complete isolation. sigyn-engine re-exports everything from sigyn-core, so downstream crates only need to depend on sigyn-engine to access both pure logic and I/O operations.

On-Disk Layout

All Sigyn data lives under ~/.sigyn/ by default (determined via the directories crate):

~/.sigyn/
  config.toml                   # Global configuration (TOML)
  identities/
    <name>/
      identity.toml             # Identity metadata (name, fingerprint)
      secret_key.enc            # Argon2id-wrapped Ed25519 + X25519 keypair
  vaults/
    <name>/
      vault.toml                # Vault metadata (UUID, name, created_at)
      members.cbor              # Envelope header: vault key slots + per-env key slots
      policy.cbor               # RBAC policy, member list, constraints
      envs/
        dev.vault               # Encrypted environment file (CBOR, per-env key)
        staging.vault
        prod.vault
      audit.log.json            # Hash-chained audit trail (JSON Lines)
      forks.cbor                # Fork metadata and state
      deploy_key.sealed         # Sealed SSH deploy key for audit push (optional)

Data Formats

FormatUsed ForRationale
CBOR (via ciborium)Encrypted blobs (members.cbor, policy.cbor, *.vault, forks.cbor)Compact binary format, schema-flexible, well-suited for encrypted payloads
TOML (via toml)Human-readable configuration (config.toml, vault.toml, identity.toml)Easy to read and edit by hand, standard in Rust ecosystem
JSON Lines (via serde_json)Audit log (audit.log.json)Append-only, one entry per line, streamable, easy to pipe through jq

Key Design Decisions

Error Handling: thiserror in Core, anyhow in CLI

sigyn-core uses thiserror to define a comprehensive SigynError enum. This gives callers precise, matchable error variants (e.g., SigynError::NoMatchingSlot, SigynError::AuditChainBroken(u64)). The CLI layer uses anyhow to ergonomically wrap these errors with context for display to the user.

Sync Core, Async Only in CLI

sigyn-core is fully synchronous. It contains no async code, no Tokio dependency, and no network I/O. The sigyn-cli crate is the only place where tokio and reqwest appear. This keeps the core library testable with standard #[test] functions and avoids async coloring the entire codebase.

Sensitive Memory: secrecy::Secret + zeroize

All sensitive key material is wrapped in secrecy::Secret<T> and derives Zeroize. When a Secret is dropped, its memory is securely zeroed. This applies to:

  • Master keys
  • Private keys (X25519, Ed25519)
  • Passphrase-derived keys
  • Decrypted secret values in transit

Atomic File Writes via tempfile::persist()

All file writes in sigyn-engine go through a write-to-temp-then-persist pattern using tempfile::NamedTempFile and its persist() method. This prevents partial writes from corrupting vault data if the process is interrupted. The fd-lock crate provides advisory file locking for concurrent access safety.

Minimum Rust Version

The workspace requires Rust 1.75+ (rust-version = "1.75" in Cargo.toml).

Release Profile

Release builds use LTO, strip symbols, and single codegen unit for minimal binary size:

[profile.release]
lto = true
strip = true
codegen-units = 1

Module Overview

Sigyn is organized into logical modules split across the core library, engine, and CLI:

sigyn-core Modules (Pure, No I/O)

ModuleResponsibility
cryptoKey generation, X25519 Diffie-Hellman, envelope encryption/decryption (vault key + per-env key slots), ChaCha20-Poly1305 AEAD, Argon2id KDF, HKDF key derivation, nonce management
vaultVault manifest types, env file encryption/decryption (pure computation)
secretsSecret value types, validation rules, cross-references, random secret generation, per-key ACLs
identityIdentity/LoadedIdentity types, passphrase wrapping, Shamir secret sharing, MFA types and sessions
environmentEnvironment management, per-env policy, diffing, secret promotion
policy7-level RBAC, member policy, secret ACLs, PolicyEngine::evaluate(), constraint checking, policy serialization
delegationDelegation tree structure, Ed25519-signed invitations, cascade revocation
forksFork types, approval workflows, fork expiry
auditAudit entry and witness types
syncVector clocks, LWW-Map CRDT, conflict detection and resolution
rotationKey rotation, cron-based scheduling, rotation history, hooks, breach mode, dead-check
hierarchyHierarchical policy engine, node manifests, OrgPath type

sigyn-engine Modules (I/O Layer)

ModuleResponsibility
vaultEnv file read/write, vault locking (fd-lock), vault path resolution and directory scanning
identityIdentity store (filesystem), MFA store, MFA session store
auditHash-chained audit log (file-based), git anchoring, witness log persistence
syncGit-based sync engine (git2)
hierarchyHierarchy paths (filesystem), slot management (CBOR read/write), git remote resolution
forksFork creation with filesystem operations (leashed/unleashed)
policyVaultPolicyExt trait for filesystem-based policy save/load

sigyn-cli Modules

ModuleFilesResponsibility
commandsidentity.rs, vault.rs, secret.rs, env.rs, policy.rs, sync.rs, audit.rs, fork.rs, run.rs, rotate.rs, delegation.rs, import.rs, status.rsOne file per CLI subcommand group
injectmod.rs, dotenv.rs, export.rs, process.rs, socket.rsSecret injection into child processes, dotenv export, Unix socket serving
importexportmod.rs, cloud.rsImport from dotenv/JSON/cloud providers (Doppler, AWS, GCP, 1Password)
tuimod.rsInteractive TUI dashboard (ratatui + crossterm)
notificationsmod.rsExpiry warnings, rotation reminders
outputmod.rsFormatting helpers (tables via tabled, JSON output, success/error messages)

Data Flow

A typical secret read follows this path:

User runs: sigyn secret get DATABASE_URL --env dev

  sigyn-cli
    1. Parse CLI args (clap)

  sigyn-engine (I/O)
    2. Load config.toml, resolve vault + identity
    3. Read identity secret key, read vault files (members.cbor, policy.cbor, envs/dev.vault)

  sigyn-cli
    4. Prompt for passphrase, unwrap via Argon2id

  sigyn-core (pure)
    5. PolicyEngine::evaluate() -- check RBAC, constraints, ACLs
    6. unseal_header() -- X25519 DH to recover vault key + per-env keys from envelope
    7. VaultCipher::decrypt() -- ChaCha20-Poly1305 decrypt the env file with that env's key
    8. Extract the requested key from the decrypted map

  sigyn-engine (I/O)
    9. Append audit entry (hash-chained, Ed25519 signed)

  sigyn-cli
   10. Output the secret value (plaintext to stdout, or JSON if --json)

Every write operation follows the same policy evaluation path. There is no bypass. See Security Model for details on the cryptographic primitives and policy engine.