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 - Open Source Secret Manager

Sigyn: Serverless Encrypted Secret Manager

Sigyn is an open-source, serverless secret manager that keeps your API keys, database credentials, and application secrets encrypted at rest and synced via git – with no server required.

Think of it as a self-hosted alternative to Doppler, AWS Secrets Manager, or HashiCorp Vault – but fully peer-to-peer, git-native, and zero-infrastructure.

Key Features

  • End-to-end encryption. Secrets are sealed with X25519 envelope encryption and encrypted with ChaCha20-Poly1305. Only authorized team members can decrypt.
  • No server, no SaaS. Secrets are encrypted files in a git repository. No hosted infrastructure to manage, no vendor lock-in, no monthly bills.
  • Role-based access control. Seven-level role hierarchy (ReadOnly through Owner), per-key ACLs, time-window constraints, and automatic expiry.
  • Git-native sync. Push and pull secrets just like code. CRDT-based conflict resolution handles concurrent edits.
  • Per-environment secrets. First-class support for dev, staging, production, and custom environments.
  • Team collaboration. Invite members, delegate access with cascade revocation, and track every operation in a signed audit trail.
  • Process injection. sigyn run -- ./app injects secrets as environment variables without writing them to disk.
  • AI-agent safe. Secrets stay out of your shell environment where coding agents run. Use sigyn run serve for zero-exposure secret access.
  • Web GUI. Browser-based dashboard for visual secret management (sigyn web).
  • Project config. Drop a .sigyn.toml in your project root for zero-flag workflows and named run commands.
  • Cross-platform. One-liner install for macOS, Linux, and Windows.

Quick Start

# Install
curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.sh | sh

# Create an identity and vault
sigyn identity create -n alice
sigyn vault create myapp

# Store and retrieve secrets
sigyn secret set DATABASE_URL 'postgres://localhost/myapp' -v myapp -e dev
sigyn secret get DATABASE_URL -e dev

# Set up project config
sigyn project init

# Run your app with secrets injected
sigyn run -- ./myapp

Who Is Sigyn For?

  • Solo developers who want encrypted secret management without SaaS dependencies
  • Small teams that need to share secrets securely via git without setting up Vault or paying for Doppler
  • Security-conscious organizations that require audit trails, RBAC, and self-hosted secret storage
  • CI/CD pipelines that need secrets injected at runtime without writing .env files to disk
  • AI-assisted development where coding agents (Claude Code, Cursor, Copilot) run in your terminal and you need to keep credentials out of their reach

The Name

In Norse mythology, Sigyn is the goddess of fidelity and devotion – she faithfully holds a bowl over her bound husband Loki to shield him from venom. Sigyn the tool does the same for your secrets: it stands guard over your sensitive data with steadfast encryption and access control.

Next Steps

Getting Started with Sigyn

A step-by-step guide to installing Sigyn, creating your first vault, managing secrets, collaborating with team members, and syncing via git.

Install

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.ps1 | iex

This downloads the latest pre-built binary for your platform and adds it to your PATH. Falls back to building from source if no pre-built binary is available.

From source

Requires Rust 1.75+ and git:

cargo install --git https://github.com/tonybenoy/sigyn.git --bin sigyn sigyn-cli
cargo install --git https://github.com/tonybenoy/sigyn.git --bin sigyn-recovery sigyn-recovery

Or clone and build locally:

git clone https://github.com/tonybenoy/sigyn.git
cd sigyn
cargo build --release
cp target/release/sigyn target/release/sigyn-recovery ~/.local/bin/

Verify

sigyn --version

Uninstall

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/uninstall.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/tonybenoy/sigyn/main/uninstall.ps1 | iex

Quick Setup (Guided Wizard)

The fastest way to get started is the onboard wizard, which walks you through everything below in one interactive flow:

sigyn onboard

Or use sigyn init for a lighter-weight setup that offers to create an identity and vault if none exist.

If you prefer to do each step manually, read on.

Create an Identity

An identity is your cryptographic keypair (X25519 for encryption, Ed25519 for signing). All vault operations require one.

sigyn identity create --name alice

You will be prompted for a passphrase (minimum 8 characters). This passphrase protects your private keys at rest via Argon2id. Remember it – there is no recovery without it (or Shamir shards).

Output:

Identity 'alice' created
  Fingerprint: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
  Store this fingerprint -- others will use it to share secrets with you.

Verify it was created:

sigyn identity list

Show full details:

sigyn identity show alice

Enable MFA (Optional)

Add TOTP-based multi-factor authentication to your identity for an extra layer of security. You’ll need an authenticator app (Google Authenticator, Authy, 1Password, etc.).

sigyn mfa setup -i alice

This will:

  1. Generate a TOTP secret and display a QR code in the terminal (plus the base32 secret for manual entry).
  2. Ask you to enter a code from your authenticator app to verify.
  3. Print 8 single-use backup codes — save these somewhere safe.

Once enrolled, any vault policy that sets require_mfa: true will prompt for a TOTP code before granting access. A session-based grace period (default: 1 hour) avoids re-prompting on every operation.

Check your enrollment status at any time:

sigyn mfa status -i alice

Set Defaults

Configure your default identity and vault to avoid typing --identity and --vault on every command:

sigyn init --identity alice

This creates ~/.sigyn/config.toml with your defaults.

For per-project defaults, run project init in your project root:

sigyn project init

This interactively creates a .sigyn.toml with your vault and identity:

[project]
vault = "myapp"
env = "dev"
identity = "alice"

[commands]
# dev = "npm run dev"
# app = "./start-server"

Uncomment and edit the [commands] section to add named run commands. With this file, most commands need no flags at all. Use --global to write to ~/.sigyn/project.toml instead (for defaults you don’t want in a repo).

Create a Vault

A vault is an encrypted container for secrets, organized by environment:

sigyn vault create myapp

This generates a random 256-bit master key, seals it to your identity via X25519 envelope encryption, and creates three default environments: dev, staging, prod.

sigyn vault info myapp

Output:

Vault Info
  Name:         myapp
  ID:           6ba7b810-9dad-11d1-80b4-00c04fd430c8
  Owner:        a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
  Environments: dev, staging, prod
  Created:      2026-02-23 14:30:00 UTC

Set it as your default vault:

sigyn init --identity alice --vault myapp

Add Secrets

Store a secret in the dev environment:

sigyn secret set DATABASE_URL 'postgres://user:pass@localhost:5432/mydb' --env dev

Add more secrets:

sigyn secret set API_KEY 'sk-dev-abc123' --env dev
sigyn secret set REDIS_URL 'redis://localhost:6379' --env dev

Generate a random secret:

sigyn secret generate JWT_SECRET --env dev --length 64

Read from stdin (useful for multiline values or piping):

cat certificate.pem | sigyn secret set TLS_CERT --env dev

Read Secrets

Retrieve a secret value:

sigyn secret get DATABASE_URL --env dev

Output:

postgres://user:pass@localhost:5432/mydb

Get structured output with metadata:

sigyn secret get DATABASE_URL --env dev --json

List Secrets

List all keys in an environment (values hidden by default):

sigyn secret list --env dev

Reveal values:

sigyn secret list --env dev --reveal

Run with Injected Secrets

Launch a process with all secrets from an environment injected as environment variables. The exec subcommand is the default, so you can omit it:

sigyn run -e dev -- ./my-app

The secrets are passed directly to the child process environment. They are never written to disk in plaintext.

Use -c / --clean to avoid inheriting the parent shell’s environment:

sigyn run -e dev -c -- node server.js

Use --prod or --staging as environment shortcuts:

sigyn run --prod -- docker compose up

If you have a .sigyn.toml with named commands, run them directly:

sigyn run dev       # runs the 'dev' command from [commands] table

Export to Dotenv

Export secrets in .env format:

sigyn run export --env dev --format dotenv > .env

Other supported formats: json, shell, docker, k8s.

# Kubernetes secret manifest
sigyn run export --env prod --format k8s --name my-app-secrets > secret.yaml

Import from .env

Import an existing .env file into a vault:

sigyn import dotenv .env --env dev

Import from a JSON file (expects a flat key-value object):

sigyn import json secrets.json --env dev

Import from cloud providers:

sigyn import aws --secret-id myapp/dev --env dev
sigyn import doppler --project myapp --config dev --env dev
sigyn import gcp --project my-gcp-project --secret db-password --env prod
sigyn import 1password --vault "Dev Secrets" --item "API Keys" --env dev

Invite a Team Member

First, your teammate creates their own identity on their machine:

# On Bob's machine:
sigyn identity create --name bob
sigyn identity list    # note the fingerprint

Bob shares their fingerprint with you out-of-band (chat, email, etc.).

Add Bob to the vault policy with a role and environment restrictions:

sigyn policy member-add <bob-fingerprint> --role contributor --envs dev,staging

Or create a signed invitation file for a more formal flow:

sigyn delegation invite create --role contributor --envs dev,staging

Share the resulting JSON file with Bob.

Accept an Invitation

Bob clones the vault and accepts the invitation in one step:

# On Bob's machine (single command):
sigyn vault clone [email protected]:team/secrets.git --invitation ./invitation-abc123.json

Or as separate steps:

sigyn vault clone [email protected]:team/secrets.git
sigyn delegation invite accept ./invitation-abc123.json

The invitation’s Ed25519 signature is verified before acceptance. Once accepted, Bob’s X25519 public key is added to the vault envelope, allowing him to decrypt secrets in the allowed environments.

View the Delegation Tree

See who has access and who invited whom:

sigyn delegation tree

Output:

alice (a1b2c3d4...) [owner]
  bob (e5f6a7b8...) [contributor]

Create Environments

Vaults start with dev, staging, and prod. Add more as needed:

sigyn env create qa
sigyn env create canary

List environments:

sigyn env list

Promote Secrets

Copy secrets from one environment to another:

sigyn env promote --from dev --to staging
sigyn env promote --from dev --to staging,prod     # chained: dev → staging → prod

Promote specific keys only:

sigyn env promote --from staging --to prod --keys DATABASE_URL,API_KEY

Then update production values to their real credentials:

sigyn secret set DATABASE_URL 'postgres://user:pass@prod-host/myapp' --env prod

Sync via Git

Sigyn vaults are directories of encrypted files that can be synced through git. All data is encrypted at rest, so syncing never exposes secrets.

Configure sync during vault creation (recommended):

sigyn vault create myapp --remote-url [email protected]:team/secrets.git

Or configure sync for an existing vault:

sigyn sync configure --remote-url [email protected]:team/secrets.git

Push local changes:

sigyn sync push

Pull remote changes:

sigyn sync pull

Check sync status:

sigyn sync status

On another machine (or as another team member), clone the vault first:

sigyn vault clone [email protected]:team/secrets.git
sigyn sync pull --vault myapp

Audit Trail

Every operation is recorded in a hash-chained, Ed25519-signed audit log.

View recent entries:

sigyn audit tail
sigyn audit tail -n 50

Verify the chain has not been tampered with:

sigyn audit verify

Query by actor or environment:

sigyn audit query --actor a1b2c3d4... --env prod

Export the full log:

sigyn audit export --output audit.json --format json

Add a witness countersignature:

sigyn audit witness

Shell Completions

Generate completions for your shell:

# Bash
sigyn completions bash > ~/.local/share/bash-completion/completions/sigyn

# Zsh
sigyn completions zsh > ~/.zfunc/_sigyn

# Fish
sigyn completions fish > ~/.config/fish/completions/sigyn.fish

Restart your shell or source the file to activate completions.

Next Steps

sigyn-recovery split --identity alice --threshold 3 --total 5

Sigyn CLI Reference

Complete command reference for the sigyn binary.

sigyn <COMMAND> [OPTIONS]

Global Flags

These flags can be used with any subcommand:

FlagShortDescription
--vault <NAME>-vVault name (overrides default from config)
--env <NAME>-eEnvironment name (overrides default from config)
--identity <NAME>-iIdentity name or fingerprint (overrides default)
--jsonOutput as JSON
--quietSuppress non-essential output
--dry-runPreview changes without applying
--verboseShow detailed config resolution and debug output
--no-project-configSkip loading .sigyn.toml project config

Resolution priority: CLI flags > ~/.sigyn/context.toml > .sigyn.toml (project dir) > ~/.sigyn/project.toml > ~/.sigyn/config.toml > defaults.

Command Aliases

Most commands have short aliases for quick typing:

CommandAlias
identityid
vaultv
secrets
enve
policyp
syncsy
audita
forkf
runr
rotaterot
importimp
delegationmember
notificationnotif
contextctx

Environment Prefix Matching

Environment names support unambiguous prefix matching. If your vault has dev, staging, and prod environments:

sigyn secret get API_KEY -e d    # resolves to 'dev'
sigyn secret get API_KEY -e p    # resolves to 'prod'
sigyn secret get API_KEY -e s    # resolves to 'staging'
sigyn secret get API_KEY -e st   # also resolves to 'staging'

Ambiguous prefixes produce an error listing the matches.

Environment Variables

VariableDescription
SIGYN_HOMEOverride the Sigyn home directory (default: ~/.sigyn)
SIGYN_NON_INTERACTIVEDisable all interactive prompts (equivalent to piping to a non-TTY)
CIWhen set, disables interactive prompts (auto-detected in most CI systems)

get (shortcut)

Get a secret value. This is a shortcut for sigyn secret get.

sigyn get DATABASE_URL -e dev
sigyn get API_KEY --copy           # copy to clipboard
sigyn get API_KEY -e prod --json
FlagShortDescription
--copy-cCopy value to clipboard instead of printing (auto-clears after 30s)

Uses the global --env / -e flag for environment selection.

set (shortcut)

Set a secret value. This is a shortcut for sigyn secret set.

sigyn set DATABASE_URL 'postgres://localhost/mydb' -e dev
echo 'supersecret' | sigyn set API_KEY -e prod

Uses the global --env / -e flag for environment selection.

ls (quick list)

Quick listing of secrets, vaults, or environments.

sigyn ls                    # list secrets in default env
sigyn ls prod               # list secrets in 'prod' env (prefix matching)
sigyn ls --vaults           # list all vaults
sigyn ls --envs             # list all environments
sigyn ls dev --reveal       # list secrets with values shown
FlagShortDescription
--vaultsList vaults instead of secrets
--envsList environments instead of secrets
--reveal-rShow decrypted values

identity (alias: id)

Manage cryptographic identities (X25519 + Ed25519 keypairs).

identity create

Create a new identity with a passphrase-protected keypair.

sigyn identity create --name alice
sigyn identity create --name alice --email [email protected]
FlagShortDescription
--name <NAME>-nRequired. Name for this identity
--email <EMAIL>-EOptional email address

Prompts for a passphrase (minimum 8 characters) with confirmation.

identity list

List all identities on this machine.

sigyn identity list
sigyn id list --json

identity show

Show details of a specific identity (name, email, fingerprint, creation date).

sigyn identity show alice
sigyn identity show                  # uses default identity
sigyn identity show a1b2c3d4...     # by fingerprint

identity change-passphrase

Change the passphrase for an existing identity. Verifies the old passphrase, then re-wraps the keys with a fresh salt. Clears the agent cache after success.

sigyn identity change-passphrase alice
sigyn identity change-passphrase          # uses default identity

Prompts for current passphrase, then new passphrase with confirmation (minimum 8 characters).

identity delete

Delete an identity from this machine. Requires passphrase to prove ownership. Before deleting, checks whether the identity is a member of any local vaults.

sigyn identity delete alice
sigyn identity delete a1b2c3d4... --force
FlagDescription
--forceSkip vault membership check and delete anyway

If the identity is a member of vaults, the command errors with the vault list unless --force is used. Interactive mode shows a confirmation prompt.

identity rotate-keys

Create a new keypair with a new fingerprint, replacing the old identity. The old identity is deleted only after the new one is successfully created.

sigyn identity rotate-keys
sigyn identity rotate-keys alice
sigyn identity rotate-keys alice --force   # required non-interactively

Warning: This creates a new fingerprint. You must be re-invited to all vaults after rotation. There is no automatic migration — vault headers are not re-encrypted with the new key.

To prevent silently locking yourself out, rotation now refuses if the identity is a member or owner of any local vault (for a vault you own, no one else can re-invite you). Move or drop those vault memberships first, or pass --force to override. --force is also required to rotate non-interactively (e.g. with SIGYN_PASSPHRASE set).

Sharing identities. There is no identity export / identity import command. Public keys are exchanged through delegation: the owner runs sigyn delegation invite --pubkey <fingerprint> … and the invitee accepts with sigyn delegation accept <invitation>. See Team Collaboration.

vault (alias: v)

Manage encrypted vaults.

vault create

Create one or more vaults. The caller’s identity becomes the Owner. Generates a random 256-bit master key and seals it to the creator’s X25519 public key.

sigyn vault create myapp
sigyn vault create myapp --remote-url [email protected]:team/secrets.git
sigyn vault create frontend backend worker --org acme -i alice
FlagDescription
--org <PATH>Link vault to an org hierarchy path
--split-auditCreate separate git repo for audit data
--remote-url <URL>Configure git sync remote URL (skips separate sync configure step)

Accepts multiple names for batch creation. All vaults share the same --org and flags. Creates the vault directory with vault.toml, members.cbor, policy.cbor, default environments (dev, staging, prod), and an audit log. After creation, Sigyn prints suggested next steps and offers to create a .sigyn.toml project config in the current directory (interactive terminals only, single vault only).

Naming rules: Vault and environment names must be 1-64 characters, contain only [a-zA-Z0-9-_], and cannot start with . or contain ...

vault clone

Clone a shared vault from a remote git repository. Derives the local vault name from the URL (strips trailing .git), or use --name to override. Optionally accepts a delegation invitation in the same command for single-step onboarding.

sigyn vault clone [email protected]:team/myapp-vault.git
sigyn vault clone https://github.com/team/vault.git --name myapp
sigyn vault clone [email protected]:team/vault.git --invitation ./invitation-abc123.json
sigyn vault clone [email protected]:team/vault.git --branch develop
FlagDescription
--name <NAME>Override the vault name (default: derived from URL)
--invitation <PATH>Accept a delegation invitation (file path, UUID, or UUID prefix)
--branch <BRANCH>Git branch to clone (default: main)

After cloning, the vault’s rollback-protection checkpoint is recorded automatically.

vault list

List all vaults on this machine with their environments.

sigyn vault list

vault open

Open (unlock) a vault for the current session.

sigyn vault open myapp

vault delete

Delete a vault and all its data. Owner-only. Writes an audit entry and attempts a sync push before destroying the directory. Removes the vault from the pinned vaults store.

sigyn vault delete myapp
sigyn vault delete myapp --force
FlagDescription
--forceSkip member check and confirmation in non-interactive mode

In interactive mode, you must type the vault name to confirm (not just yes/no). If other members exist, the command errors unless --force is used.

vault info

Show vault metadata (UUID, owner fingerprint, environments, creation date).

sigyn vault info myapp
sigyn vault info                     # uses default vault
sigyn vault info myapp --json

vault transfer

Transfer ownership of a vault to another member. This is phase 1 of a two-phase transfer. The new owner must already be a member of the vault.

sigyn vault transfer myapp --to a1b2c3d4e5f6...
sigyn vault transfer myapp --to a1b2c3d4e5f6... --downgrade-to admin
sigyn vault transfer myapp --to a1b2c3d4e5f6... --downgrade-to remove
FlagDescription
--to <FP>Fingerprint of the new owner (must be an existing member)
--downgrade-to <ROLE>Role for old owner after transfer (default: admin). Use remove to leave the vault.

Creates a signed pending_transfer.cbor recording the intended transfer. The vault’s manifest and policy are not changed until the new owner accepts — the old owner retains full control in the interim. Transfers expire after 7 days.

vault accept-transfer

Accept a pending ownership transfer (phase 2). Verifies the old owner’s signature, checks the transfer has not expired, then atomically updates the manifest owner, adjusts the policy (downgrading/removing the old owner), re-signs the header and policy with the new owner’s signing key, and updates the TOFU pin.

sigyn vault accept-transfer myapp

If the transfer has expired, the pending file is removed and the old owner must initiate a new transfer.

vault export

Export a vault as an encrypted tar.gz archive. All data on disk is already encrypted, so the archive contains no plaintext secrets. Requires Admin+ access.

sigyn vault export myapp -o backup.tar.gz
sigyn vault export myapp --output /backups/myapp-2025.tar.gz
FlagShortDescription
--output-oOutput file path for the tar.gz archive
--forceOverwrite the output file if it already exists

secret (alias: s)

Manage secrets within a vault environment.

secret set

Set one or more secrets. Supports KEY VALUE, KEY=VALUE, or multiple KEY=VALUE pairs. Reads from stdin if a single key is given with no value.

sigyn secret set DATABASE_URL 'postgres://localhost/mydb' --env dev
sigyn secret set DATABASE_URL='postgres://localhost/mydb' --env dev
sigyn secret set DB_URL=postgres://localhost API_KEY=sk-123 REDIS=redis://localhost -e dev
echo 'supersecret' | sigyn secret set API_KEY --env prod
FlagDescription
--env, -e <ENV>Target environment (default: dev)

secret import (alias: imp)

Import secrets from a .env file.

sigyn secret import .env --env dev
sigyn secret import .env.production --env prod --force
cat secrets.env | sigyn secret import - --env dev
FlagDescription
--env, -e <ENV>Target environment (default: dev)
--forceOverwrite existing secrets without prompting

secret get

Retrieve and decrypt a secret value.

sigyn secret get DATABASE_URL --env dev
sigyn secret get API_KEY --env prod --json
sigyn secret get API_KEY --env dev --copy    # copy to clipboard
FlagShortDescription
--env-eTarget environment (default: dev)
--copy-cCopy value to clipboard instead of printing

JSON output includes key, value, type, environment, and version. With --copy, JSON output includes "copied": true instead of the value.

secret list

List all secrets in an environment. Values are hidden by default.

sigyn secret list --env dev
sigyn secret list --env dev --reveal
FlagShortDescription
--env-eTarget environment (default: dev)
--reveal-rShow decrypted values instead of masked output

secret remove (aliases: rm, delete, del)

Delete one or more secrets from an environment.

sigyn secret remove OLD_KEY --env dev
sigyn secret delete UNUSED_VAR --env staging
sigyn secret rm OLD_KEY1 OLD_KEY2 OLD_KEY3 -e dev

secret generate

Generate a random secret and store it.

sigyn secret generate JWT_SECRET --env dev
sigyn secret generate API_TOKEN --env dev --length 64 --type hex
sigyn secret generate SESSION_ID --env dev --type uuid
FlagShortDescription
--length-lLength of generated value (default: 32)
--type-tGeneration type: password, uuid, hex, base64, alphanumeric (default: password)
--env-eTarget environment (default: dev)

secret edit

Open all secrets in an environment in $EDITOR for batch editing. On save, shows a diff of changes (added, modified, removed keys) and asks for confirmation.

sigyn secret edit --env dev
EDITOR=nano sigyn secret edit --env staging
FlagShortDescription
--env-eTarget environment (default: dev)

Search for secrets matching a glob pattern across all environments in a vault.

sigyn secret search 'DB_*'
sigyn secret search 'API_*' --reveal
sigyn secret search '*_URL' --json
FlagShortDescription
--reveal-rShow decrypted values (default: masked)

Supports * (match any) and ? (match single character) wildcards.

secret history

Show the change history of a secret.

sigyn secret history DATABASE_URL --env dev

secret copy

Copy secrets between vaults (or between environments in different vaults). Supports glob patterns for key matching. Each vault is unlocked separately, and both sides are audited.

sigyn secret copy DATABASE_URL --from-vault app1 --to-vault app2
sigyn secret copy 'DB_*' 'API_*' --from-vault src --to-vault dst --from-env prod --to-env staging
FlagDescription
--from-vault <NAME>Source vault name
--to-vault <NAME>Destination vault name
--from-env <ENV>Source environment (default: dev)
--to-env <ENV>Destination environment (default: dev)

Secrets are decrypted from the source vault and re-encrypted with the destination vault’s key. Requires Read access on source and Write access on destination. Audit entries are written on both sides (SecretRead on source, SecretsCopied on destination).

env (alias: e)

Manage environments within a vault.

env list

List all environments in a vault.

sigyn env list
sigyn env list --vault myapp

env create

Create one or more environments.

sigyn env create qa
sigyn env create staging canary prod --vault myapp

env diff

Compare secrets between two environments. Shows added, removed, and changed keys.

sigyn env diff dev staging
sigyn env diff dev prod --reveal
sigyn env diff staging prod --json
FlagDescription
--revealShow actual values in the diff (default: masked markers)

env clone

Clone all secrets from one environment to a new environment.

sigyn env clone dev qa
sigyn env clone staging canary

env promote

Copy secrets from one environment to another. Reports promoted, overwritten, and skipped keys.

sigyn env promote --from dev --to staging
sigyn env promote --from dev --to staging,prod              # chained: dev→staging→prod
sigyn env promote --from staging --to prod --keys DATABASE_URL,API_KEY
FlagDescription
--from <ENV>Source environment
--to <ENV,...>Target environment(s). Multiple targets are chained: each uses the previous target as its source.
--keys <K1,K2,...>Optional comma-separated list of keys to promote (default: all)

env delete

Delete an environment and all its secrets. Requires Admin+ access. Cannot delete the last environment in a vault.

sigyn env delete qa
sigyn env delete canary --force
FlagDescription
--forceSkip confirmation prompt (required in non-interactive mode)

Removes the environment file, updates the manifest, and cleans up the header’s env_slots. The deletion is audited.

policy (alias: p)

Manage access policies, members, and RBAC rules.

policy show

Display the vault policy: owner, audit mode, all members with their roles, allowed environments, secret patterns, and delegation chain.

sigyn policy show
sigyn policy show --vault myapp --json

policy member-add

Add a member to the vault policy. The member’s delegated_by is automatically set to the caller’s fingerprint.

sigyn policy member-add a1b2c3d4e5f6... --role contributor --envs dev,staging
sigyn policy member-add a1b2c3d4e5f6... --role readonly --envs '*' --patterns 'DB_*,API_*'
FlagDescription
--role <ROLE>Role to assign: readonly, auditor, operator, contributor, manager, admin (default: readonly)
--envs <ENVS>Comma-separated allowed environments, or * for all (default: *)
--patterns <PATS>Comma-separated secret key glob patterns (default: *)

policy member-remove

Remove a member from the vault policy.

sigyn policy member-remove a1b2c3d4e5f6...

policy check

Test whether a specific access request would be allowed or denied.

sigyn policy check a1b2c3d4e5f6... read --env dev
sigyn policy check a1b2c3d4e5f6... write --env prod --key DATABASE_URL
FlagDescription
--env, -e <ENV>Environment to check (default: dev)
--key, -k <KEY>Optional secret key to check

Actions: read, write, delete, manage-members, manage-policy, create-env, promote.

policy history

Show policy-related events from the audit log. Filters for member invitations, revocations, policy changes, ownership transfers, and environment creation/deletion.

sigyn policy history
sigyn policy history -n 100
sigyn policy history --json
FlagShortDescription
-nNumber of entries to show (default: 50)

policy audit-mode

Set the vault’s audit push mode. Requires ManagePolicy access (owner or admin).

sigyn policy audit-mode offline
sigyn policy audit-mode online
sigyn policy audit-mode best-effort
ModeBehavior
offlineDefault. Audit entries appended locally; push when convenient.
onlineAudit entries must be pushed after each operation. Operations fail if push fails.
best-effortTry to push; warn on failure but don’t block the operation.

The mode is stored in the signed policy.cbor — members cannot tamper with it.

policy require-mfa

Set per-action MFA requirements on global constraints.

sigyn policy require-mfa read,write,delete
sigyn policy require-mfa all
sigyn policy require-mfa none

policy member-require-mfa

Set per-action MFA requirements on a specific member.

sigyn policy member-require-mfa a1b2c3d4e5f6... read,write

mfa

Manage TOTP-based multi-factor authentication for identities.

mfa setup

Enroll TOTP-based MFA for an identity. Generates a TOTP secret, displays the otpauth:// URI for your authenticator app, then verifies with a code. On success, generates 8 single-use backup codes.

sigyn mfa setup -i alice
FlagDescription
--identity, -i <NAME>Identity to enroll (uses default if omitted)
--jsonOutput URI, secret, and backup codes as JSON

mfa disable

Disable MFA for an identity. Requires a valid TOTP code or backup code to confirm.

sigyn mfa disable -i alice

mfa status

Show MFA enrollment status, including whether a session is active and how many backup codes remain.

sigyn mfa status -i alice

mfa backup

Generate a new set of 8 backup codes, invalidating any previous codes. Requires a valid TOTP code to confirm.

sigyn mfa backup -i alice

delegation (alias: member)

Manage the delegation tree and invitations.

delegation tree

Display the delegation tree showing who invited whom.

sigyn delegation tree
sigyn member tree --vault myapp

Example output:

alice (a1b2c3d4...) [owner]
  bob (e5f6a7b8...) [admin]
    carol (c9d0e1f2...) [contributor]
  dave (a3b4c5d6...) [manager]

delegation invite

Create a signed invitation file for a new member. The invitation is an Ed25519-signed JSON document that can be shared out-of-band (email, chat, etc.) or saved to the vault directory for git-based sharing.

sigyn delegation invite --pubkey <FP> --role contributor --envs dev,staging
sigyn delegation invite --pubkey <FP> --role readonly --envs '*'
sigyn delegation invite --pubkey <FP> --role operator --save-to-vault
FlagDescription
--pubkey <FP>Invitee’s public key fingerprint
--role <ROLE>Role to assign (default: readonly)
--envs <E1,E2>Allowed environments, comma-separated (default: *)
--save-to-vaultAlso save invitation to vault directory for git-based sharing

When --save-to-vault is used, the invitation is written to <vault>/invitations/<uuid>.json. After sigyn sync push, the invitee can sigyn vault clone the repo and accept using the UUID.

delegation accept

Accept an invitation and join a vault. Accepts a file path, full UUID, or UUID prefix. Verifies the Ed25519 signature before accepting. After acceptance, Sigyn prints next steps (sync pull, test access).

# By file path
sigyn delegation accept ./invitation-abc123.json

# By UUID (from 'sigyn delegation pending')
sigyn delegation accept 3a03a59b-114e-444c-a346-6ff33566aa71

# By UUID prefix (shortest unambiguous match)
sigyn delegation accept 3a03a59b

delegation revoke

Revoke one or more members’ access. Environment keys are rotated on revoke.

sigyn delegation revoke a1b2c3d4e5f6...
sigyn delegation revoke a1b2c3d4 b5c6d7e8 f9a0b1c2 --cascade

With --cascade, performs BFS traversal of the delegation tree and revokes the target plus everyone they transitively invited. See Delegation for details.

delegation grant-env

Grant one or more members access to an additional environment.

sigyn delegation grant-env a1b2c3d4 --env prod
sigyn delegation grant-env a1b2c3d4 b5c6d7e8 --env staging

delegation revoke-env

Revoke one or more members’ access to a specific environment. The environment key is rotated.

sigyn delegation revoke-env a1b2c3d4 --env prod
sigyn delegation revoke-env a1b2c3d4 b5c6d7e8 --env staging

delegation pending

List pending invitations.

sigyn delegation pending

delegation bulk-invite

Invite multiple members at once from a JSON file. All entries are validated before any mutations are applied (all-or-nothing validation). Header and policy are saved once at the end.

sigyn delegation bulk-invite --file members.json
sigyn delegation bulk-invite --file members.json --force
FlagDescription
--file <PATH>Path to JSON file with member definitions
--forceSkip confirmation prompt

File format (JSON array):

[
  {"fingerprint": "a1b2c3d4e5f6...", "role": "contributor", "envs": "dev,staging"},
  {"fingerprint": "f7e8d9c0b1a2...", "role": "readonly", "envs": "*"}
]

Each entry supports fingerprint (required), role (default: readonly), and envs (default: *).

delegation bulk-revoke

Revoke multiple members at once from a JSON file. Delegates to the standard revoke logic, including environment key rotation and optional cascade.

sigyn delegation bulk-revoke --file revoke-list.json
sigyn delegation bulk-revoke --file revoke-list.json --cascade --force
FlagDescription
--file <PATH>Path to JSON file with fingerprint list
--cascadeAlso revoke all members they transitively invited
--forceSkip confirmation prompt

File format (JSON array of fingerprint strings):

["a1b2c3d4e5f6...", "f7e8d9c0b1a2..."]

audit

View and verify the hash-chained audit trail.

audit tail

Show the most recent audit entries.

sigyn audit tail
sigyn audit tail -n 50
sigyn audit tail --vault myapp --json
FlagDescription
-n <COUNT>Number of entries to show (default: 20)

audit query

Search the audit log with filters.

sigyn audit query --actor a1b2c3d4...
sigyn audit query --env prod
sigyn audit query --actor a1b2c3d4... --env dev

audit verify

Verify the integrity of the hash chain. Reports the first broken entry if tampering is detected, and also checks for tail truncation against a device-local audit tip.

Exit status: returns non-zero when the chain is broken, a signature is invalid, or the log has been truncated — so it is safe to gate on, e.g. sigyn audit verify && deploy. Entries whose signing key is not available locally are reported as a warning and do not cause a non-zero exit.

sigyn audit verify
sigyn audit verify --vault myapp

audit export

Export the full audit log to a file.

sigyn audit export --output audit.json --format json
sigyn audit export --output audit.csv --format csv
FlagDescription
--output <PATH>Output file path
--format <FMT>Export format: json or csv (default: json)

audit witness

Countersign the latest audit entry with your Ed25519 key. Provides independent verification that a second party observed the entry.

sigyn audit witness
sigyn audit witness --vault myapp

audit anchor

Anchor the audit trail to a git commit for tamper-evidence.

sigyn audit anchor -v myapp

sync (alias: sy)

Synchronize vaults via git. All data is encrypted at rest; sync never needs the master key.

sync push

Push local vault changes to a remote repository.

sigyn sync push
sigyn sync push --remote origin --branch main
FlagDescription
--remote <NAME>Git remote name (default: origin)
--branch <NAME>Branch name (default: main)

sync pull

Pull remote changes into the local vault.

sigyn sync pull
sigyn sync pull --remote origin --branch main

sync status

Show sync status (clean, local changes, ahead/behind remote).

sigyn sync status
sigyn sync status --vault myapp

sync resolve

Resolve a sync conflict for a specific key.

sigyn sync resolve DATABASE_URL --strategy local
sigyn sync resolve API_KEY --strategy remote
sigyn sync resolve CONFIG --strategy latest
FlagDescription
--strategy <S>Resolution strategy: local, remote, latest

sync configure

Configure sync settings for a vault.

sigyn sync configure --remote-url [email protected]:team/secrets.git
sigyn sync configure --auto-sync true

sync deploy-key generate

Generate a sealed SSH deploy key for audit push. The key is encrypted with the vault cipher and stored in the vault directory. Requires ManagePolicy access.

sigyn sync deploy-key generate

Prints the public key — add it as a deploy key with push access on your git remote.

sync deploy-key show-pubkey

Show the public key of the vault’s deploy key.

sigyn sync deploy-key show-pubkey

sync deploy-key remove

Remove the vault’s deploy key.

sigyn sync deploy-key remove

fork (alias: f)

Manage vault forks for isolated workstreams.

fork create

Create a fork of the current vault.

sigyn fork create feature-branch
sigyn fork create sandbox --mode unleashed
sigyn fork create sprint-3 --mode leashed --expires-days 30
FlagDescription
--mode <MODE>Fork mode: leashed (connected to parent) or unleashed (independent). Default: leashed
--expires-days <N>Days until fork expires. 0 = no expiry. Default: 0

fork list

List all forks for a vault.

sigyn fork list

fork sync

Sync a leashed fork with its parent vault.

sigyn fork sync feature-branch

fork approve

Approve pending changes from a fork back to the parent.

sigyn fork approve feature-branch

fork expire

Manually expire a fork.

sigyn fork expire feature-branch

fork status

Show the status of a specific fork.

sigyn fork status feature-branch

project

Manage project-level configuration.

project init

Generate a .sigyn.toml in the current directory. Interactively prompts to select a vault and identity from those available on the machine. Sigyn auto-detects the project type from package.json, Cargo.toml, pyproject.toml, or go.mod and pre-selects a matching vault if one exists.

sigyn project init                          # interactive (with auto-detection)
sigyn project init -v myapp -i alice        # non-interactive
sigyn project init --global                 # write to ~/.sigyn/project.toml instead
FlagShortDescription
--vault-vVault name (skips prompt)
--env-eEnvironment name (default: dev)
--identity-iIdentity name (skips prompt)
--globalWrite to ~/.sigyn/project.toml instead of ./.sigyn.toml

context (alias: ctx)

Manage a persistent context that sets default vault and environment. Stored at ~/.sigyn/context.toml. Context sits between CLI flags and .sigyn.toml in the resolution chain.

context set

Set the active context.

sigyn context set myapp          # set vault only
sigyn context set myapp prod     # set vault and env
sigyn ctx set myapp dev          # using alias

context show

Show the current context.

sigyn context show
sigyn ctx show --json

context clear

Clear the current context.

sigyn context clear

run (alias: r)

Run processes with injected secrets or export secrets in various formats.

The exec subcommand is the default – you can omit it:

# These are equivalent:
sigyn run -- ./my-app
sigyn run exec -- ./my-app

If no vault is configured (no --vault, no .sigyn.toml, no default), Sigyn will detect the project type and offer to create a .sigyn.toml interactively. In non-interactive environments (CI, piped output), it prints an actionable error instead.

Use --dry-run to preview what would happen without executing:

sigyn run --dry-run -e prod -- ./my-app
# [dry-run] Vault: 'myapp', env: 'prod', secrets: 12
# [dry-run] Command: ./my-app
# [dry-run] Clean env: no (inheriting parent)

run (exec)

Execute a command with secrets injected as environment variables. Secrets are never written to disk in plaintext.

sigyn run -e dev -- ./my-app
sigyn run --prod -- docker compose up
sigyn run -e dev -c -- node server.js
FlagShortDescription
--env-eEnvironment to load secrets from
--clean-cDo not inherit the parent process environment
--prodShorthand for --env prod
--stagingShorthand for --env staging

Inline Secret Refs

You can reference secrets directly in command arguments using {{KEY}} syntax. The values are substituted before the command is executed:

sigyn run -e prod -- curl -H "Authorization: Bearer {{API_KEY}}" https://api.example.com
sigyn run -e dev -- psql "{{DATABASE_URL}}"

Unresolved refs (keys not found in the environment) are left as-is.

Named commands

If a .sigyn.toml is present with a [commands] table, you can run named commands:

# .sigyn.toml
[commands]
dev = "npm run dev"
app = "./my-app"
sigyn run dev              # runs 'npm run dev' with secrets injected
sigyn run app --prod       # runs './my-app' with prod env secrets

run export

Export secrets in a specified format to stdout.

sigyn run export --env dev --format dotenv > .env
sigyn run export --env dev --format json
sigyn run export --env prod --format k8s --name my-app-secrets
sigyn run export --env dev --format docker
sigyn run export --env dev --format shell
FlagShortDescription
--env-eEnvironment to export
--format-fOutput format: dotenv, json, shell, docker, k8s (default: dotenv)
--nameResource name for k8s format (default: app-secrets)

run serve

Serve secrets over a Unix domain socket for local process consumption.

sigyn run serve --env dev
sigyn run serve --env dev --socket /tmp/my-app.sock

watch

Watch for secret changes and automatically restart a command. Polls the encrypted environment file for modifications and restarts the child process when changes are detected.

sigyn watch -- npm run dev
sigyn watch -e staging --interval 5 -- ./my-app
sigyn watch --clean -- node server.js
FlagShortDescription
--interval <SECS>Poll interval in seconds (default: 2)
--clean-cDon’t inherit the parent process environment

Uses the global --env / -e flag for environment selection. If the child process exits on its own, watch exits with the same code (does not restart).

rotate (alias: rot)

Manage secret rotation and lifecycle.

rotate key

Rotate a specific secret by generating a new random value.

sigyn rotate key DATABASE_PASSWORD --env prod
sigyn rotate key API_KEY --env dev

rotate schedule list

List all rotation schedules.

sigyn rotate schedule list -v myapp

rotate schedule set

Set a rotation schedule for a key.

sigyn rotate schedule set -v myapp --key DB_PASSWORD --cron "0 0 * * MON" --grace-hours 48

Options:

  • --key — Secret key name
  • --cron — Cron expression for schedule
  • --grace-hours — Grace period in hours (default: 24)
  • --hooks — Post-rotation hooks (comma-separated)

rotate schedule remove

Remove a rotation schedule.

sigyn rotate schedule remove -v myapp --key DB_PASSWORD

rotate due

List secrets that are due for rotation based on age.

sigyn rotate due --env dev
sigyn rotate due --env prod --max-age 30
FlagDescription
--max-age <DAYS>Maximum age in days before a secret is considered due (default: 90)
--env, -e <ENV>Environment to check

rotate breach-mode

Activate breach mode: rotate all secrets across all environments and revoke all delegated members. Requires confirmation unless --force is set.

sigyn rotate breach-mode
sigyn rotate breach-mode --force

rotate dead-check

Find unused or stale secrets that may be candidates for removal.

sigyn rotate dead-check --env dev
sigyn rotate dead-check --env prod --max-age 365
FlagDescription
--max-age <DAYS>Maximum age in days (default: 180)

import (alias: imp)

Import secrets from external files and cloud providers.

import dotenv

Import key-value pairs from a .env file.

sigyn import dotenv .env --env dev
sigyn import dotenv /path/to/production.env --env prod

import json

Import key-value pairs from a JSON file (expects a flat object with string values).

sigyn import json secrets.json --env dev

import doppler

Import secrets from Doppler.

sigyn import doppler --project myapp --config dev --env dev

import aws

Import a secret from AWS Secrets Manager.

sigyn import aws --secret-id myapp/db-credentials --env prod
sigyn import aws --secret-id arn:aws:secretsmanager:... --region us-east-1 --env prod

import gcp

Import a secret from GCP Secret Manager.

sigyn import gcp --project my-gcp-project --secret db-password --env prod
sigyn import gcp --project my-gcp-project --secret api-key --version 3 --env prod

import 1password (alias: op)

Import secrets from 1Password via the op CLI.

sigyn import 1password --vault "Dev Secrets" --item "API Keys" --env dev
sigyn import op --vault Production --item "Database Creds" --env prod

org

Manage hierarchical organizations. See Organizations for the full design document.

org create

Create a root organization. The caller becomes the owner.

sigyn org create acme

Creates ~/.sigyn/orgs/acme/ with node.toml, members.cbor, and policy.cbor.

org node create

Add a child node to an existing org path.

sigyn org node create platform --parent acme --type division
sigyn org node create web --parent acme/platform --type team
FlagDescription
--parent <PATH>Parent org path (e.g., acme or acme/platform)
--type <TYPE>Node type string (default: team)

org node remove

Remove an empty node (no children, no linked vaults).

sigyn org node remove acme/platform/web

org tree

Display the hierarchy tree.

sigyn org tree
sigyn org tree --org acme

org info

Show node metadata (UUID, type, owner, children, linked vaults, git remote).

sigyn org info acme/platform/web

org policy show

Display the RBAC policy at a hierarchy node.

sigyn org policy show --path acme

org policy member-add

Add a member at a hierarchy level. Permissions cascade to child nodes via inheritance.

sigyn org policy member-add <fingerprint> --role admin --path acme
FlagDescription
--role <ROLE>Role to assign: readonly, auditor, operator, contributor, manager, admin, owner
--path <PATH>Org path where the membership applies

org policy member-remove

Remove a member from a hierarchy level.

sigyn org policy member-remove <fingerprint> --path acme

org policy effective

Show the merged effective permissions for a member by walking the chain from the target node to the root org.

sigyn org policy effective <fingerprint> --path acme/platform/web

org sync configure

Set the git remote for a hierarchy node. Child nodes without an explicit remote inherit from the nearest ancestor.

sigyn org sync configure --path acme --remote-url [email protected]:acme/secrets.git
sigyn org sync configure --path acme/platform/web --remote-url [email protected]:acme/web.git --branch develop
FlagDescription
--path <PATH>Org path to configure
--remote-url <URL>Git remote URL
--branch <NAME>Branch name (default: main)

recovery

Disaster recovery via the standalone sigyn-recovery binary.

recovery split

Split an identity’s private key into Shamir shards.

sigyn-recovery split --identity alice --threshold 3 --total 5
sigyn-recovery split --identity alice --threshold 2 --total 3 --output ./shards

recovery restore

Reconstruct an identity from Shamir shards.

sigyn-recovery restore shard-a1b2c3d4-1.json shard-a1b2c3d4-2.json shard-a1b2c3d4-3.json
sigyn-recovery restore shard-*.json --output recovered_key.bin

recovery print-shards

Print shard details for labeling paper backups.

sigyn-recovery print-shards shard-a1b2c3d4-*.json

recovery snapshots

List available vault snapshots from git history.

sigyn-recovery snapshots --vault myapp

agent

Manage the passphrase agent — an ssh-agent style daemon that caches decrypted keys in memory, eliminating repeated passphrase prompts when running multiple commands.

agent start

Start the agent daemon in the background.

eval $(sigyn agent start)
eval $(sigyn agent start --timeout 60)   # 60-minute cache

Prints a shell-eval-able export statement for SIGYN_AGENT_SOCK. The agent forks to background and listens on a Unix socket with owner-only permissions (0600).

FlagDescription
--timeout <MINUTES>Key cache timeout (default: 30 minutes)

agent stop

Stop the agent daemon and zeroize all cached keys.

sigyn agent stop

agent lock

Clear all cached keys but keep the daemon running.

sigyn agent lock

agent status

Show whether the agent is running and how many keys are cached.

sigyn agent status

Utility Commands

web

Launch the local web GUI in your browser. Requires building with --features web.

sigyn web                          # http://127.0.0.1:9847
sigyn web --port 8080              # custom port
sigyn web --open                   # auto-open browser
sigyn web --timeout 60             # 60-minute session timeout
FlagDescription
--port <PORT>Port to listen on (default: 9847)
--timeout <MINUTES>Session timeout in minutes (default: 30)
--openOpen browser automatically on start

The web GUI binds exclusively to 127.0.0.1 (localhost) and is never accessible remotely. See the Web GUI page for details.

tui

Launch the interactive TUI dashboard (built with ratatui + crossterm).

sigyn tui
sigyn tui --vault myapp --env dev

doctor

Run health checks on the Sigyn installation. Checks include: home directory, identities, vaults, config file, .sigyn.toml in the current directory tree, default vault accessibility, git availability, pending invitations, and project type detection.

sigyn doctor

onboard

Guided first-run setup wizard. Walks through identity creation, vault creation, .env file import, and .sigyn.toml setup. In non-interactive mode, prints a checklist of what’s missing.

sigyn onboard
sigyn onboard --json   # report setup status as JSON

status

Show current configuration status including identity, vault, environments, sync status, rotation schedules, and pending invitations.

sigyn status
sigyn status --json

init

Initialize the default configuration file at ~/.sigyn/config.toml. In interactive mode, offers to create an identity and vault if none exist, then runs doctor checks.

sigyn init
sigyn init --identity alice --vault myapp

notification configure

Interactively configure a webhook for receiving notifications about vault events. Webhook URLs must use HTTPS (HTTP is only allowed for localhost). If a shared secret is provided, requests include an X-Sigyn-Signature HMAC header for verification.

sigyn notification configure

notification test

Send a test notification to all configured webhooks.

sigyn notification test
sigyn notification test --json

notification list

List all configured webhook endpoints and their event filters.

sigyn notification list
sigyn notification list --json

update

Self-update to the latest release. Downloads the appropriate binary for the current platform and verifies the SHA-256 checksum before replacing the running binary. The update will abort if the checksum file cannot be downloaded or verification fails.

Security hardening: HTTP requests have connect (10s) and total (120s) timeouts. Archives exceeding 100 MiB are rejected. Tar entries with path traversal (..) are blocked. Temp files use random names to prevent symlink attacks.

sigyn update

completions

Generate shell completion scripts.

sigyn completions bash > ~/.local/share/bash-completion/completions/sigyn
sigyn completions zsh > ~/.zfunc/_sigyn
sigyn completions fish > ~/.config/fish/completions/sigyn.fish
sigyn completions powershell >> $PROFILE

Supported shells: bash, zsh, fish, powershell.

ci

CI/CD integration helpers.

ci setup

Export identity bundle for use in CI/CD pipelines. Outputs the base64-encoded identity file, device key, and fingerprint needed to configure GitHub Actions secrets (or equivalent in other CI systems).

sigyn ci setup              # uses default identity
sigyn ci setup ci-bot       # uses specific identity
sigyn ci setup --json       # machine-readable output

The command outputs a single SIGYN_CI_BUNDLE value (base64-encoded JSON containing identity file, device key, and fingerprint). You also need SIGYN_PASSPHRASE and VAULT_SSH_KEY as separate GitHub secrets — 3 secrets total.

See the CI/CD Integration page for full workflow examples, the GitHub Action reference, and troubleshooting.

Web GUI

Sigyn includes an optional local web GUI for managing vaults and secrets through your browser. It provides a visual alternative to the CLI while using the same engine and security model underneath.

Building

The web GUI is behind a feature flag to keep the default binary lean:

cargo build --release --features web

Usage

sigyn web                     # Start on http://127.0.0.1:9847
sigyn web --open              # Start and open browser
sigyn web --port 8080         # Custom port
sigyn web --timeout 60        # 60-minute session timeout
FlagDescription
--port <PORT>Port to listen on (default: 9847)
--timeout <MINUTES>Session timeout in minutes (default: 30)
--openOpen browser automatically

Features

  • Identity login – Select your identity and enter your passphrase to authenticate
  • Vault dashboard – Browse all vaults in a card-based layout
  • Secret management – View, add, edit, and delete secrets with full policy enforcement
  • Environment switching – Switch between dev/staging/prod via dropdown
  • Member list – View vault members and their roles
  • Audit log – Browse the hash-chained audit trail with timestamps and actors
  • Vault info – View vault metadata (ID, owner, environments, description)

Architecture

The web GUI is implemented as a Rust crate (sigyn-web) with:

  • Backend: axum HTTP server calling sigyn-engine directly
  • Frontend: Single embedded HTML file using Alpine.js and Tailwind CSS (no build tooling)
  • Sessions: In-memory session store with TTL expiry (mirrors the agent daemon pattern)

The entire GUI is compiled into the binary via include_str! – no external files needed at runtime.

Security

Localhost-only binding

The server binds exclusively to 127.0.0.1. It is never accessible from other machines on the network.

Session management

  • 256-bit cryptographically random session tokens
  • HttpOnly; SameSite=Strict cookies (no JavaScript access, no cross-site leakage)
  • Configurable timeout with automatic session sweep (default: 30 minutes of inactivity)
  • Key material is held in memory only while the session is active

Passphrase handling

  • The passphrase is used once to call IdentityStore::load(), then immediately cleared
  • It is never stored in the session, never logged, never written to disk
  • Only the derived LoadedIdentity (private keys) is cached, matching the agent daemon’s approach

Rate limiting

Failed login attempts are rate-limited to 5 per minute per fingerprint to prevent brute-force passphrase guessing.

Policy enforcement

All secret operations go through the same PolicyEngine::evaluate() checks as the CLI. Every mutation generates a signed audit log entry.

REST API

The web GUI exposes a JSON REST API that the frontend consumes. These endpoints are also usable for scripting if needed:

MethodPathAuthDescription
POST/api/auth/loginNo{fingerprint, passphrase} – create session
POST/api/auth/logoutYesDestroy session
GET/api/auth/statusYesCheck session, return identity info
GET/api/identitiesNoList identities (public info only)
GET/api/vaultsYesList vault names
GET/api/vaults/:nameYesVault info (triggers unlock)
GET/api/vaults/:vault/envs/:env/secretsYesList secrets (values masked)
GET/api/vaults/:vault/envs/:env/secrets/:keyYesRead secret value
POST/api/vaults/:vault/envs/:env/secretsYesSet secret {key, value, secret_type}
DELETE/api/vaults/:vault/envs/:env/secrets/:keyYesDelete secret
GET/api/vaults/:vault/audit?limit=50YesAudit log entries

Comparison with TUI

FeatureTUI (sigyn tui)Web GUI (sigyn web)
Read secretsView keys (values masked)View keys + reveal values
Write secretsNot supportedFull CRUD
MembersRead-only listRead-only list
Audit logLast 20 entriesPaginated (up to 100)
EnvironmentsFixed at launchSwitchable via dropdown
Build requirementDefault build--features web
External depsNoneBrowser

Environments

Sigyn organizes secrets by environment. Common environments include dev, staging, and prod, but you can create any name.

Creating Environments

sigyn env create dev
sigyn env create staging
sigyn env create prod

Each environment is stored as a separate encrypted file (envs/<name>.vault), encrypted with its own independent 256-bit key. Members only receive decryption keys for environments they are authorized to access, providing cryptographic isolation between environments – not just policy-level separation.

Listing Environments

sigyn env list

Setting Secrets Per Environment

sigyn secret set DATABASE_URL 'postgres://localhost/myapp' --env dev
sigyn secret set DATABASE_URL 'postgres://staging-host/myapp' --env staging
sigyn secret set DATABASE_URL 'postgres://prod-host/myapp' --env prod

Promoting Secrets

Promotion copies secrets from one environment to another. This is useful when a configuration has been validated in staging and is ready for production.

# Promote all secrets from dev to staging
sigyn env promote --from dev --to staging

# Promote specific keys only
sigyn env promote --from staging --to prod --keys DATABASE_URL,API_KEY

Promotion records are logged in the audit trail.

Deleting Environments

Environments can be deleted if they are no longer needed. Requires Admin+ access. A vault must always have at least one environment.

sigyn env delete qa
sigyn env delete canary --force

Deletion removes the encrypted env file, removes the environment from the manifest, and cleans up the corresponding key slots from the header. The action is recorded in the audit trail.

Environment Policies

Environments can have role-based restrictions. For example, you can require the Admin role to write to prod while allowing Contributor access to dev and staging. These are configured through the policy engine:

sigyn policy member add <fingerprint> --role contributor --env dev,staging

Members with access to an environment can read and write secrets within that environment, subject to their role and any additional constraints (time windows, IP allowlists, expiry).

Cryptographic Isolation

Each environment has its own independent encryption key. When a member is granted access to specific environments (e.g., --env dev,staging), they receive key slots only for those environments. They physically cannot decrypt environments they are not authorized for.

This means:

  • A Contributor with allowed_envs: ["dev"] cannot decrypt prod secrets, even with direct access to the encrypted files on disk.
  • Revoking a member only rotates keys for environments they had access to – unaffected environments are untouched.
  • Granting or revoking access to individual environments is supported without affecting other environments:
# Grant access to an additional environment
sigyn delegation grant-env <fingerprint> staging

# Revoke access to a specific environment (rotates that env's key)
sigyn delegation revoke-env <fingerprint> staging

See Security Model for details on the envelope encryption scheme.

Import and Export

Sigyn can import secrets from several sources and export to multiple formats.

Import

From .env Files

sigyn import dotenv .env --env dev
sigyn import dotenv .env.production --env prod

From JSON

sigyn import json secrets.json --env dev

The JSON file should be a flat object with string values:

{
  "DATABASE_URL": "postgres://...",
  "API_KEY": "sk-..."
}

From Doppler

sigyn import doppler --project myapp --config dev --env dev

Requires the Doppler CLI to be installed and authenticated. Sigyn calls doppler secrets download and parses the output.

From AWS Secrets Manager

sigyn import aws --secret-id myapp/dev --env dev --region us-east-1

Requires the AWS CLI to be installed and configured. Supports both key/value and plain string secrets.

From GCP Secret Manager

sigyn import gcp --secret myapp-config --env dev --project my-gcp-project

Requires the gcloud CLI to be installed and authenticated.

From 1Password

sigyn import 1password --item "MyApp Secrets" --vault "Engineering" --env dev

Requires the 1Password CLI (op) to be installed and authenticated. Imports all fields from the specified item.

Export

Export secrets to various formats for use in deployment pipelines or local development.

# .env format
sigyn run export --env dev --format dotenv > .env

# JSON
sigyn run export --env dev --format json > secrets.json

# Kubernetes Secret manifest
sigyn run export --env prod --format k8s > k8s-secret.yaml

# Docker --env-file format
sigyn run export --env dev --format docker > docker.env

# Shell eval (export statements)
sigyn run export --env dev --format shell
eval "$(sigyn run export --env dev --format shell)"

Process Injection

The recommended way to use secrets in applications is process injection. Secrets are passed as environment variables to a child process and never written to disk:

sigyn run exec --env dev -- ./myapp
sigyn run exec --env prod -- docker compose up

Unix Socket Server

For programmatic access, Sigyn can serve secrets over a Unix domain socket:

sigyn run serve --env dev --socket ~/.sigyn/sigyn.sock

Clients connect and use a simple text protocol:

  • LIST — returns all secret keys
  • GET <key> — returns the value for a key
  • QUIT — closes the connection

Working with Multiple Vaults, Projects, and Organizations

Sigyn supports managing secrets across multiple projects and organizations from a single machine. Each vault is fully independent with its own master key, members, policies, audit log, and git remote.

Multiple Vaults

Create a separate vault for each project:

sigyn vault create webapp
sigyn vault create api-service
sigyn vault create infra

Each vault is stored in its own directory under ~/.sigyn/vaults/:

~/.sigyn/vaults/
├── webapp/
├── api-service/
└── infra/

Use --vault to specify which vault a command operates on:

sigyn secret set DB_URL 'postgres://...' --vault webapp --env dev
sigyn secret set DB_URL 'postgres://...' --vault api-service --env dev

Or set a default vault to avoid repeating it:

sigyn init --vault webapp
sigyn secret set DB_URL 'postgres://...' --env dev   # uses webapp

List all vaults on this machine:

sigyn vault list

Multiple Git Remotes

Each vault syncs to its own git remote independently. This means different projects can live in different repositories, different GitHub organizations, or different hosting providers entirely.

sigyn sync configure --vault webapp --remote-url [email protected]:acme/webapp-secrets.git
sigyn sync configure --vault api-service --remote-url [email protected]:acme/api-secrets.git
sigyn sync configure --vault infra --remote-url [email protected]:acme/infra-secrets.git

Push and pull are per-vault:

sigyn sync push --vault webapp
sigyn sync pull --vault api-service

Multiple Organizations

If you work for multiple organizations, you can use separate identities and vaults for each. There is no shared state between vaults — each has its own master key and member list.

# Identity for Acme Corp
sigyn identity create --name "alice-acme" --email [email protected]

# Identity for Globex Inc
sigyn identity create --name "alice-globex" --email [email protected]

Create vaults using the appropriate identity:

sigyn vault create acme-app --identity alice-acme
sigyn vault create globex-app --identity alice-globex

Each vault syncs to that organization’s repository:

sigyn sync configure --vault acme-app --remote-url [email protected]:acme/secrets.git
sigyn sync configure --vault globex-app --remote-url [email protected]:globex/secrets.git

Access secrets with the matching identity:

sigyn secret get API_KEY --vault acme-app --identity alice-acme --env prod
sigyn secret get API_KEY --vault globex-app --identity alice-globex --env prod

Switching Contexts

The --vault and --identity flags override the defaults set in ~/.sigyn/config.toml for a single command. To change your default context:

# Switch default vault
sigyn init --vault acme-app --identity alice-acme

# Later, switch to a different project
sigyn init --vault globex-app --identity alice-globex

The sigyn status command shows your current defaults:

sigyn status

Isolation Guarantees

Each vault is cryptographically isolated:

  • Separate master keys. Vault A’s master key cannot decrypt vault B’s secrets.
  • Separate member lists. Being a member of vault A grants no access to vault B.
  • Separate audit logs. Operations on vault A do not appear in vault B’s audit trail.
  • Separate sync. Pushing vault A does not affect vault B’s git remote.
  • Separate policies. RBAC roles, constraints, and delegation trees are per-vault.

An identity (keypair) can be a member of multiple vaults. The same X25519 public key is used to create a recipient slot in each vault’s envelope header. Compromising one vault’s master key does not compromise other vaults, even if they share members.

Example: Full Multi-Org Setup

# Create identities
sigyn identity create --name "alice-acme" --email [email protected]
sigyn identity create --name "alice-globex" --email [email protected]

# Create vaults
sigyn vault create acme-web --identity alice-acme
sigyn vault create acme-api --identity alice-acme
sigyn vault create globex-platform --identity alice-globex

# Configure sync remotes
sigyn sync configure --vault acme-web --remote-url [email protected]:acme/web-secrets.git
sigyn sync configure --vault acme-api --remote-url [email protected]:acme/api-secrets.git
sigyn sync configure --vault globex-platform --remote-url [email protected]:globex/platform-secrets.git

# Add secrets to each
sigyn secret set DB_URL 'postgres://acme-web-db/...' --vault acme-web --env prod
sigyn secret set DB_URL 'postgres://acme-api-db/...' --vault acme-api --env prod
sigyn secret set DB_URL 'postgres://globex-db/...' --vault globex-platform --env prod

# Run applications with the right secrets
sigyn run exec --vault acme-web --env prod -- ./acme-web-server
sigyn run exec --vault globex-platform --env prod -- ./globex-app

# Sync each independently
sigyn sync push --vault acme-web
sigyn sync push --vault acme-api
sigyn sync push --vault globex-platform

Organizations

This document describes how Sigyn organizes vaults into hierarchical organizations, including the on-disk layout, RBAC inheritance, cascading encryption, and git remote inheritance.

Overview

Organizations let you group vaults under a nested hierarchy of divisions, teams, and other organizational units. Each node in the hierarchy carries its own encrypted RBAC policy. Permissions cascade downward – a member added at the top of the tree automatically gains access to every child node and linked vault below.

Three governing principles:

  1. Highest role wins: when a member appears at multiple levels, the highest role is used for access decisions.
  2. Permission union: allowed environments and secret patterns are unioned across all levels in the chain.
  3. Encryption per node: each hierarchy node has its own envelope header and encrypted policy, so a compromise at one level does not expose sibling branches.

On-Disk Layout

Organizations live under ~/.sigyn/orgs/. Each node is a directory containing a manifest, encrypted policy, and envelope header:

~/.sigyn/orgs/
  └── acme/                          # root org (node_type: "org")
      ├── node.toml                  # NodeManifest (metadata, owner, children)
      ├── members.cbor               # Envelope header (sealed master key slots)
      ├── policy.cbor                # Encrypted VaultPolicy (RBAC)
      └── children/
          └── platform/              # child node (node_type: "division")
              ├── node.toml
              ├── members.cbor
              ├── policy.cbor
              └── children/
                  └── web/           # grandchild (node_type: "team")
                      ├── node.toml
                      ├── members.cbor
                      └── policy.cbor

NodeManifest (node.toml)

Each node’s manifest contains:

FieldDescription
node_idUnique UUID
nameHuman-readable node name
node_typeNode kind: org, division, team, or any custom string
parent_idUUID of the parent node (None for root orgs)
ownerBLAKE3 fingerprint of the node creator
childrenList of ChildRef entries (id, name, type)
created_atUTC creation timestamp
descriptionOptional description
git_remoteOptional GitRemoteConfig (url, branch)

Vault Linking

Vaults are linked to org nodes via their VaultManifest. When a vault is attached to an org path, its org_path field is set (e.g., "acme/platform/web"). The vault files remain in ~/.sigyn/vaults/ – only the metadata link changes.

Creating Organizations

Create a root org

sigyn org create acme

Creates ~/.sigyn/orgs/acme/ with a node.toml (type org), sealed members.cbor, and encrypted policy.cbor. The caller becomes the owner.

Add child nodes

sigyn org node create platform --parent acme --type division
sigyn org node create web --parent acme/platform --type team
FlagDescription
--parent <PATH>Parent org path (e.g., acme or acme/platform)
--type <TYPE>Node type string (default: team)

Remove a node

sigyn org node remove acme/platform/web

Only empty nodes (no children, no linked vaults) can be removed.

Linking Vaults to Organizations

Create a vault under an org

sigyn vault create myapp --org acme/platform/web

Creates the vault and immediately sets its org_path to the given path.

Attach an existing vault

sigyn vault attach legacy-app --org acme

Sets the vault’s org_path field. The vault must not already be linked to another org.

Detach a vault

sigyn vault detach legacy-app

Clears the vault’s org_path field, removing it from the hierarchy.

Viewing the Hierarchy

Tree view

sigyn org tree
sigyn org tree --org acme

Displays the full org hierarchy with node types:

acme (org)
  └── platform (division)
      └── web (team)

Node info

sigyn org info acme/platform/web

Shows node metadata: UUID, type, owner, children, creation date, linked vaults, and git remote configuration.

RBAC at the Org Level

Each hierarchy node has its own encrypted VaultPolicy. Members can be added, removed, and inspected at any level.

Add a member

sigyn org policy member-add <fingerprint> --role admin --path acme
sigyn org policy member-add <fingerprint> --role contributor --path acme/platform/web
FlagDescription
--role <ROLE>Role to assign: readonly, auditor, operator, contributor, manager, admin, owner
--path <PATH>Org path where the membership applies

Remove a member

sigyn org policy member-remove <fingerprint> --path acme

Show policy

sigyn org policy show --path acme
sigyn org policy show --path acme/platform/web

Displays all members at that node: fingerprint, role, allowed environments, and secret patterns.

Check effective permissions

sigyn org policy effective <fingerprint> --path acme/platform/web

Computes the merged permissions by walking the chain from the target node up to the root org, then displays the effective role, environments, and patterns.

Inherited Permissions

When Sigyn evaluates access for a member at a given node, it builds a policy chain from the target node up through every ancestor to the root org. The merge rules are:

Highest role wins

If a member is ReadOnly at acme/platform/web but Admin at acme, their effective role is Admin.

Role levels (highest wins):
  1 ReadOnly
  2 Auditor
  3 Operator
  4 Contributor
  5 Manager
  6 Admin
  7 Owner

Environment union

Allowed environments from all levels are combined. If any level grants * (all environments), the result is *.

LevelEnvsMerged
acmestaging, prod
acme/platform–
acme/platform/webdevdev, staging, prod

Pattern union

Secret patterns from all levels are combined with the same logic. If any level grants *, the result is *.

Owner at any level

If a member is the owner at any node in the chain, they are granted full access immediately – no further checks are performed.

Cascading Encryption

Each hierarchy node has its own members.cbor (envelope header) and policy.cbor. When a member is added at a node, a sealed slot is created for that node’s master key. This means:

  • Adding a member at acme gives them a key slot for the acme node only.
  • The member must also have slots at child nodes to decrypt those policies.
  • The org policy member-add command handles slot creation at the target level.

When a member is removed, their slot is deleted and the node’s master key is rotated, just like vault-level revocation.

Git Remote Inheritance

Each node can optionally have a GitRemoteConfig (url + branch). When Sigyn needs the git remote for a node, it walks up the hierarchy from the target node to the root and uses the first non-empty remote it finds.

# Set a remote at the org level
sigyn org sync configure --path acme \
  --remote-url [email protected]:acme/secrets.git

# Override at a child node
sigyn org sync configure --path acme/platform/web \
  --remote-url [email protected]:acme/web-secrets.git \
  --branch develop
FlagDescription
--path <PATH>Org path to configure
--remote-url <URL>Git remote URL
--branch <NAME>Branch name (default: main)

Child nodes without an explicit remote inherit from their nearest ancestor that has one configured.

Worked Example

A complete walkthrough for setting up an organization:

# 1. Create identities
sigyn identity create -n alice
sigyn identity create -n bob

# 2. Create the org hierarchy
sigyn org create acme
sigyn org node create platform --parent acme --type division
sigyn org node create web --parent acme/platform --type team
sigyn org node create mobile --parent acme/platform --type team

# 3. Create vaults under the hierarchy
sigyn vault create webapp --org acme/platform/web
sigyn vault create ios-app --org acme/platform/mobile

# 4. Attach an existing vault
sigyn vault create shared-infra
sigyn vault attach shared-infra --org acme

# 5. Configure git sync at the org level (inherited by children)
sigyn org sync configure --path acme \
  --remote-url [email protected]:acme-corp/secrets.git

# 6. Add an org-wide admin (inherits access to all children and vaults)
sigyn org policy member-add <bob-fingerprint> --role admin --path acme

# 7. Add a team-level contributor
sigyn org policy member-add <carol-fingerprint> --role contributor \
  --path acme/platform/web

# 8. Check Bob's effective permissions at the web team level
sigyn org policy effective <bob-fingerprint> --path acme/platform/web
#   -> Role: Admin (inherited from acme)
#   -> Envs: * (inherited)
#   -> Patterns: * (inherited)

# 9. View the full hierarchy
sigyn org tree
#   acme (org)
#     └── platform (division)
#         ├── web (team)
#         └── mobile (team)

# 10. Detach a vault if needed
sigyn vault detach shared-infra

Commands Summary

CommandDescription
org create <name>Create a root organization
org node create <name> --parent <path>Add a child node
org node remove <path>Remove an empty node
org tree [--org <name>]Display hierarchy tree
org info <path>Show node details
org policy show --path <path>Show RBAC at a node
org policy member-add <fp> --role <role> --path <path>Add member at node
org policy member-remove <fp> --path <path>Remove member from node
org policy effective <fp> --path <path>Show merged effective permissions
org sync configure --path <path> --remote-url <url>Set git remote for node
vault create <name> --org <path>Create vault linked to org
vault attach <name> --org <path>Link existing vault to org
vault detach <name>Unlink vault from org

CI/CD Integration

Sigyn provides first-class CI/CD support so your pipelines can decrypt and use vault secrets without storing them in plaintext. This page covers the official integrations for GitHub Actions, GitLab CI/CD, and Bitbucket Pipelines, along with generic CI setup, security best practices, and troubleshooting.

Overview

The CI/CD workflow has three components:

  1. CI identity — a dedicated Sigyn identity for your pipeline (least-privilege, scoped to specific environments)
  2. CI bundle — a single base64 string containing the identity file, device key, and fingerprint
  3. Secret injection — the action (or manual steps) clones the vault, decrypts secrets, and makes them available to your workflow

Quick Start

1. Create a CI identity

Create a dedicated identity for your CI pipeline. Use a separate identity per repo or team — never reuse your personal identity.

# Create a CI-specific identity
sigyn identity create --name ci-bot

# Invite it to the vault with minimal permissions
sigyn delegation invite create --role reader --envs staging,prod

# Accept the invite on the CI identity, then generate the bundle
sigyn ci setup ci-bot

The sigyn ci setup command outputs a single SIGYN_CI_BUNDLE value. Use --json for machine-readable output.

2. Configure repository secrets

Add 3 secrets to your repository (Settings > Secrets and variables > Actions):

GitHub SecretSourceDescription
SIGYN_CI_BUNDLEOutput of sigyn ci setupBase64-encoded JSON containing identity file, device key, and fingerprint
SIGYN_PASSPHRASEYour CI identity’s passphraseUsed to decrypt the identity’s private key
VAULT_SSH_KEYSSH deploy key for the vault repoMust have read access to clone the vault repository

Tip: Use a dedicated SSH deploy key (not a personal key) with read-only access to the vault repository. Generate one with ssh-keygen -t ed25519 -C "sigyn-ci" and add it under the vault repo’s Settings > Deploy Keys.

3. Add the action to your workflow

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Load secrets
        uses: tonybenoy/sigyn/action@main
        with:
          bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
          passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
          vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
          vault-repo: [email protected]:myorg/sigyn-vaults.git
          vault: myapp
          environment: prod

      # All secrets are now available as environment variables
      - name: Deploy
        run: ./deploy.sh

GitHub Action Reference

Inputs

InputRequiredDefaultDescription
bundleYes—CI bundle from sigyn ci setup
passphraseYes—Identity passphrase
vault-ssh-keyYes—SSH private key for cloning the vault repo
vault-repoYes—Git URL for the vault repository (e.g., [email protected]:myorg/vaults.git)
vaultYes—Vault name
environmentYes—Environment name (e.g., dev, staging, prod)
exportNoenvExport mode: env, dotenv, json, or mask-only
dotenv-pathNo.envOutput path for dotenv or json export modes (must be a relative path)
keysNo"" (all)Comma-separated list of specific secret keys to export
maskNotrueMask all secret values in GitHub Actions logs
versionNolatestSigyn version to install

Outputs

OutputDescription
secrets-jsonCompact JSON of exported secrets (env/json modes only). Warning: accessible to all subsequent steps in the job — use key filtering to limit exposure.
countNumber of secrets exported

Export Modes

ModeBehavior
env (default)Writes secrets to $GITHUB_ENV — available as environment variables in all subsequent steps
dotenvWrites a .env file to the path specified by dotenv-path
jsonWrites a JSON file to the path specified by dotenv-path; also sets secrets-json output
mask-onlyMasks secret values in GitHub Actions logs but does not export them anywhere

What the Action Does

The composite action performs five steps:

  1. Install Sigyn — downloads and installs the Sigyn binary (prefers the install script bundled with the action ref; version controlled via the version input)
  2. Restore identity — decodes the CI bundle, validates the fingerprint format, writes the identity file and device key to $SIGYN_HOME
  3. Clone vault — validates the vault name, fetches SSH host keys for github.com/gitlab.com, uses the provided SSH key to clone (or pull) the vault repository with strict host key checking
  4. Export secrets — runs sigyn run export --format json, optionally filters by key, masks values in logs, and writes to the chosen destination
  5. Cleanup — removes identity files, device key, vault data, and SSH keys from the runner (runs even if previous steps fail)

Examples

Basic deployment

- name: Load secrets
  uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-repo: [email protected]:myorg/sigyn-vaults.git
    vault: myapp
    environment: prod

- name: Deploy
  run: |
    echo "Deploying with database at $DATABASE_URL"
    ./deploy.sh

Filtering specific keys

Export only the secrets your step needs:

- name: Load AWS credentials
  uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-repo: [email protected]:myorg/sigyn-vaults.git
    vault: infra
    environment: prod
    keys: "AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_DEFAULT_REGION"

- name: Login to ECR
  run: aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REGISTRY

Writing a .env file

Useful for frameworks that read from .env:

- name: Load secrets as dotenv
  uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-repo: [email protected]:myorg/sigyn-vaults.git
    vault: myapp
    environment: staging
    export: dotenv
    dotenv-path: .env.staging

- name: Run tests
  run: npm test

Using outputs in subsequent steps

- name: Load secrets
  id: secrets
  uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-repo: [email protected]:myorg/sigyn-vaults.git
    vault: myapp
    environment: prod

- name: Report
  run: echo "Loaded ${{ steps.secrets.outputs.count }} secrets"

Multiple vaults in one workflow

- name: Load shared infrastructure secrets
  uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-repo: [email protected]:myorg/sigyn-vaults.git
    vault: shared-infra
    environment: prod

- name: Load app-specific secrets
  uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-repo: [email protected]:myorg/sigyn-vaults.git
    vault: myapp
    environment: prod

When loading from multiple vaults, secrets from later steps override earlier ones if the keys overlap (since both write to $GITHUB_ENV).

Environment matrix

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        env: [dev, staging]
    steps:
      - uses: actions/checkout@v4

      - name: Load secrets for ${{ matrix.env }}
        uses: tonybenoy/sigyn/action@main
        with:
          bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
          passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
          vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
          vault-repo: [email protected]:myorg/sigyn-vaults.git
          vault: myapp
          environment: ${{ matrix.env }}

      - name: Run tests
        run: npm test

Other CI Platforms

GitLab CI

Sigyn provides an official GitLab CI/CD template that you can include directly in your .gitlab-ci.yml. It handles installation, identity restoration, vault cloning, secret export, and cleanup — the same five steps as the GitHub Action.

Quick start

include:
  - remote: 'https://raw.githubusercontent.com/tonybenoy/sigyn/main/action/gitlab-ci-template.yml'

deploy:
  extends: .sigyn-secrets
  variables:
    SIGYN_VAULT: myapp
    SIGYN_ENVIRONMENT: prod
    SIGYN_VAULT_REPO: [email protected]:myorg/sigyn-vaults.git
  script:
    - echo "Deploying with $DATABASE_URL"
    - ./deploy.sh

Configure CI/CD variables

Add these as masked and protected variables in Settings > CI/CD > Variables:

VariableDescription
SIGYN_CI_BUNDLEOutput of sigyn ci setup (base64-encoded JSON)
SIGYN_PASSPHRASECI identity passphrase
VAULT_SSH_KEYSSH private key for cloning the vault repo

Template variables

VariableRequiredDefaultDescription
SIGYN_VAULTYes—Vault name
SIGYN_ENVIRONMENTYes—Environment name (e.g. dev, staging, prod)
SIGYN_VAULT_REPOYes—Git URL for the vault repository
SIGYN_EXPORT_MODENodotenvExport mode: env, dotenv, or json
SIGYN_DOTENV_PATHNo.envOutput path for dotenv/json export
SIGYN_KEYSNo"" (all)Comma-separated list of specific keys to export
SIGYN_VERSIONNolatestSigyn version to install
SIGYN_INSTALL_URLNoGitHubOverride URL for install.sh (for self-hosted or mirrored setups)

Note: The template downloads install.sh and release binaries from GitHub by default. If your GitLab runners cannot reach github.com, set SIGYN_INSTALL_URL to a self-hosted mirror, or pre-install the sigyn binary in your CI image.

Export modes

ModeBehavior
envSources secrets into the current shell environment and creates a sigyn.env dotenv artifact so downstream jobs can consume them via needs:
dotenv (default)Writes a .env file to SIGYN_DOTENV_PATH
jsonWrites a JSON file to SIGYN_DOTENV_PATH

Passing secrets to downstream jobs

When using env mode, the template automatically creates a dotenv artifact report. Downstream jobs can consume the variables:

include:
  - remote: 'https://raw.githubusercontent.com/tonybenoy/sigyn/main/action/gitlab-ci-template.yml'

load-secrets:
  extends: .sigyn-secrets
  variables:
    SIGYN_VAULT: myapp
    SIGYN_ENVIRONMENT: prod
    SIGYN_VAULT_REPO: [email protected]:myorg/sigyn-vaults.git
    SIGYN_EXPORT_MODE: env
  script:
    - echo "Secrets loaded"

deploy:
  stage: deploy
  needs: [load-secrets]
  script:
    # DATABASE_URL and other secrets are available here
    - ./deploy.sh

GitLab environment matrix

include:
  - remote: 'https://raw.githubusercontent.com/tonybenoy/sigyn/main/action/gitlab-ci-template.yml'

.test-template:
  extends: .sigyn-secrets
  variables:
    SIGYN_VAULT: myapp
    SIGYN_VAULT_REPO: [email protected]:myorg/sigyn-vaults.git
  script:
    - npm test

test-staging:
  extends: .test-template
  variables:
    SIGYN_ENVIRONMENT: staging

test-prod:
  extends: .test-template
  variables:
    SIGYN_ENVIRONMENT: prod
  environment:
    name: production

What the template does

The .sigyn-secrets hidden job provides before_script and after_script blocks:

  1. Install Sigyn — downloads and installs the Sigyn binary
  2. Restore identity — decodes the CI bundle, validates the fingerprint, writes identity and device key
  3. Clone vault — validates the vault name, sets up SSH with strict host key checking, clones the vault
  4. Export secrets — runs sigyn run export --format json, optionally filters by key, writes to the chosen destination
  5. Cleanup (after_script) — removes identity files, device key, vault data, and SSH keys (runs even on failure)

Bitbucket Pipelines

Sigyn provides a Bitbucket Pipe that you can use directly in your bitbucket-pipelines.yml.

Quick start

pipelines:
  default:
    - step:
        name: Deploy
        script:
          - pipe: docker://tonybenoy/sigyn-pipe:latest
            variables:
              SIGYN_CI_BUNDLE: $SIGYN_CI_BUNDLE
              SIGYN_PASSPHRASE: $SIGYN_PASSPHRASE
              VAULT_SSH_KEY: $VAULT_SSH_KEY
              VAULT_REPO: "[email protected]:myorg/sigyn-vaults.git"
              VAULT: "myapp"
              ENVIRONMENT: "prod"
          - source .env  # load the exported secrets
          - ./deploy.sh

Alternatively, run the pipe script directly without Docker:

pipelines:
  default:
    - step:
        name: Deploy
        script:
          - curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/action/bitbucket-pipe/pipe.sh | bash
          - source .env
          - ./deploy.sh

Configure repository variables

Add these as secured variables in Repository Settings > Pipelines > Repository variables:

VariableDescription
SIGYN_CI_BUNDLEOutput of sigyn ci setup (base64-encoded JSON)
SIGYN_PASSPHRASECI identity passphrase
VAULT_SSH_KEYSSH private key for cloning the vault repo

Pipe variables

VariableRequiredDefaultDescription
VAULT_REPOYes—Git URL for the vault repository
VAULTYes—Vault name
ENVIRONMENTYes—Environment name (e.g. dev, staging, prod)
EXPORT_MODENodotenvExport mode: env, dotenv, or json
DOTENV_PATHNo.envOutput path for dotenv/json export
KEYSNo"" (all)Comma-separated list of specific keys to export
VERSIONNolatestSigyn version to install
SIGYN_INSTALL_URLNoGitHubOverride URL for install.sh (for self-hosted or mirrored setups)

Note: The pipe downloads install.sh and release binaries from GitHub by default. If your Bitbucket runners cannot reach github.com, set SIGYN_INSTALL_URL to a self-hosted mirror, or use a custom Docker image with sigyn pre-installed.

Examples

Filtering specific keys:

- pipe: docker://tonybenoy/sigyn-pipe:latest
  variables:
    SIGYN_CI_BUNDLE: $SIGYN_CI_BUNDLE
    SIGYN_PASSPHRASE: $SIGYN_PASSPHRASE
    VAULT_SSH_KEY: $VAULT_SSH_KEY
    VAULT_REPO: "[email protected]:myorg/sigyn-vaults.git"
    VAULT: "infra"
    ENVIRONMENT: "prod"
    KEYS: "AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY"

JSON export:

- pipe: docker://tonybenoy/sigyn-pipe:latest
  variables:
    SIGYN_CI_BUNDLE: $SIGYN_CI_BUNDLE
    SIGYN_PASSPHRASE: $SIGYN_PASSPHRASE
    VAULT_SSH_KEY: $VAULT_SSH_KEY
    VAULT_REPO: "[email protected]:myorg/sigyn-vaults.git"
    VAULT: "myapp"
    ENVIRONMENT: "staging"
    EXPORT_MODE: "json"
    DOTENV_PATH: "secrets.json"

Deployment environments:

pipelines:
  branches:
    staging:
      - step:
          name: Deploy to staging
          deployment: staging
          script:
            - pipe: docker://tonybenoy/sigyn-pipe:latest
              variables:
                SIGYN_CI_BUNDLE: $SIGYN_CI_BUNDLE
                SIGYN_PASSPHRASE: $SIGYN_PASSPHRASE
                VAULT_SSH_KEY: $VAULT_SSH_KEY
                VAULT_REPO: "[email protected]:myorg/sigyn-vaults.git"
                VAULT: "myapp"
                ENVIRONMENT: "staging"
            - source .env
            - ./deploy.sh
    main:
      - step:
          name: Deploy to production
          deployment: production
          script:
            - pipe: docker://tonybenoy/sigyn-pipe:latest
              variables:
                SIGYN_CI_BUNDLE: $SIGYN_CI_BUNDLE
                SIGYN_PASSPHRASE: $SIGYN_PASSPHRASE
                VAULT_SSH_KEY: $VAULT_SSH_KEY
                VAULT_REPO: "[email protected]:myorg/sigyn-vaults.git"
                VAULT: "myapp"
                ENVIRONMENT: "prod"
            - source .env
            - ./deploy.sh

Generic CI (CircleCI, Jenkins, etc.)

The manual setup follows the same pattern used by all the platform integrations:

  1. Install Sigyn: curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.sh | sh
  2. Decode the bundle and restore identity files
  3. Clone the vault repo
  4. Run sigyn run export or sigyn run exec to inject secrets
  5. Clean up sensitive files
#!/bin/bash
set -euo pipefail

# Install
curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.sh | sh
export PATH="$HOME/.sigyn/bin:$PATH"

# Restore identity from bundle
SIGYN_HOME="${SIGYN_HOME:-$HOME/.sigyn}"
mkdir -p "$SIGYN_HOME/identities"
BUNDLE_JSON=$(echo "$SIGYN_CI_BUNDLE" | base64 -d)
FINGERPRINT=$(echo "$BUNDLE_JSON" | jq -r '.fingerprint')
echo "$BUNDLE_JSON" | jq -r '.identity' | base64 -d > "$SIGYN_HOME/identities/${FINGERPRINT}.identity"
chmod 600 "$SIGYN_HOME/identities/${FINGERPRINT}.identity"
echo "$BUNDLE_JSON" | jq -r '.device_key' | base64 -d > "$SIGYN_HOME/.device_key"
chmod 400 "$SIGYN_HOME/.device_key"

# Clone vault
mkdir -p ~/.ssh
echo "$VAULT_SSH_KEY" > ~/.ssh/sigyn_vault_key
chmod 600 ~/.ssh/sigyn_vault_key
export GIT_SSH_COMMAND="ssh -i ~/.ssh/sigyn_vault_key -o StrictHostKeyChecking=yes"
# Add host keys for your git provider
ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null
git clone "$VAULT_REPO" "$SIGYN_HOME/vaults/$VAULT_NAME"
rm -f ~/.ssh/sigyn_vault_key

# Export secrets and run your command
export SIGYN_PASSPHRASE="$SIGYN_PASSPHRASE"
sigyn run exec --vault "$VAULT_NAME" --env "$ENVIRONMENT" -- your-command-here

# Cleanup
rm -rf "$SIGYN_HOME/identities" "$SIGYN_HOME/.device_key" "$SIGYN_HOME/vaults" 2>/dev/null || true

Security Best Practices

Built-in protections

All official integrations (GitHub Action, GitLab CI template, and Bitbucket Pipe) include the following security hardening measures:

  • Input validation — vault names and fingerprints are validated against strict patterns to prevent path traversal attacks
  • SSH host key verification — uses StrictHostKeyChecking=yes with pre-fetched host keys for github.com and gitlab.com (no TOFU)
  • Automatic cleanup — identity files, device keys, vault data, and SSH keys are removed from the runner after every run (even on failure)
  • Path restriction — dotenv-path must be a relative path with no .. components to prevent writing secrets to arbitrary locations
  • Stderr suppression — sigyn run export stderr is suppressed to prevent leaking values before masking takes effect

Pin to a specific version

Using @main or latest is convenient but means your pipeline tracks whatever is on the main branch. For production workflows, pin to a specific version:

# GitHub Actions — pin to a commit SHA
- uses: tonybenoy/sigyn/action@<commit-sha>

# GitLab CI — pin to a tagged version
include:
  - remote: 'https://raw.githubusercontent.com/tonybenoy/sigyn/<commit-sha>/action/gitlab-ci-template.yml'

# Bitbucket Pipelines — pin to a versioned Docker tag
- pipe: docker://tonybenoy/sigyn-pipe:1.0.0

This protects against supply chain attacks where the integration code could change between your runs.

Use a dedicated CI identity with minimal permissions

  • Create a separate identity for CI — never reuse a human’s personal identity
  • Grant the minimum role needed (usually reader)
  • Scope to specific environments (e.g., --envs staging,prod instead of all environments)
  • Consider separate identities per deployment target (one for staging, one for prod)
sigyn identity create --name ci-staging
sigyn delegation invite create --role reader --envs staging

sigyn identity create --name ci-prod
sigyn delegation invite create --role reader --envs prod

Use read-only deploy keys

The vault-ssh-key should be a deploy key with read-only access to the vault repository. Never use a personal SSH key or a key with write access unless your pipeline needs to push audit entries.

Rotate CI credentials

Periodically rotate your CI identity and bundle:

# Revoke the old CI identity
sigyn delegation revoke <old-ci-fingerprint>

# Create a fresh one
sigyn identity create --name ci-bot-v2
sigyn delegation invite create --role reader --envs staging,prod
sigyn ci setup ci-bot-v2

# Update repository secrets with the new bundle and passphrase

Mask secrets in logs

The action masks secret values by default (mask: true). Keep this enabled — it prevents accidental exposure in build logs. Note that GitHub Actions masking has limitations:

  • Values shorter than 4 characters are not masked by GitHub
  • Multiline values are masked line by line — each line is registered as a separate mask
  • Structured output (JSON, YAML) may partially leak key names even when values are masked

Limit secret scope with key filtering

Only export the secrets each step actually needs:

# Only load database credentials for the migration step
- name: Load DB secrets
  uses: tonybenoy/sigyn/action@main
  with:
    # ...
    keys: "DATABASE_URL,DATABASE_POOL_SIZE"

- name: Run migrations
  run: ./migrate.sh

Use GitHub Environments for approval gates

Combine Sigyn environments with GitHub Environments for manual approval before production deployments:

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment: production  # requires manual approval
    steps:
      - uses: tonybenoy/sigyn/action@main
        with:
          # ...
          environment: prod

Troubleshooting

“Permission denied (publickey)” when cloning vault

The SSH key cannot access the vault repository.

  • Verify the VAULT_SSH_KEY secret contains the private key (it should have the standard OpenSSH PEM header)
  • Ensure the corresponding public key is added as a deploy key on the vault repo
  • Check that the deploy key has read access to the correct repository
  • The key must not have a passphrase — generate with ssh-keygen -t ed25519 -N ""

“Failed to decrypt identity” or “Invalid passphrase”

  • Verify SIGYN_PASSPHRASE matches the passphrase set when the CI identity was created
  • Ensure SIGYN_CI_BUNDLE hasn’t been truncated — GitHub has a 48KB limit per secret
  • Regenerate the bundle with sigyn ci setup <identity-name> if the identity or device key has changed

“No such vault” or “Vault not found”

  • The vault input must match the vault directory name inside the cloned repository
  • Check that the vault repo URL is correct and the vault exists at the expected path
  • If using a monorepo with multiple vaults, ensure the vault name matches the subdirectory

“Access denied” for an environment

  • The CI identity’s delegation must include the requested environment
  • Check with sigyn delegation list that the identity has access to the environment
  • If the delegation was created with --envs staging, the identity cannot access prod

Secrets not available in subsequent steps

  • GitHub Actions: env mode writes to $GITHUB_ENV, which is only available in subsequent steps, not the same step
  • GitLab CI: env mode uses dotenv artifact reports — downstream jobs must declare needs: to receive the variables
  • Bitbucket Pipelines: Use source .env after the pipe step to load secrets into the current shell
  • Verify the export step completed successfully (check for errors in the logs)
  • Check the count output — if it’s 0, the environment may be empty or the key filter matched nothing

Bundle encoding issues

The bundle is a base64-encoded JSON string. Common issues:

  • Copy/paste errors: Use sigyn ci setup --json and pipe directly: sigyn ci setup ci-bot | pbcopy
  • Line breaks: GitHub Secrets strip trailing newlines but preserve internal ones. The bundle should be a single line.
  • Re-encoding: Do not base64-encode the bundle again when pasting into GitHub Secrets — it’s already encoded

Action version / install failures

  • If version: latest fails to download, the install script may be unreachable. Pin a specific version as a fallback.
  • All integrations require curl, jq, and git — pre-installed on most CI runner images
  • Self-hosted runners or minimal Docker images may need these dependencies installed manually

Example Usage & Recipes

This page provides concrete examples for common Sigyn workflows.

Quick Shortcuts

Using Short Aliases

Every major command has a short alias for fast typing:

# These pairs are equivalent:
sigyn secret list --env dev     →  sigyn s list -e dev
sigyn vault list                →  sigyn v list
sigyn env diff dev staging      →  sigyn e diff dev staging
sigyn audit tail                →  sigyn a tail
sigyn sync push                 →  sigyn sy push
sigyn run -e dev -- ./app       →  sigyn r -e dev -- ./app

Top-Level get/set

Skip the secret subcommand for the most common operations:

# Instead of: sigyn secret get DATABASE_URL -e dev
sigyn get DATABASE_URL -e dev

# Instead of: sigyn secret set API_KEY 'new-key' -e prod
sigyn set API_KEY 'new-key' -e prod

# Copy a secret to clipboard
sigyn get API_KEY -e prod --copy

Quick Listing with ls

sigyn ls                  # list secrets in default env
sigyn ls prod             # list secrets in prod (prefix matching)
sigyn ls --vaults         # list all vaults
sigyn ls --envs           # list all environments
sigyn ls dev --reveal     # show secret values

Environment Prefix Matching

Type just enough of the environment name to be unambiguous:

sigyn get API_KEY -e d        # resolves to 'dev'
sigyn get API_KEY -e p        # resolves to 'prod'
sigyn get API_KEY -e st       # resolves to 'staging'
sigyn run -e p -- ./deploy    # run with prod secrets

Context for Repeated Operations

Set a persistent context to avoid typing --vault and --env repeatedly:

sigyn context set myapp prod
sigyn get DATABASE_URL        # uses myapp vault, prod env
sigyn ls                      # lists prod secrets
sigyn run -- ./deploy.sh      # runs with prod secrets

sigyn context clear           # go back to defaults

Inline Secret Refs in Commands

Reference secrets directly in command arguments with {{KEY}}:

sigyn run -e prod -- curl -H "Authorization: Bearer {{API_KEY}}" https://api.example.com
sigyn run -e dev -- psql "{{DATABASE_URL}}"

Watch Mode for Development

Automatically restart your app when secrets change:

sigyn watch -e dev -- npm run dev
sigyn watch -e dev --interval 5 -- cargo run

Useful during development when a teammate updates shared secrets — your local server restarts with the new values automatically.

Working with AI Coding Agents

AI coding assistants (Claude Code, GitHub Copilot, Cursor, Windsurf, etc.) run in your terminal and can read environment variables, .env files, and process listings. Sigyn keeps secrets invisible to them.

The Problem

# DON'T DO THIS — any AI agent in your terminal can read these:
export DATABASE_URL="postgres://prod:[email protected]/myapp"
echo "API_KEY=sk-live-abc123" >> .env
source .env

AI agents can see secrets via env, printenv, cat .env, or /proc/self/environ. Even if the agent doesn’t deliberately exfiltrate them, secrets may appear in conversation logs, tool outputs, or error messages.

Run your app with injected secrets in one terminal, your AI agent in another:

# Terminal 1 — app runs with secrets, isolated from your shell
sigyn run -e dev -- npm run dev

# Terminal 2 — AI agent has no access to the secrets
claude   # or cursor, copilot, etc.

Secrets are injected only into the child process (npm run dev). Your shell — where the AI agent runs — never sees them.

Solution 2: Clean Environment

Use --clean to strip the entire parent environment, injecting only vault secrets:

sigyn run --clean -e dev -- ./myapp

This prevents even inherited environment variables (like AWS_SECRET_ACCESS_KEY from a previous aws configure) from being visible.

Solution 3: Unix Socket (Maximum Security)

For zero-exposure secret access, serve secrets over a Unix socket:

# Start the socket server (owner-only permissions, 0600)
sigyn run serve -e prod --socket /tmp/sigyn.sock &

# Your app reads secrets from the socket at runtime
# No environment variables, no files, no process listing exposure

Solution 4: Named Commands in .sigyn.toml

Set up your project so developers never need to type secrets or export them:

# .sigyn.toml
[project]
vault = "myapp"
env = "dev"

[commands]
dev = "npm run dev"
test = "npm test"
migrate = "npx prisma migrate deploy"
sigyn run dev     # secrets injected, never in your shell
sigyn run test    # same — AI agent can't see them

Quick Comparison

MethodSecrets in env?Secrets on disk?Agent can read?
.env + sourceYesYesYes
export KEY=valYesNoYes
sigyn run execChild onlyNoNo
sigyn run --cleanChild onlyNoNo
sigyn run serveNeverNoNo

Letting AI Agents Run and Debug Your App

The previous section covers keeping secrets away from agents entirely. But what if you need the agent to actually run your app (which needs secrets) for debugging?

Strategy 1: Dev Environment with Non-Sensitive Credentials

The simplest approach — use local/dummy credentials in dev that aren’t actually secrets:

# These dev values aren't sensitive — it's fine if the agent sees them
sigyn secret set DATABASE_URL "postgres://localhost/myapp_dev" -e dev
sigyn secret set REDIS_URL "redis://localhost:6379" -e dev
sigyn secret set API_KEY "sk-test-not-a-real-key" -e dev

Configure .sigyn.toml so the agent just runs commands:

[project]
vault = "myapp"
env = "dev"

[commands]
dev = "npm run dev"
test = "npm test"

The agent can run sigyn run dev, see the app output, debug errors — and the dev credentials aren’t worth protecting because they only work against localhost.

Keep real credentials in staging/prod environments only. The agent works in dev where exposure doesn’t matter.

Strategy 2: Sigyn Run with Output Isolation

Let the agent invoke sigyn run but prevent it from reading individual secret values:

# Agent CAN do this (runs the app, sees stdout/stderr):
sigyn run -e dev -- npm run dev

# Agent CANNOT do this (requires interactive passphrase):
sigyn secret get DATABASE_URL -e dev
# ^ Blocked because the agent doesn't have your passphrase

As long as you don’t cache your passphrase in the agent’s terminal session (don’t run eval $(sigyn agent start) there), the agent can only run pre-configured commands — not extract individual secrets.

Strategy 3: Named Commands as an Allowlist

Define exactly which commands the agent is allowed to run:

# .sigyn.toml
[project]
vault = "myapp"
env = "dev"

[commands]
dev = "npm run dev"
test = "npm test"
lint = "npm run lint"
migrate = "npx prisma migrate dev"
debug = "node --inspect ./src/index.js"

The agent can run sigyn run dev, sigyn run test, sigyn run debug — but can’t invent new commands that extract secrets. Combined with not having the passphrase cached, this acts as an effective allowlist.

Strategy 4: Socket Server for Running Apps

Start the socket server yourself, then let the agent restart the app as needed:

# You start the socket server (once, in your terminal):
sigyn run serve -e dev --socket /tmp/sigyn-dev.sock &

# Your app reads secrets from the socket at runtime.
# The agent can restart the app, read logs, debug — but
# the secret values only flow through the socket, never
# through environment variables the agent could inspect.

This requires your app to support reading from a Unix socket (via SIGYN_SOCK env var), but provides the strongest isolation.

What to Watch Out For

Even with these strategies, secrets can leak through:

  • Application logs — if your app logs Connected to postgres://user:password@host, the agent sees it in stdout. Configure your app to redact credentials from logs.
  • Error messages — database connection errors often include the connection string. Use structured error handling that strips credentials.
  • Core dumps / stack traces — these can contain in-memory secrets. Disable core dumps in dev.

For most teams, Strategy 1 + Strategy 2 is the sweet spot:

# 1. Use non-sensitive dev credentials
sigyn secret set DATABASE_URL "postgres://localhost/myapp_dev" -e dev

# 2. Keep real secrets in staging/prod only
sigyn secret set DATABASE_URL "postgres://prod:[email protected]/myapp" -e prod

# 3. Agent works in dev — can run and debug freely
# .sigyn.toml points to dev by default
# Agent runs: sigyn run dev

# 4. You deploy to prod yourself — agent never touches it
sigyn run -e prod -- ./deploy.sh

CI/CD Integration

For full CI/CD documentation including the GitHub Action reference, all export modes, security best practices, and troubleshooting, see the dedicated CI/CD Integration page.

Quick example:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Load secrets
        uses: tonybenoy/sigyn/action@main
        with:
          bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
          passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
          vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
          vault-repo: [email protected]:myorg/sigyn-vaults.git
          vault: myapp
          environment: prod

      - name: Deploy
        run: ./deploy.sh

Advanced Process Injection

Using with Docker

Injecting secrets into a Docker container without ever writing them to a .env file:

sigyn run exec --env prod -- docker run --env-file <(sigyn run export --format docker) my-app:latest

Or more simply if you want all Sigyn secrets to be env vars in the container:

sigyn run exec --env prod -- docker run my-app:latest

Shell Alias for Development

Add this to your .bashrc or .zshrc to always run your local dev server with Sigyn secrets:

alias dev='sigyn run -e dev -- npm run dev'

Or better yet, use a .sigyn.toml project config with named commands:

# .sigyn.toml
[project]
vault = "myapp"
env = "dev"

[commands]
dev = "npm run dev"

Then just run sigyn run dev – no alias needed.

Team Onboarding and Offboarding

Onboarding a New Team Member

Step 1 — New member creates their identity:

# Bob runs this on his machine
sigyn identity create --name bob --email [email protected]
sigyn identity export --name bob > bob.pub
# Send bob.pub to the team lead (Alice) via a secure channel

Step 2 — Team lead creates a scoped invitation:

# Alice imports Bob's public key and creates an invitation
sigyn identity import bob.pub

sigyn delegation invite create \
  --role contributor \
  --envs dev,staging \
  --patterns '*'
# Produces invitation-<id>.json — send it to Bob

Step 3 — New member clones the vault, accepts, and verifies access:

# Bob clones and accepts in one step
sigyn vault clone [email protected]:team/secrets.git --invitation ./invitation-abc123.json

# Or separately:
# sigyn vault clone [email protected]:team/secrets.git
# sigyn delegation invite accept ./invitation-abc123.json

# Pull the vault to get encrypted secrets
sigyn sync pull

# Verify access works
sigyn policy check $(sigyn identity show bob --json | jq -r .fingerprint) read --env dev
#   Result: ALLOW

sigyn policy check $(sigyn identity show bob --json | jq -r .fingerprint) write --env prod
#   Result: DENY (not in allowed envs)

Offboarding a Team Member

When someone leaves the team, revoke their access and clean up the delegation tree.

Step 1 — Inspect the delegation tree to understand impact:

sigyn delegation tree
# alice (a1b2c3d4...) [owner]
#   bob (e5f6a7b8...) [contributor]
#     carol (c9d0e1f2...) [readonly]    <-- Bob invited Carol
#   dave (a3b4c5d6...) [admin]

Step 2 — Revoke with cascade (removes Bob and everyone Bob invited):

sigyn delegation revoke e5f6a7b8... --cascade
# Revoked: bob (e5f6a7b8...), carol (c9d0e1f2...)
# Master key rotated.

Step 3 — Push changes and rotate high-value secrets:

sigyn sync push

# Rotate any secrets Bob had access to
sigyn rotate key DB_PASSWORD --env dev
sigyn rotate key DB_PASSWORD --env staging
sigyn rotate key API_KEY --env dev
sigyn rotate key API_KEY --env staging

If Carol should keep access, re-invite her directly before revoking Bob, or use sigyn delegation revoke e5f6a7b8... (without --cascade) and manually reassign Carol’s delegation.

Contractor and Temporary Access

Granting Time-Limited Access

Create a time-limited invitation with scoped permissions for a contractor:

sigyn delegation invite create \
  --role contributor \
  --envs dev \
  --patterns "STRIPE_*,PAYMENT_*" \
  --expires 90d
# Produces invitation-<id>.json — send to the contractor

The invitation (and resulting access) expires automatically after 90 days.

Verifying Contractor Constraints

After the contractor accepts, verify what they can and cannot do:

# Allowed: read payment secrets in dev
sigyn policy check <contractor-fp> read --env dev --key STRIPE_SECRET_KEY
#   Result: ALLOW

# Denied: access production
sigyn policy check <contractor-fp> read --env prod --key STRIPE_SECRET_KEY
#   Result: DENY (env not allowed)

# Denied: access secrets outside their pattern
sigyn policy check <contractor-fp> read --env dev --key DATABASE_URL
#   Result: DENY (key does not match allowed patterns)

Cleanup

When the contract ends, either wait for auto-expiry or revoke explicitly:

sigyn delegation revoke <contractor-fp>
sigyn sync push

Complex Policies

Restricting a Member to Specific Keys

Add a member who can only see database-related secrets in the staging environment:

sigyn policy member-add <fingerprint>
  --role readonly
  --envs staging
  --patterns "DB_*,POSTGRES_*"

Implementing a “Break Glass” Auditor

An auditor who can see everything but only in an emergency (monitored via audit logs):

sigyn policy member-add <fingerprint>
  --role auditor
  --envs '*'
  --patterns '*'

MFA Enforcement

Requiring MFA for All Members

Enable MFA globally by setting require_mfa: true in the vault’s global constraints. Every non-owner member will be prompted for a TOTP code on their first access (then a 1-hour session grace period kicks in).

First, have each team member enroll:

sigyn mfa setup -i alice

Then set the policy constraint (as vault owner):

# In the vault policy, set global_constraints.require_mfa = true
sigyn policy show --vault myapp   # verify current policy

MFA for Specific Members

You can also require MFA only for certain members by setting require_mfa: true on their individual member constraints rather than globally. This is useful for restricting high-privilege roles while leaving read-only members unaffected.

Generating New Backup Codes

If a team member loses their backup codes, they can generate a fresh set (the old codes are immediately invalidated):

sigyn mfa backup -i alice

Secret Rotation

Manual Rotation of a Database Password

  1. Rotate the secret in Sigyn:
    sigyn rotate key DB_PASSWORD --env prod
    
  2. Export the new value to update your cloud provider (e.g., AWS RDS):
    NEW_PASS=$(sigyn secret get DB_PASSWORD --env prod)
    aws rds modify-db-instance --db-instance-identifier mydb --master-user-password "$NEW_PASS"
    

Rotation Schedules with Hooks

Rotation hooks are executed directly (no shell) after Sigyn rotates a secret. The new secret value is passed via stdin, not as an environment variable. This prevents the secret from being visible in /proc/<pid>/environ. The environment name is available as $SIGYN_ENV.

Hook commands are validated when saved to policy: shell metacharacters (;, |, &, $, `, \, >, <, (, )) are rejected, and the command name must not contain path traversal (..). Maximum command length is 512 characters.

Automated Database Password Rotation

Create a hook script that reads the new secret from stdin and updates AWS RDS:

#!/usr/bin/env bash
# hooks/rotate-db.sh — called after sigyn rotates DB_PASSWORD
# The new secret value is passed via stdin.
set -euo pipefail

NEW_PASS=$(cat)   # read new secret from stdin
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --master-user-password "$NEW_PASS"

echo "RDS password updated successfully"

Set a weekly rotation schedule with the hook:

sigyn rotate schedule set -v myapp \
  --key DB_PASSWORD \
  --cron "0 0 * * MON" \
  --grace-hours 48 \
  --hooks hooks/rotate-db.sh

Verify the schedule is active:

sigyn rotate schedule list -v myapp
# KEY            CRON             GRACE  HOOKS              NEXT RUN
# DB_PASSWORD    0 0 * * MON     48h    rotate-db.sh       2026-03-02 00:00 UTC

Test it manually to make sure the hook works end-to-end:

sigyn rotate key DB_PASSWORD --env prod

Monthly Redis Auth Token Rotation

#!/usr/bin/env bash
# hooks/rotate-redis.sh
# The new secret value is passed via stdin.
set -euo pipefail

NEW_TOKEN=$(cat)   # read new secret from stdin
redis-cli -h redis.internal CONFIG SET requirepass "$NEW_TOKEN"
echo "Redis auth token updated"
sigyn rotate schedule set -v myapp \
  --key REDIS_AUTH_TOKEN \
  --cron "0 2 1 * *" \
  --grace-hours 24 \
  --hooks hooks/rotate-redis.sh

First-Time Setup

Using the Onboard Wizard

The fastest way to get started is the guided wizard:

sigyn onboard

This walks you through:

  1. Creating an identity (if none exists)
  2. Creating a vault (auto-detects project name)
  3. Importing .env files found in the current directory
  4. Setting up .sigyn.toml

In CI or non-interactive environments, it prints a checklist instead:

sigyn onboard
# Sigyn Setup Checklist
# ─────────────────────────────────
#   ✓ Identity created
#   ✗ Vault created
#     → sigyn vault create <name>
#   ✗ Project config (.sigyn.toml)
#     → sigyn project init

Smarter Init

sigyn init now offers to create missing identity and vault interactively:

sigyn init
# No identities found. Create one now? [Y/n]
# Identity name: alice
# ...
# No vaults found. Create one now? [Y/n]
# Vault name: myapp (detected from Cargo.toml)
# ...
# ✓ Configuration initialized

Environment Comparison and Cloning

Comparing Environments

Before promoting, check what differs between environments:

# Summary of differences (values hidden)
sigyn env diff dev staging
#   - OLD_DEBUG_KEY (removed)
#   + NEW_STAGING_KEY (added)
#   ~ DATABASE_URL (changed)

# See actual values
sigyn env diff dev staging --reveal

# Machine-readable output
sigyn env diff dev prod --json

Cloning an Environment

Quickly create a copy of an environment for testing:

sigyn env clone dev qa
# ✓ Cloned 'dev' → 'qa' (12 secrets)

# Now customize the QA-specific values
sigyn secret set DATABASE_URL "postgres://qa-db/myapp" --env qa

Editing Secrets in Your Editor

Open all secrets in an environment for bulk editing:

sigyn secret edit --env dev
# Opens $EDITOR with:
#   DATABASE_URL=postgres://localhost/myapp
#   API_KEY=sk-abc123
#   FEATURE_FLAG=true
#
# Make changes, save, close. Sigyn shows a diff and confirms.

Searching Across Environments

Find secrets matching a pattern across all environments in a vault:

# Find all database-related secrets
sigyn secret search 'DB_*'
#   [dev] DB_HOST = ••••••••
#   [dev] DB_PORT = ••••••••
#   [staging] DB_HOST = ••••••••
#   [prod] DB_HOST = ••••••••

# Show actual values
sigyn secret search 'API_*' --reveal

Passphrase Agent (Session Caching)

Avoiding Repeated Passphrase Prompts

When running multiple commands in a session, use the passphrase agent to cache your decrypted key in memory — similar to ssh-agent or gpg-agent.

# Start the agent (prints a shell-eval-able export)
eval $(sigyn agent start)

# First command prompts for your passphrase — then caches it
sigyn secret list -v myapp -e dev

# Subsequent commands skip the passphrase prompt entirely
sigyn secret set API_KEY="new-key" -e dev
sigyn vault create new-project -i alice
sigyn delegation tree

Custom Timeout

By default, cached keys expire after 30 minutes. Adjust with --timeout:

# Cache keys for 2 hours
eval $(sigyn agent start --timeout 120)

# Cache keys for 5 minutes (for quick scripted workflows)
eval $(sigyn agent start --timeout 5)

Locking and Stopping

# Clear cached keys but keep daemon running (next command re-prompts)
sigyn agent lock

# Check agent status
sigyn agent status

# Stop the daemon entirely and zeroize all keys
sigyn agent stop

Agent + Batch Operations

The agent is especially useful when combined with batch commands in a multi-step workflow:

eval $(sigyn agent start)

# All of these run without re-prompting for the passphrase
sigyn vault create api worker web --org acme
sigyn env create dev staging prod
sigyn secret set DB_URL="postgres://localhost" REDIS="redis://localhost" -e dev
sigyn secret import .env.production -e prod --force
sigyn delegation revoke abc123 def456 --cascade
sigyn sync push

Shell Profile Integration

Add this to your .bashrc or .zshrc to auto-start the agent on login:

if [ -z "$SIGYN_AGENT_SOCK" ]; then
  eval $(sigyn agent start --timeout 60)
fi

Webhook Notifications

Setting Up Notifications

Configure a webhook to get notified when secrets change. Webhook URLs must use HTTPS (HTTP is only allowed for localhost during development):

sigyn notification configure
# Webhook URL: https://hooks.slack.com/services/T.../B.../xxx
# Select events: * (all events)
# Shared secret: (optional — used to compute HMAC signature)
# ✓ Webhook configured

When a shared secret is configured, each webhook POST includes an X-Sigyn-Signature header containing an HMAC (blake3 keyed hash) of the JSON payload body. Verify this on the receiving end to authenticate that the request came from Sigyn.

Testing Notifications

Send a test event to verify your webhook works:

sigyn notification test
#   ✓ https://hooks.slack.com/services/T.../B.../xxx
# ✓ Test sent to 1 webhook(s)

Listing Configured Webhooks

sigyn notification list
# Configured Webhooks
# ────────────────────────────────────────────────────────────
#   1. https://hooks.slack.com/services/T.../B.../xxx
#      Events: *

Auto-Sync

Enable auto-sync to automatically push changes after every write:

sigyn sync configure --auto-sync true

# Now every secret set/remove/generate automatically pushes
sigyn secret set API_KEY "new-value" --env dev
# ✓ Set 'API_KEY' in env 'dev'
# note: auto-syncing...

Environment Promotion Workflow

Promoting Secrets Through the Pipeline

A typical workflow: set secrets in dev, then promote through staging to production.

Step 1 — Set secrets in dev:

# Batch set — all at once
sigyn secret set API_KEY="dev-key-abc123" DATABASE_URL="postgres://localhost/myapp_dev" FEATURE_FLAG="true" --env dev

Step 2 — Promote all secrets from dev to staging:

# Preview what will change first
sigyn env promote --from dev --to staging --dry-run
# Would promote: API_KEY, DATABASE_URL, FEATURE_FLAG
# Would overwrite: (none)

# Apply the promotion
sigyn env promote --from dev --to staging

Step 3 — Override staging-specific values:

sigyn secret set DATABASE_URL "postgres://staging-db.internal/myapp" --env staging

Step 4 — Selectively promote to production:

# Only promote specific keys (not DATABASE_URL, which differs per environment)
sigyn env promote --from staging --to prod --keys API_KEY,FEATURE_FLAG

# Production DATABASE_URL is managed separately
sigyn secret set DATABASE_URL "postgres://prod-db.internal/myapp" --env prod

Adding a QA Environment

# Option 1: Clone dev (copies all secrets in one step)
sigyn env clone dev qa

# Option 2: Create empty and promote selectively
sigyn env create qa
sigyn env promote --from dev --to qa

# Either way, QA team can now work with a snapshot of dev secrets

Multi-Project Organization

Scenario

You have one organization with multiple projects — say 3 backend services and 1 frontend app — each with their own environments and secrets. You want centralized access control, minimal GitHub repos for sync, and per-project .sigyn.toml configs so developers just cd into a repo and go.

1. Set up the org hierarchy

sigyn org create acme
sigyn org node create backend  --parent acme --type division
sigyn org node create frontend --parent acme --type division

This gives you:

acme (org)
├── backend (division)
└── frontend (division)

2. Create a vault per project, linked to the org

# Batch create — multiple vaults in one command
sigyn vault create api-service auth-service worker-service --org acme/backend
sigyn vault create dashboard --org acme/frontend

Each vault has independent environments (dev, staging, prod), secrets, and member lists — but inherits org-level policies and git remote configuration.

3. Configure git sync once at the org level

sigyn org sync configure --path acme \
  --remote-url [email protected]:acme-corp/secrets.git

All 4 vaults inherit this single remote. If the frontend team later needs a separate repo, override at the child level:

sigyn org sync configure --path acme/frontend \
  --remote-url [email protected]:acme-corp/frontend-secrets.git

Now acme/backend still inherits the org remote, while acme/frontend uses its own.

4. Add a .sigyn.toml to each project repo

In api-service/:

[project]
vault = "api-service"
env = "dev"
identity = "alice"

[commands]
dev = "cargo run"
migrate = "sqlx migrate run"

In dashboard/:

[project]
vault = "dashboard"
env = "dev"
identity = "alice"

[commands]
dev = "npm run dev"
build = "npm run build"

5. Day-to-day usage

# In api-service/ — secrets come from the api-service vault, dev env
sigyn secret set DATABASE_URL "postgres://localhost/api"
sigyn run dev

# In dashboard/ — secrets come from the dashboard vault, dev env
sigyn secret set VITE_API_URL "http://localhost:8080"
sigyn run dev

# Switch to staging for a deploy
sigyn run exec --env staging -- ./deploy.sh

6. Team access control

Add an org-wide admin who can access all vaults:

sigyn org policy member-add <bob-fp> --role admin --path acme

Add a frontend contractor who can only access dashboard secrets in dev:

sigyn org policy member-add <carol-fp> --role contributor \
  --path acme/frontend --envs dev

Check effective permissions:

sigyn org policy effective <carol-fp> --path acme/frontend
#   Role: Contributor
#   Envs: dev
#   Patterns: *

7. Sync everything

sigyn sync push --vault api-service
sigyn sync push --vault auth-service
sigyn sync push --vault worker-service
sigyn sync push --vault dashboard

Why this works

  • One or two GitHub repos instead of one per vault — git remote inheritance means you set it once at the org level.
  • Per-project isolation — each vault has its own master key, so compromising one doesn’t affect the others.
  • Per-project .sigyn.toml — developers just cd into a repo and sigyn run dev picks up the right vault, env, and identity automatically.
  • Cascading RBAC — add someone at acme and they get access everywhere; add them at acme/frontend and they only see frontend vaults.
  • Independent environments — api-service can be on staging while dashboard is still on dev.

Monorepo with Shared Secrets

Scenario

You have a monorepo with 3 services (API, worker, web frontend) that share some infrastructure secrets (database URL, Redis URL) but also have service-specific secrets (API keys, signing tokens). You want each service to see only what it needs while avoiding duplication of shared values.

1. Create the vaults

sigyn vault create shared-infra api-service worker-service web-frontend

2. Populate shared and per-service secrets

# Batch set — multiple KEY=VALUE pairs per command
sigyn secret set DATABASE_URL="postgres://db.internal/myapp" REDIS_URL="redis://redis.internal:6379" -v shared-infra -e prod

sigyn secret set STRIPE_SECRET_KEY="sk_live_..." JWT_SECRET="..." -v api-service -e prod

sigyn secret set QUEUE_URL="https://sqs.us-east-1.amazonaws.com/..." DEAD_LETTER_ARN="arn:aws:sqs:..." -v worker-service -e prod

sigyn secret set NEXT_PUBLIC_API_URL="https://api.example.com" -v web-frontend -e prod

# Or import from a .env file
sigyn secret import .env.production -v api-service -e prod --force

3. Per-directory .sigyn.toml files

In services/api/.sigyn.toml:

[project]
vault = "api-service"
env = "dev"

[commands]
dev = "cargo run"

In services/worker/.sigyn.toml:

[project]
vault = "worker-service"
env = "dev"

[commands]
dev = "cargo run"

In services/web/.sigyn.toml:

[project]
vault = "web-frontend"
env = "dev"

[commands]
dev = "npm run dev"

4. Compose shared + service secrets at runtime

Use sigyn run export to merge secrets from multiple vaults:

# In services/api/ — load shared infra secrets, then layer on API-specific ones
eval $(sigyn run export -v shared-infra --env prod --format shell)
exec sigyn run exec --env prod -- ./api-server

Or in a wrapper script (services/api/start.sh):

#!/usr/bin/env bash
set -euo pipefail

# Export shared secrets into the environment
eval $(sigyn run export -v shared-infra --env "${SIGYN_ENV:-dev}" --format shell)

# Run with service-specific secrets layered on top
sigyn run exec --env "${SIGYN_ENV:-dev}" -- "$@"
# Usage
cd services/api && ./start.sh cargo run

Emergency Breach Response

Full Breach — All Secrets Compromised

If you suspect a broad compromise (e.g., vault key leaked), use breach mode to rotate everything and revoke all delegated members in one step.

Step 1 — Activate breach mode:

sigyn rotate breach-mode --force
# All secrets rotated across all environments.
# All delegated members revoked.
# Master key rotated.

Step 2 — Push the rotated vault:

sigyn sync push

Step 3 — Verify the delegation tree is clean:

sigyn delegation tree
# alice (a1b2c3d4...) [owner]
#   (no delegated members)

Step 4 — Update external systems with new secret values:

# Update RDS
NEW_DB_PASS=$(sigyn secret get DB_PASSWORD --env prod)
aws rds modify-db-instance --db-instance-identifier mydb --master-user-password "$NEW_DB_PASS"

# Update Redis
NEW_REDIS_TOKEN=$(sigyn secret get REDIS_AUTH_TOKEN --env prod)
redis-cli -h redis.internal CONFIG SET requirepass "$NEW_REDIS_TOKEN"

# Repeat for all external integrations...

Step 5 — Re-onboard trusted team members:

# Create new invitations for each trusted member
sigyn delegation invite create --role admin --envs '*'
sigyn delegation invite create --role contributor --envs dev,staging
# Send invitations via a verified secure channel

Step 6 — Audit, verify, and witness:

# Export the audit log for incident documentation
sigyn audit export --output incident-audit.json --format json

# Verify the audit chain is intact
sigyn audit verify

# Have a second team member countersign the recovery
sigyn audit witness

Targeted Response — Single Member Compromise

If only one member’s key was compromised (e.g., stolen laptop), you can scope the response to just that member:

# Revoke the compromised member and their delegates
sigyn delegation revoke <compromised-fp> --cascade
sigyn sync push

# Rotate only the secrets that member had access to
sigyn rotate key DB_PASSWORD --env dev
sigyn rotate key DB_PASSWORD --env staging
sigyn rotate key API_KEY --env dev

# Monitor for suspicious activity
sigyn audit tail -n 100
sigyn audit query --actor <compromised-fp>

Disaster Recovery

Creating Paper Backups

  1. Create 5 shards, requiring 3 to recover:
    sigyn-recovery split --identity alice --threshold 3 --total 5 --output ./shards
    
  2. Print them (or generate QR codes) to store in physical safes:
    sigyn-recovery print-shards ./shards/*.json
    

Recovering a Vault

If Alice loses her laptop, she can reconstruct her identity on a new machine:

sigyn-recovery restore shard1.json shard2.json shard3.json --output recovered_identity.toml
sigyn identity import recovered_identity.toml

Auditing and Compliance

Verifying Audit Chain Integrity

Before any compliance review, verify the hash chain has not been tampered with:

sigyn audit verify
# Audit chain OK: 1,247 entries verified, no breaks detected.

Querying Access Patterns

Search the audit log for specific access patterns:

# All production access events
sigyn audit query --env prod

# All actions by a specific member
sigyn audit query --actor a1b2c3d4...

# Combine filters
sigyn audit query --actor a1b2c3d4... --env prod

Exporting for Compliance

Export the full audit trail for external compliance tools or auditors:

# JSON for programmatic analysis
sigyn audit export --output audit-q1-2026.json --format json

# CSV for spreadsheet review
sigyn audit export --output audit-q1-2026.csv --format csv

Witness Countersigning

After sensitive operations (rotation, member changes, breach response), have a second team member countersign the audit entry:

# After a rotation or breach response, the second admin runs:
sigyn audit witness
# Countersigned entry #1247 with fingerprint e5f6a7b8...

Git Anchoring

Anchor the audit trail to a git commit for external tamper-evidence:

sigyn audit anchor -v myapp
# Anchored audit hash abc123... to git commit def456...

Periodic Compliance Script

Automate weekly compliance checks with a cron job:

#!/usr/bin/env bash
# compliance-check.sh — run weekly via cron
set -euo pipefail

echo "=== Sigyn Compliance Check $(date -I) ==="

# Verify audit chain integrity
sigyn audit verify

# Check for secrets due for rotation
sigyn rotate due --env prod --max-age 90

# Export audit log for the past week
sigyn audit export --output "/var/log/sigyn/audit-$(date -I).json" --format json

# Anchor to git
sigyn audit anchor -v myapp

echo "=== Compliance check complete ==="
0 6 * * MON /opt/scripts/compliance-check.sh >> /var/log/sigyn/compliance.log 2>&1

Scripting with Sigyn

Bulk Update from a CSV

# From a .env file (preferred)
sigyn secret import secrets.env --env dev

# From CSV with shell loop
while IFS=, read -r key value; do
  sigyn secret set "$key" "$value" --env dev
done < secrets.csv

Checking for Expiring Secrets

Find secrets that haven’t been rotated in 6 months:

sigyn rotate due --env prod --max-age 180

Frequently Asked Questions

General

What is Sigyn?

Sigyn is a free, open-source, serverless secret manager for developers and teams. It encrypts your API keys, database credentials, and other secrets at rest, stores them locally, and syncs them between team members via git – with no central server or SaaS dependency.

How does Sigyn compare to Doppler, AWS Secrets Manager, or HashiCorp Vault?

Unlike hosted secret managers, Sigyn requires no server infrastructure and has no monthly cost. Your secrets live in your own git repositories, giving you full control over your data. Sigyn uses the same enterprise-grade cryptography (ChaCha20-Poly1305, X25519, Ed25519, Argon2id) but without vendor lock-in or third-party availability concerns.

SigynDopplerAWS Secrets ManagerHashiCorp Vault
Self-hostedYes (git)NoNoYes
Server requiredNoYes (SaaS)Yes (AWS)Yes
CostFreePaidPer-secret pricingFree (OSS) / Paid
EncryptionE2E (client-side)Server-sideServer-sideServer-side
SyncGitAPIAPIAPI

What platforms does Sigyn support?

Sigyn runs on macOS (Intel and Apple Silicon), Linux (x86_64 and ARM64), and Windows (x86_64). Install with a single command:

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.sh | sh

# Windows (PowerShell)
irm https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.ps1 | iex

Getting Started

How do I set up Sigyn for an existing project?

Run sigyn project init in your project root, then import your existing .env file:

sigyn project init
sigyn import dotenv .env -e dev

This creates a .sigyn.toml with your vault and identity, then encrypts your secrets.

What is the config file priority order?

Sigyn resolves settings in this order (highest to lowest):

  1. CLI flags (--vault, --env, --identity)
  2. .sigyn.toml in current or parent directory
  3. ~/.sigyn/project.toml
  4. ~/.sigyn/config.toml
  5. Built-in defaults

Is it safe to commit .sigyn.toml to my repository?

Yes. The .sigyn.toml project config only contains vault/environment/identity names and command aliases – never secret values. It’s designed to be committed and shared with your team.

What are the naming rules for vaults, environments, and identities?

Names must be 1–64 characters, contain only [a-zA-Z0-9-_], and cannot start with . or contain ...


Secrets & Environments

How do I inject secrets into my application?

Use the sigyn run command. Secrets are passed directly as environment variables to the child process without writing them to disk:

sigyn run -e dev -- ./my-app
sigyn run --prod -- docker compose up

What export formats does sigyn run export support?

Five formats are available:

sigyn run export -e dev -f dotenv    # .env file (default)
sigyn run export -e dev -f json      # JSON object
sigyn run export -e dev -f shell     # Shell export statements
sigyn run export -e dev -f docker    # Docker --env-file format
sigyn run export -e dev -f k8s       # Kubernetes Secret YAML manifest

Can I use {{KEY}} substitution in commands?

Yes, but only with the --allow-inline-secrets flag to prevent secrets from leaking into the process list:

sigyn run exec --allow-inline-secrets -- bash -c 'echo {{API_KEY}}'

Without the flag, {{KEY}} references are left as-is.

Are secrets in different environments cryptographically isolated?

Yes. Each environment has its own independent 256-bit encryption key. Members only receive keys for environments they’re authorized to access – they physically cannot decrypt other environments, even with direct file access.

How do I promote secrets across environments?

Use sigyn env promote. Multiple targets can be chained in a single command:

sigyn env promote --from dev --to staging
sigyn env promote --from dev --to staging,prod            # chained: dev → staging → prod
sigyn env promote --from staging --to prod --keys DB_URL  # specific keys only

What secret key names are valid?

Keys must start with a letter or underscore ([A-Za-z_]), and contain only alphanumerics, underscores, dots, dashes, and slashes. Maximum 128 characters. Nested paths like db/primary/host are supported.

Does Sigyn auto-sync after writing secrets?

If auto_sync = true in your ~/.sigyn/config.toml, Sigyn automatically pushes after secret set, delete, edit, import, and environment promote operations.


Team Collaboration

How do I share secrets with my team?

The quickest workflow:

# You: create vault with sync, invite teammate
sigyn vault create myapp --remote-url [email protected]:team/secrets.git
sigyn delegation invite --pubkey <bob-fingerprint> --role contributor --envs dev,staging --save-to-vault
sigyn sync push

# Bob: clone and accept in one step
sigyn vault clone [email protected]:team/secrets.git --invitation <uuid-or-path>

How do I clone a vault that someone shared with me?

Use sigyn vault clone with the git URL. Optionally accept the invitation in the same command:

# Single command (clone + accept invitation)
sigyn vault clone [email protected]:team/secrets.git --invitation ./invitation.json

# Or step by step
sigyn vault clone [email protected]:team/secrets.git
sigyn delegation accept ./invitation.json

The vault name is derived from the repository URL automatically, or override with --name.

What does --save-to-vault do on delegation invite?

It saves a copy of the invitation to the vault’s invitations/ directory. After you sigyn sync push, the invitee can clone the repo and accept using the UUID – no manual file transfer needed.

What does “inviter identity not found locally” mean?

When accepting an invitation, Sigyn verifies the Ed25519 signature using the inviter’s public key. If their identity file isn’t on your machine, verification fails. Import the inviter’s identity first, then re-accept.

What happens if two people edit the same secret at the same time?

Sigyn uses vector clocks and CRDTs (Conflict-free Replicated Data Types) to resolve conflicts deterministically. By default, Last-Write-Wins (LWW) is used. You can manually resolve conflicts with sigyn sync resolve.

Does Sigyn have a GUI?

Yes. Sigyn includes a local web GUI that runs in your browser. Launch with sigyn web --open. It provides visual vault and secret management while enforcing the same security model as the CLI. See the Web GUI page for details.

Can AI coding agents (Claude Code, Copilot, Cursor) see my secrets?

Not if you use Sigyn correctly. AI agents run in your shell and can read environment variables and .env files. Sigyn’s sigyn run injects secrets only into child processes, keeping them invisible to agents running in your terminal. For maximum security, use sigyn run serve which serves secrets over a Unix socket — no environment variables at all. See Working with AI Coding Agents for detailed workflows.


Security

Is my passphrase stored anywhere?

No. Your passphrase is processed through Argon2id to derive a key that decrypts your private keys. The passphrase itself is never written to disk or stored in memory beyond the current operation.

What happens if I lose my passphrase?

If you lose your passphrase and haven’t set up Shamir recovery shards, your secrets are permanently inaccessible. Set up recovery shards immediately after creating an identity:

sigyn-recovery split --identity alice --threshold 3 --total 5

Any 3 of the 5 shards can reconstruct the key; fewer than 3 reveal nothing. Distribute shards to trusted parties in different locations.

Are my secrets encrypted in git?

Yes. Every secret is encrypted with ChaCha20-Poly1305 before being committed to git. Even with full access to the git repository, secrets cannot be read without an authorized private key.

What dangerous environment variables does sigyn run block?

sigyn run prevents secrets from overriding critical system variables to prevent hijacking:

  • Execution: PATH, LD_PRELOAD, LD_LIBRARY_PATH, DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH
  • Sensitive: SIGYN_HOME, SIGYN_PASSPHRASE

If a secret conflicts, it’s skipped with a warning. Rename the secret or use sigyn run serve for socket-based injection.

Is clipboard data cleared automatically?

Yes. When you copy a secret with sigyn secret get --copy, the clipboard is automatically cleared after 30 seconds.

What is TOFU (Trust On First Use)?

On first vault access, Sigyn pins the vault ID and owner fingerprint locally. On subsequent access, it verifies they match. If the owner changed unexpectedly (possible copy-and-rehost attack), the unlock is blocked. Use sigyn vault trust <name> --accept-new-owner if the change is legitimate.

What does the “rollback detected” warning mean?

The remote git history doesn’t descend from your last sync checkpoint. Someone may have force-pushed to restore revoked access. Sigyn rejects this by default. Use sigyn sync pull --force only if you trust the change.

What is split-repo audit?

With sigyn vault create <name> --split-audit, vault data and audit data live in separate git repos. This lets auditors have read access to audit logs without accessing the vault’s encrypted secrets.


CI/CD

Can I use Sigyn in CI/CD?

Yes. Use the official GitHub Action or run sigyn run export directly:

- uses: tonybenoy/sigyn/action@main
  with:
    bundle: ${{ secrets.SIGYN_CI_BUNDLE }}
    passphrase: ${{ secrets.SIGYN_PASSPHRASE }}
    vault-repo: [email protected]:team/secrets.git
    vault-ssh-key: ${{ secrets.VAULT_SSH_KEY }}
    vault-name: myapp
    env: prod

What is SIGYN_CI_BUNDLE?

A base64-encoded bundle containing the CI identity file, device key, and identity fingerprint. Generate it with:

sigyn ci setup <identity-name>

Store the output as a CI secret (e.g., GitHub Secret).


Forks

What is a fork?

A fork creates a copy of a vault for branching workflows – like feature branches, org splits, or experiment isolation.

What’s the difference between leashed and unleashed forks?

  • Leashed fork: Stays connected to the parent vault. The parent admin can audit, revoke, or expire the fork. Secrets can sync back with approval. Good for feature branches.
  • Unleashed fork: Fully independent with a new master key. No upstream access or connection. Good for permanent org splits.

Can unleashed forks sync back to the parent?

No. Unleashed forks are cryptographically independent. Only leashed forks maintain a connection for syncing changes back.


Recovery

How does Shamir secret sharing work in Sigyn?

Shamir’s scheme splits your identity’s encrypted private key into N shards, any K of which can reconstruct it. For example, 3-of-5 means you distribute 5 shards and any 3 can recover the key, but 2 or fewer reveal nothing.

# Create shards
sigyn-recovery split --identity alice --threshold 3 --total 5

# Restore from shards
sigyn-recovery restore --shards shard1.json,shard2.json,shard3.json

Where should I store recovery shards?

Distribute shards to trusted parties in different physical locations: printed paper in a safe, USB drives with different colleagues, password manager vaults. Never store all shards in one place.

Delegation

This document describes how Sigyn manages vault membership through a delegation tree, including invitation creation, acceptance, role constraints, cascade revocation, and per-environment key rotation.

Overview

Sigyn uses a delegation tree to track how members were added to a vault. Every member (except the Owner) has a delegated_by field pointing to the fingerprint of the person who invited them. This creates a tree rooted at the vault Owner.

Three governing principles:

  1. Least privilege: members can only grant roles at or below their own level.
  2. Traceability: every delegation is recorded, forming an auditable tree.
  3. Secure revocation: removing a member cascades to their entire subtree and triggers per-environment key rotation for affected environments.
Owner (alice)
  |-- Admin (bob)         delegated_by: alice
  |     |-- Contributor (carol)   delegated_by: bob
  |     |-- ReadOnly (dave)       delegated_by: bob
  |-- Manager (eve)       delegated_by: alice
        |-- Operator (frank)      delegated_by: eve

Invitation Flow

Step 1: Create an Invitation

A member with the Manager role or higher can create an invitation. The inviter can only assign a role strictly below their own level (e.g., a Manager can invite Contributors but not other Managers or Admins). The Owner can invite any role except Owner. The invitation is an InvitationFile – a JSON document signed with the inviter’s Ed25519 key.

sigyn delegation invite create --role contributor --envs dev,staging

The invitation file contains:

FieldDescription
idUnique UUID for this invitation
vault_nameName of the vault being shared
vault_idUUID of the vault
inviter_fingerprintBLAKE3 fingerprint of the inviter
proposed_roleRole offered to the invitee
allowed_envsEnvironments the invitee will have access to
signatureEd25519 signature over the canonical payload
created_atUTC timestamp

Example invitation file:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "vault_name": "myapp",
  "vault_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "inviter_fingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "proposed_role": "contributor",
  "allowed_envs": ["dev", "staging"],
  "secret_patterns": ["*"],
  "max_delegation_depth": 0,
  "signature": "<Ed25519 signature bytes>",
  "created_at": "2026-02-23T10:30:00Z"
}

Signing Payload

The Ed25519 signature covers a versioned, deterministic concatenation of fields:

"sigyn-invitation-v1:" || id || vault_name || vault_id || inviter_fingerprint || role_string || env1 || env2 || ...

This is constructed by InvitationFile::signing_payload() and prevents field reordering or modification attacks.

Step 2: Out-of-Band Sharing

The invitation file is shared with the invitee through any channel: email, chat, file transfer, USB drive, etc. The file does not contain any secret key material. It is safe to transmit over insecure channels – the Ed25519 signature ensures the invitee can verify it was created by a legitimate vault member.

Step 3: Accept the Invitation

The invitee clones the vault and accepts the invitation on their machine:

# Single command (clone + accept):
sigyn vault clone [email protected]:team/secrets.git --invitation ./invitation-abc123.json

# Or separately:
sigyn vault clone [email protected]:team/secrets.git
sigyn delegation invite accept ./invitation-abc123.json

During acceptance:

  1. Signature verification: the invitation’s Ed25519 signature is verified against the inviter’s public key via InvitationFile::verify(). If invalid, acceptance is rejected.
  2. Role validation: the system confirms the inviter has sufficient privileges to delegate the proposed role.
  3. Envelope update: the invitee’s X25519 public key is registered in the vault’s EnvelopeHeader. A vault_key_slot is created (encrypting the vault key via X25519 ECDH + HKDF + ChaCha20-Poly1305), and env_slots are created for each environment in the invitee’s allowed_envs list, each encrypting that environment’s independent key.
  4. Policy update: a MemberPolicy entry is created with the proposed role, allowed environments, and delegated_by set to the inviter’s fingerprint.
  5. Audit entry: the acceptance is recorded in the hash-chained audit log.

Invitation Lifecycle

Invitations have the following statuses:

StatusMeaning
PendingCreated but not yet accepted
AcceptedSuccessfully accepted by the invitee
RejectedDeclined by the invitee
ExpiredPast the expires_at timestamp
RevokedCanceled by the inviter before acceptance

List pending invitations:

sigyn delegation pending

Role Constraints

A member can only delegate a role at or below their own level. The role hierarchy (from lowest to highest):

LevelRoleCan Delegate?
1ReadOnlyNo
2AuditorNo
3OperatorNo
4ContributorNo
5ManagerYes (roles 1–5)
6AdminYes (roles 1–6)
7OwnerYes (roles 1–7)

Only Manager (level 5) and above can delegate. This is enforced by Role::can_delegate() which returns true only for Role >= Manager.

Examples:

  • A Manager can invite someone as Manager, Contributor, Operator, Auditor, or ReadOnly.
  • A Manager cannot invite someone as Admin or Owner.
  • A Contributor cannot invite anyone, because delegation requires Manager or higher.
  • An Admin can invite other Admins, Managers, and all lower roles.

Environment and secret pattern restrictions are also inherited: a delegator cannot grant access to environments or patterns they do not have access to themselves.

Delegation Tree

The delegation tree is represented by DelegationNode:

#![allow(unused)]
fn main() {
DelegationNode {
    fingerprint: KeyFingerprint,
    name: String,
    role: Role,
    depth: u32,
    delegated_by: Option<KeyFingerprint>,
    children: Vec<DelegationNode>,
}
}

The tree is constructed by walking all MemberPolicy entries and linking each member to their delegated_by parent. The display_tree() method renders it with indentation.

View the tree:

sigyn delegation tree

Output:

alice (a1b2c3d4e5f6a7b8) [owner]
  bob (e5f6a7b8c9d0e1f2) [admin]
    carol (c9d0e1f2a3b4c5d6) [contributor]
    dave (1a2b3c4de7f8a9b0) [readonly]
  eve (5e6f7a8bc9d0e1f2) [manager]
    frank (9c0d1e2fa3b4c5d6) [operator]

Cascade Revocation

When a member is revoked with --cascade, Sigyn removes the target and all members they transitively invited. This is implemented as a BFS (breadth-first search) traversal of the delegation tree in collect_cascade().

Algorithm

function collect_cascade(root, policy):
    revoked = []
    queue = [root]
    while queue is not empty:
        parent = queue.pop()
        for member in policy.members:
            if member.delegated_by == parent and member not in revoked:
                revoked.append(member)
                queue.append(member)
    return revoked

Example

Given the tree:

alice [owner]
  bob [admin]
    carol [contributor]
    dave [readonly]
  eve [manager]

Revoking bob with --cascade:

  1. BFS starts from bob.
  2. Finds carol (delegated_by: bob) – added to revoked set.
  3. Finds dave (delegated_by: bob) – added to revoked set.
  4. Checks carol’s children – none.
  5. Checks dave’s children – none.
  6. Result: bob, carol, and dave are all revoked. Eve is unaffected.
sigyn delegation revoke <bob-fingerprint> --cascade

After revocation:

alice [owner]
  eve [manager]

Without Cascade

Revoking bob without --cascade removes only bob from the policy. Carol and dave remain as members but their delegated_by still points to the now-removed bob. This is useful when you want to remove a specific member without disrupting their delegates.

sigyn delegation revoke <bob-fingerprint>

Per-Environment Key Rotation on Revoke

Every revocation triggers key rotation for the affected environments. This is a critical security property: it ensures revoked members can no longer decrypt any vault data they previously had access to, even if they retained a copy of the encrypted files.

Rotation Process

  1. The revoke_member_v2() function builds the full list of fingerprints to remove (target + cascade, if applicable).
  2. All affected members are removed from the VaultPolicy.
  3. The revoked members’ vault_key_slots and env_slots are removed from the EnvelopeHeader.
  4. For each environment the revoked members had access to, a new 256-bit environment key is generated and sealed to the remaining authorized members.
  5. Only the affected environment files are re-encrypted with their new keys. Unaffected environments are untouched – this is a key benefit of per-environment key isolation.
  6. The updated EnvelopeHeader and VaultPolicy are persisted atomically.

RevocationResult

The revoke_member_v2() function returns:

#![allow(unused)]
fn main() {
RevocationResultV2 {
    directly_revoked: KeyFingerprint,                 // The target member
    cascade_revoked: Vec<KeyFingerprint>,             // Transitively revoked delegates
    new_vault_cipher: Option<VaultCipher>,            // Rotated vault cipher (if applicable)
    rotated_env_ciphers: BTreeMap<String, VaultCipher>, // New ciphers per affected env
    affected_envs: Vec<String>,                       // Environments that were re-keyed
}
}

Only environments the revoked members had access to are re-keyed. For example, revoking a member with allowed_envs: ["dev", "staging"] rotates only the dev and staging keys – the prod key and env file are untouched.

Per-Environment Access Management

Beyond full revocation, individual environment access can be adjusted:

# Grant a member access to an additional environment
sigyn delegation grant-env <fingerprint> staging

# Revoke a member's access to a specific environment (rotates that env key)
sigyn delegation revoke-env <fingerprint> staging

revoke-env removes the member’s slot for that environment, rotates the environment key, and re-encrypts the environment file – without affecting other environments or other members’ access.

Constraints on Delegated Access

Beyond role level, delegated members can have constraints that are checked by PolicyEngine::evaluate() on every access:

Time Windows

Restrict access to specific days and hours. Supports overnight ranges (e.g., start_hour: 22, end_hour: 6).

#![allow(unused)]
fn main() {
TimeWindow {
    days: [Mon, Tue, Wed, Thu, Fri],
    start_hour: 9,
    end_hour: 17,
}
}

Expiry

Members can have an expires_at timestamp. After expiry, all access is denied regardless of role.

Environment Restrictions

Members are limited to the environments specified in their allowed_envs list. A wildcard * grants access to all environments.

Secret Patterns

Members can be restricted to secrets matching specific glob patterns (e.g., DB_*, REDIS_*). The key is matched against the member’s secret_patterns list via matches_secret_pattern().

Breach Mode

In an emergency, breach mode provides a one-command response that revokes all delegated members and rotates all keys:

sigyn rotate breach-mode

This:

  1. Rotates the vault key and re-encrypts vault-level metadata (manifest, policy, audit).
  2. Rotates every per-environment key and re-encrypts every environment file.
  3. Rotates every secret in every environment to a new random value.
  4. Removes all delegated members from all slots.
  5. Saves the updated policy.
  6. Logs an audit entry for the breach mode activation.

Only members with Admin role or higher can activate breach mode (AccessAction::ManagePolicy is required).

Bulk Operations

For onboarding or offboarding multiple team members at once, use the bulk commands:

Bulk Invite

sigyn delegation bulk-invite --file members.json

The JSON file contains an array of member definitions:

[
  {"fingerprint": "a1b2c3d4e5f6...", "role": "contributor", "envs": "dev,staging"},
  {"fingerprint": "f7e8d9c0b1a2...", "role": "readonly", "envs": "*"}
]

All entries are validated before any are applied. If any fingerprint is invalid or any role is unrecognized, the entire operation is aborted. On success, the header and policy are saved once (not per entry), and each member gets an audit entry.

Bulk Revoke

sigyn delegation bulk-revoke --file revoke-list.json --cascade

The JSON file is a simple array of fingerprint strings:

["a1b2c3d4e5f6...", "f7e8d9c0b1a2..."]

Uses the same revocation logic as delegation revoke, including key rotation and optional cascade. All fingerprints are validated before any mutations.

Ownership Transfer

Vault ownership can be transferred to another member using a two-phase protocol:

# Phase 1: Current owner initiates transfer
sigyn vault transfer myapp --to a1b2c3d4e5f6...

# Phase 2: New owner accepts and re-signs
sigyn vault accept-transfer myapp

Phase 1 only writes a signed pending_transfer.cbor — the old owner retains full control until the new owner accepts. Phase 2 atomically updates the manifest, policy, and header. The old owner is downgraded to Admin by default (configurable with --downgrade-to). Transfers expire after 7 days. Both phases are recorded in the audit trail.

Operational Recommendations

  1. Use cascade revocation when removing someone who has invited others, unless you specifically want to preserve their subtree.
  2. Set expiry on contractor access to ensure temporary members lose access automatically.
  3. Use time windows for CI/CD to limit automated access to deployment hours.
  4. Audit the delegation tree regularly with sigyn delegation tree to understand the access structure.
  5. Prefer Contributor over Admin when in doubt. Follow the principle of least privilege.
  6. Set up Shamir recovery shards before distributing vault access widely, so that key loss does not lock out the entire team.
  • Security Model – RBAC, policy engine, and constraints
  • CLI Reference – complete command reference for delegation commands
  • Sync – how delegation changes are synchronized
  • Architecture – module structure for delegation code

Sync

This document describes how Sigyn synchronizes vault data between machines, including the git-based transport, conflict detection via vector clocks, and CRDT-based resolution.

Design Principles

  • Encrypted at rest: the sync engine operates on encrypted CBOR blobs. It never needs the master key. A git remote (or any file synchronization tool) can host Sigyn vaults without having access to the plaintext secrets.
  • Vaults are directories: each vault is a self-contained directory containing vault.toml, members.cbor, policy.cbor, envs/*.vault, audit.log.json, and forks.cbor. This maps naturally to git repositories.
  • No central server: Sigyn is peer-to-peer. Any member can push/pull to a shared git remote.

On-Disk Layout

The files synced for each vault:

~/.sigyn/vaults/myapp/
  vault.toml        # Vault metadata (TOML, human-readable)
  members.cbor      # Envelope header with encrypted master key slots
  policy.cbor       # Encrypted RBAC policy
  envs/
    dev.vault       # Encrypted environment (CBOR)
    staging.vault
    prod.vault
  audit.log.json    # Hash-chained audit trail (JSON Lines)
  forks.cbor        # Fork metadata
  deploy_key.sealed # Sealed SSH deploy key for audit push (optional)

All .cbor and .vault files are encrypted with ChaCha20-Poly1305. The sync engine pushes and pulls these opaque blobs without any decryption.

Git-Based Sync

Sigyn uses git2 (libgit2 Rust bindings) for sync operations. Each vault directory can be initialized as a git repository via GitSyncEngine.

GitSyncEngine

The engine operates on the vault directory:

#![allow(unused)]
fn main() {
GitSyncEngine {
    vault_path: PathBuf,
}
}

Key methods:

MethodDescription
init()Initialize a git repository in the vault directory
stage_all()Stage all changed files (git add *)
commit(message)Create a commit with staged changes
push(remote, branch)Push to a remote (refs/heads/branch)
pull(remote, branch)Fetch and fast-forward merge
sync(remote, branch, message)Pull, stage, commit, push in one operation
has_changes()Check for uncommitted changes
status()Compute sync status relative to remote

Sync Status

The engine computes sync status by comparing local HEAD with refs/remotes/origin/main:

StatusMeaning
NeverSyncedNo remote configured or no commits yet
UpToDateLocal and remote are at the same commit
LocalAhead(n)Local has n commits not yet pushed
RemoteAhead(n)Remote has n commits not yet pulled
DivergedBoth local and remote have new commits

Pull Results

A pull operation can result in:

ResultMeaning
UpToDateNothing to pull
FastForwardRemote changes applied cleanly
ConflictDiverged histories; needs resolution

Commands

# Configure a remote
sigyn sync configure --remote-url [email protected]:team/secrets.git

# Push local changes
sigyn sync push
sigyn sync push --remote origin --branch main

# Pull remote changes
sigyn sync pull
sigyn sync pull --remote origin --branch main

# Check status
sigyn sync status

# Enable auto-sync
sigyn sync configure --auto-sync true

# Generate a deploy key for audit push (sealed with vault cipher)
sigyn sync deploy-key generate

# Show the deploy key's public key
sigyn sync deploy-key show-pubkey

# Remove the deploy key
sigyn sync deploy-key remove

Vector Clocks

Sigyn uses vector clocks to track causal ordering of secret updates across nodes. Each node is identified by its BLAKE3 key fingerprint.

Structure

#![allow(unused)]
fn main() {
VectorClock {
    clocks: HashMap<String, u64>,  // fingerprint_hex -> counter
}
}

Operations

OperationDescription
tick(node_id)Increment this node’s counter by 1
merge(other)Take the component-wise maximum of two clocks
happened_before(other)True if this clock causally precedes the other
concurrent_with(other)True if neither clock precedes the other (conflict)

Causal Ordering

Given two vector clocks A and B:

  • A happened-before B: every component of A is less than or equal to the corresponding component of B, and at least one is strictly less.
  • B happened-before A: the reverse.
  • Concurrent: neither happened-before the other. This indicates a conflict – two members modified the same key independently.

Example

Alice writes DATABASE_URL:
  Clock: { alice: 1 }

Bob (who has seen Alice's write) writes API_KEY:
  Clock: { alice: 1, bob: 1 }

Alice (who has NOT seen Bob's write) writes API_KEY:
  Clock: { alice: 2 }

Alice's { alice: 2 } and Bob's { alice: 1, bob: 1 } are concurrent.
  -> Conflict detected on API_KEY.

After sync, the clocks are merged:

Merged: { alice: 2, bob: 1 }

LWW-Map CRDT

For automatic conflict resolution, Sigyn uses a Last-Writer-Wins Map (LWW-Map). Each entry in the map tracks metadata alongside the value:

#![allow(unused)]
fn main() {
LwwEntry<V> {
    value: V,                           // The secret value (encrypted)
    timestamp: DateTime<Utc>,            // When it was written
    clock: VectorClock,                  // Causal ordering
    writer: String,                      // Fingerprint of the writer
}

LwwMap<V> {
    entries: HashMap<String, LwwEntry<V>>,
}
}

Merge Semantics

When merging two LWW-Maps (e.g., during sigyn sync pull):

  • For each key present in both maps, the entry with the later timestamp wins.
  • Keys present in only one map are copied to the merged result.
#![allow(unused)]
fn main() {
fn merge(&mut self, other: &LwwMap<V>) {
    for (key, other_entry) in &other.entries {
        match self.entries.get(key) {
            Some(local_entry) => {
                if other_entry.timestamp > local_entry.timestamp {
                    self.entries.insert(key.clone(), other_entry.clone());
                }
            }
            None => {
                self.entries.insert(key.clone(), other_entry.clone());
            }
        }
    }
}
}

This provides automatic convergence: all nodes that receive the same set of updates will arrive at the same state, regardless of the order in which they receive them.

Conflict Detection and Resolution

When vector clocks indicate concurrent modifications to the same key, Sigyn records a Conflict:

#![allow(unused)]
fn main() {
Conflict {
    key: String,
    env: String,
    local_value: String,
    remote_value: String,
    local_clock: VectorClock,
    remote_clock: VectorClock,
}
}

Resolution Strategies

Conflicts can be resolved using one of six strategies:

StrategyDescription
TakeLocalKeep the local value, discard remote
TakeRemoteKeep the remote value, discard local
TakeLatestTimestampKeep whichever value has the later UTC timestamp (LWW)
TakeHigherRoleKeep the value written by the member with the higher RBAC role level
Merge(value)Provide a custom merged value
DeferSkip resolution; the conflict remains until resolved manually

Automatic vs Manual Resolution

Sigyn syncs over plain Git. When histories diverge (the same vault modified on two machines without syncing in between), sigyn sync pull reports a conflict that must be reconciled — it will not silently overwrite either side, and it refuses to overwrite uncommitted local changes.

Automated secret-level conflict resolution is not yet implemented. Diverged histories are reconciled manually with Git:

# See whether the vault has uncommitted local changes
sigyn sync status

# Reconcile a diverged history manually in the vault's git repo
cd ~/.sigyn/vaults/<vault>
git log --oneline --graph --all      # inspect both sides
git merge origin/main                # or rebase; resolve as normal
sigyn sync push

sigyn sync resolve currently validates its arguments and then exits with an error pointing you at the manual Git workflow above, rather than pretending to resolve the conflict.

Security Considerations

Sync Never Needs the Master Key

All vault files are encrypted at rest. The sync layer operates on opaque encrypted blobs. A git remote hosting Sigyn vaults cannot read any secret values – it only sees CBOR-encoded ciphertext.

Audit Log Integrity Across Sync

The hash-chained audit log is included in sync. After pulling, you can verify the chain to detect any tampering that may have occurred on the remote:

sigyn sync pull
sigyn audit verify

Revocation Propagation

When a member is revoked on one machine, the updated policy.cbor and members.cbor (with the revoked slot removed and master key rotated) are synced to all other members on the next push/pull cycle. The revoked member’s slot is no longer present in the envelope header, so they cannot decrypt any data even if they pull the latest files.

Vector Clocks Prevent Silent Overwrites

Concurrent modifications are always detected and surfaced via the conflict system. No data is silently overwritten during sync.

Workflow Example

A typical team workflow:

# Alice creates a vault with sync and pushes
sigyn vault create myapp --remote-url [email protected]:team/secrets.git
sigyn secret set DATABASE_URL 'postgres://...' --env dev
sigyn sync push

# Bob clones the vault, then pulls latest
sigyn vault clone [email protected]:team/secrets.git
sigyn sync pull --vault myapp

# Bob adds a secret and pushes
sigyn secret set API_KEY 'sk-...' --env dev
sigyn sync push

# Alice pulls Bob's changes
sigyn sync pull
sigyn secret list --env dev    # sees both DATABASE_URL and API_KEY

# If both Alice and Bob modify the same key simultaneously:
sigyn sync pull
# Output: Conflict detected on API_KEY (dev)
sigyn sync resolve API_KEY --strategy latest
sigyn sync push

Forks

Forks allow teams to create independent or semi-independent copies of a vault. This is useful for feature branches, team-specific configurations, or organizational splits.

Fork Modes

Leashed Fork

A leashed fork maintains a link to the parent vault. The upstream admin retains access to the fork and can audit or revoke access at any time.

sigyn fork create myapp-feature --mode leashed

Properties:

  • Parent admin’s key is always in the fork’s member list
  • Parent can audit the fork’s operations
  • Parent can revoke the fork
  • Secrets can be synced back to the parent with approval
  • Maximum drift (days since last sync) can be enforced

Unleashed Fork

An unleashed fork is fully independent. A new master key is generated, and the parent vault has no access.

sigyn fork create myapp-independent --mode unleashed

Properties:

  • Completely independent encryption (new master key)
  • No upstream access or audit capability
  • Cannot sync back to the parent
  • Useful for permanent splits or when full autonomy is required

Managing Forks

# List all forks of the current vault
sigyn fork list

# Check fork status (drift, sync state)
sigyn fork status myapp-feature

# Sync a leashed fork with its parent
sigyn fork sync myapp-feature

# Approve a fork sync request (as parent admin)
sigyn fork approve myapp-feature

# Set expiry on a fork
sigyn fork expire myapp-feature --days 30

Fork Policy

Fork policies control:

  • Sharing policy: Private (fork-only), SharedWithParent, or Public
  • Max drift days: How long a leashed fork can go without syncing
  • Inherit revocations: Whether parent revocations cascade to the fork
  • New member allowance: Whether fork admins can add members independently

Sigyn Security Model

This document describes the cryptographic primitives, access control system, and security properties of Sigyn.

Cryptographic Primitives

PrimitiveAlgorithmPurpose
Key exchangeX25519 (Curve25519 Diffie-Hellman)Derive shared secrets for envelope encryption
SignaturesEd25519Sign audit entries, invitation files, identity verification
Symmetric encryptionChaCha20-Poly1305 (AEAD)Encrypt secret data and master key slots
Key derivation (passphrase)Argon2idDerive wrapping key from user passphrase
Key derivation (context)HKDF-SHA256Derive per-slot keys from DH shared secrets
Hashing / fingerprintsBLAKE3Audit chain hashing, key fingerprints

All cryptographic operations use audited Rust crates from the RustCrypto project: x25519-dalek, ed25519-dalek, chacha20poly1305, argon2, hkdf, blake3.

Envelope Encryption

Sigyn uses envelope encryption with per-environment key isolation to allow multiple members to access a vault without sharing a single passphrase, while cryptographically enforcing environment-level access boundaries.

Two-Tier Key Architecture

Each vault has two tiers of symmetric keys:

  1. Vault key (256-bit) – encrypts vault-level metadata: manifest, policy, audit log, rotation schedules.
  2. Per-environment keys (256-bit each) – each environment (dev, staging, prod, etc.) has its own independent key that encrypts only that environment’s secret data.
                    +-------------------+
                    |   Vault Key       |  (encrypts manifest, policy, audit)
                    +-------------------+
                            |
              +-------------+-------------+
              |             |             |
        +-----------+ +-----------+ +-----------+
        | Slot: Alice| | Slot: Bob | | Slot: Carol|  (vault_key_slots)
        +-----------+ +-----------+ +-----------+


    +-------------------+  +-------------------+  +-------------------+
    |  Dev Env Key      |  | Staging Env Key   |  |  Prod Env Key     |
    +-------------------+  +-------------------+  +-------------------+
            |                       |                       |
      +-----------+           +-----------+           +-----------+
      | Slot: Alice|          | Slot: Alice|          | Slot: Alice|
      | Slot: Bob  |          | Slot: Alice|          +-----------+
      | Slot: Carol|          | Slot: Bob  |
      +-----------+           +-----------+           (env_slots)

A member only receives slots for environments they are authorized to access. A Contributor with allowed_envs: ["dev"] physically cannot decrypt prod secrets because they never receive the prod environment key. This is a cryptographic barrier, not just a policy-layer check.

Sealing (encrypt a key for a recipient)

  1. Generate an ephemeral X25519 keypair.
  2. Perform Diffie-Hellman: shared_secret = ephemeral_private * recipient_public.
  3. Derive a slot key via HKDF-SHA256: slot_key = HKDF(salt=vault_id, ikm=shared_secret, info="sigyn-envelope-v1").
  4. Encrypt the key with ChaCha20-Poly1305 using the slot key and a random nonce.
  5. Store (recipient_fingerprint, ephemeral_public_key, nonce || ciphertext) as the slot.

For vault key slots, the AEAD Additional Authenticated Data (AAD) is fingerprint || vault_id. For environment key slots, the AAD is fingerprint || vault_id || env_name_len (4 LE bytes) || env_name_bytes. The length prefix prevents collisions between environment names (e.g., “dev” + “prod” cannot be confused with “devprod”).

Unsealing

  1. Find the RecipientSlot matching your key fingerprint.
  2. Perform Diffie-Hellman: shared_secret = my_private * slot.ephemeral_public.
  3. Derive the same slot key via HKDF-SHA256 with the vault UUID as salt.
  4. Decrypt the key with ChaCha20-Poly1305.

For vault operations (manifest, policy, audit), unseal from vault_key_slots. For environment operations, unseal the specific environment key from env_slots[env_name].

Key Properties

  • Each slot uses a unique ephemeral keypair, so compromising one slot reveals nothing about others.
  • The vault UUID is used as HKDF salt, binding each slot key to a specific vault.
  • Environment names are bound into the AAD, preventing cross-environment slot reuse.
  • A member without a slot for a given environment cannot decrypt that environment’s secrets, regardless of any policy bypass.
  • On member revocation, only the affected environment keys are rotated and re-sealed to remaining members. Unaffected environments are untouched.
  • Adding a member to a new environment requires the inviter to hold that environment’s key – you cannot grant access you don’t have.

Identity

Each Sigyn user has an identity consisting of two keypairs:

KeypairAlgorithmPurpose
Encryption keypairX25519Receive sealed vault and environment key slots
Signing keypairEd25519Sign audit entries, invitations

Both keypairs are stored on disk encrypted with an Argon2id-derived key from the user’s passphrase. The identity is identified by its key fingerprint, which is a BLAKE3 hash of the X25519 public key truncated to 128 bits (16 bytes), displayed as a hex string.

Passphrase Protection

passphrase --> Argon2id --> wrapping_key --> ChaCha20-Poly1305 --> encrypted keypairs on disk

The Argon2id parameters are chosen for interactive use (moderate memory and time cost). When the user runs any command that needs their private key, they are prompted for their passphrase. The wrapping key is held in a secrecy::Secret and zeroed on drop.

On-Disk Layout

~/.sigyn/identities/<name>/
  identity.toml             # Metadata: name, fingerprint, email, created_at
  secret_key.enc            # Argon2id-wrapped Ed25519 + X25519 keypair

Role-Based Access Control (RBAC)

Sigyn implements a 7-level role hierarchy. Each role inherits all permissions of lower roles:

LevelRoleReadWriteManage MembersManage PolicyDelegate
1ReadOnlyyesnononono
2Auditoryesnononono
3Operatoryesnononono
4Contributoryesyesnonono
5Manageryesyesyesnoyes
6Adminyesyesyesyesyes
7Owneryesyesyesyesyes

The Owner is the vault creator and always bypasses policy checks. There is exactly one Owner per vault.

Permission Breakdown

  • Read: decrypt and view secret values (all roles).
  • Write: set or delete secrets (Contributor and above).
  • Manage Members: add/remove members, create invitations (Manager and above).
  • Manage Policy: modify RBAC rules, constraints, and ACLs (Admin and above).
  • Delegate: create invitations for new members, limited to roles at or below your own (Manager and above).

Policy Engine

Policy files (policy.cbor) are encrypted with the vault cipher and Ed25519-signed (SGSN format). The signature binds the policy to the vault UUID, preventing tampering and cross-vault replay. All policy files must be signed; unsigned files are rejected.

Every secret access goes through PolicyEngine::evaluate(). There is no code path that reads or writes secrets without a policy check. The engine evaluates the following in order:

  1. Owner bypass: if the actor is the vault owner, access is always granted.
  2. Membership check: the actor must be a registered vault member.
  3. Constraint check: if the member has constraints (time windows, expiry, MFA), they are all evaluated. Any failure results in denial.
  4. Environment check: the member must have explicit access to the requested environment (or wildcard *).
  5. Role check: the member’s role must have the required permission for the action (read, write, delete, manage members, manage policy, create env, promote).
  6. Secret pattern check: if the member has secret-level ACL patterns, the requested key must match at least one allowed pattern (glob syntax: DB_*, AWS_*, *_SECRET).

The engine returns one of three decisions:

  • Allow – access granted.
  • Deny(reason) – access denied with an explanation string.
  • AllowWithWarning(message) – access granted but with a warning.

Constraints

Members can have constraints attached to their access:

Time Windows

#![allow(unused)]
fn main() {
TimeWindow {
    days: [Mon, Tue, Wed, Thu, Fri],
    start_hour: 9,
    end_hour: 17,
}
}

If any time windows are defined for a member, at least one must match the current time for access to be granted. Supports overnight windows (e.g., start_hour: 22, end_hour: 6 wraps past midnight).

Expiry

Members can have an expires_at timestamp. After expiry, all access is denied regardless of role.

MFA (Multi-Factor Authentication)

When require_mfa: true is set on global or member constraints, the policy engine returns RequiresMfa instead of Allow. The CLI then:

  1. Checks for a valid MFA session (default grace period: 1 hour).
  2. If no session, loads the encrypted .mfa file (decrypted with a key derived via HKDF-SHA256 from the identity’s X25519 private key with context b"mfa-state").
  3. Prompts for a TOTP code or single-use backup code.
  4. On success, creates a session file (HMAC-protected with a key derived via HKDF from the device key with context b"sigyn-session-hmac-v1") so subsequent operations within the grace period skip the prompt. The HMAC covers the identity fingerprint as well as the verification timestamp, so a session file is bound to the identity that created it — copying alice.session to bob.session no longer validates for Bob.

MFA state is stored per identity at ~/.sigyn/identities/<fingerprint>.mfa, encrypted with ChaCha20-Poly1305 using the identity’s fingerprint as AEAD Associated Authenticated Data (AAD). This binds the MFA ciphertext to the specific identity — copying an MFA file from one identity to another will fail decryption. Sessions are stored at ~/.sigyn/sessions/<fingerprint>.session with 0o600 permissions in a 0o700 directory.

Backup codes are hashed with blake3 before storage and verified using constant-time comparison to prevent timing side-channel attacks. Each backup code is consumed on use and cannot be reused. Session HMACs are also compared in constant time.

Per-Key ACLs

Beyond role-based access, individual secrets can have fine-grained ACLs via SecretAcl:

VariantMeaning
EveryoneAll members with sufficient role can access (default)
Roles(set)Only members with one of the listed roles
Fingerprints(set)Only the listed key fingerprints
DenyExplicitly deny all access

Each secret key can have separate read and write ACLs via the KeyAcl struct. Additionally, members can be restricted to secrets matching specific glob patterns (e.g., DB_*, AWS_*, *_SECRET).

Delegation

See Delegation for a full deep dive. Key security properties:

  • Members can only delegate roles strictly below their own level (e.g., a Manager can invite Contributors but not Managers). The Owner can invite any role except Owner.
  • Each delegation records delegated_by, forming a tree rooted at the Owner.
  • Cascade revocation: revoking a member revokes everyone they invited, transitively (BFS traversal of the delegation tree).
  • Vault key rotation on revoke: the vault-level encryption key is rotated immediately upon revocation. A new random key is generated and re-sealed to the remaining authorized members only. The revoked member’s cached vault key can no longer decrypt policy, manifest, or audit data written after revocation.
  • Per-environment key rotation on revoke: in addition to the vault key, only the environment keys the revoked member had access to are rotated. Each affected environment gets a new random key, re-sealed to the remaining authorized members. Unaffected environments are untouched. The revoked member (and their cascade subtree) immediately lose decryption access.
  • Invitation expiry: invitations are created with a 7-day expiry (expires_at). Attempting to accept an expired invitation is rejected with a clear error message. This limits the window for invitation file theft or replay.

Audit Trail

Sigyn maintains a hash-chained, signed, and encrypted audit log for every vault, stored at <vault>/audit.log.json. Each line is individually encrypted with a vault-derived cipher (HKDF from the vault key with context sigyn-audit-v1 and vault_id as salt), then base64-encoded.

Entry Structure

Each AuditEntry contains:

  • sequence: monotonically increasing counter.
  • timestamp: UTC timestamp.
  • actor: BLAKE3 key fingerprint of the actor.
  • action: what was done (SecretRead, SecretWritten, SecretDeleted, VaultCreated, MemberRevoked, PolicyChanged, MasterKeyRotated, EnvironmentCreated, EnvironmentPromoted, SecretsExported, SecretsInjected, SecretsServed, SecretsListed, etc.).
  • env: which environment was affected (optional).
  • outcome: Success or Failure(reason).
  • nonce: 16-byte random nonce (prevents rainbow table attacks on the hash chain).
  • prev_hash: BLAKE3 hash of the previous entry (or null for the first entry).
  • entry_hash: BLAKE3 hash of (sequence, timestamp, actor, nonce, prev_hash).
  • signature: Ed25519 signature over entry_hash by the actor.

Security Properties

  • Hash chain: each entry includes the hash of the previous entry. Tampering with or removing any entry breaks the chain, which is detected by sigyn audit verify.
  • Ed25519 signatures: each entry is signed by the actor’s Ed25519 key, proving authorship and preventing forgery.
  • Append-only: the log file is opened in append mode only. The reader tolerates a single trailing corrupt line (crash recovery for interrupted writes) while still detecting mid-file tampering.
  • Witness countersigning: a second party can countersign the latest entry to provide independent verification via sigyn audit witness. Witness signatures are stored separately in a WitnessLog.
  • External anchoring: audit hashes can be anchored to external systems for additional tamper evidence.

Verification

sigyn audit verify

This walks the entire log and verifies:

  1. Hash linkage: each entry’s prev_hash matches the preceding entry’s entry_hash.
  2. Hash integrity: each entry’s entry_hash is recomputed and compared.
  3. Ed25519 signatures: each entry’s signature is verified against the actor’s public key from the vault policy. Entries by unknown actors (not in policy) are rejected.
  4. Sequence continuity: all sequence numbers from 0 to N must be present with no gaps, detecting selective deletion of entries.

Any break is reported with the sequence number of the first invalid entry (SigynError::AuditChainBroken(seq)).

Shamir Secret Sharing (Recovery)

For disaster recovery, Sigyn implements K-of-N Shamir secret sharing over GF(256):

  • The identity encryption private key is split into N shards.
  • Any K shards (where K <= N) can reconstruct the original key.
  • Fewer than K shards reveal zero information about the key (information-theoretic security).

Implementation details:

  • Arithmetic is performed in GF(256) with irreducible polynomial x^8 + x^4 + x^3 + x + 1 (0x11B).
  • Polynomial evaluation uses Horner’s method.
  • Reconstruction uses Lagrange interpolation at x=0.
  • Inverse computation uses Fermat’s little theorem: a^(-1) = a^254 in GF(256).

Typical usage: split into 5 shards with threshold 3, distribute to trusted parties.

sigyn-recovery split --identity alice --threshold 3 --total 5
sigyn-recovery restore shard-*.json

The standalone sigyn-recovery binary is intentionally minimal so it can be distributed independently of the main CLI.

Fork Isolation

Vaults can be forked in two modes:

ModeDescriptionUpstream Access
LeashedFork stays connected to the parent vault. The upstream owner retains the ability to revoke or expire the fork. Changes can be synced back.Parent retains access
UnleashedFork is fully independent. A new master key is generated. No connection to the parent vault remains.Fully severed

Fork policies control sharing mode, max drift (days without syncing), revocation inheritance, and whether the fork can add members independently. Fork envelope headers are SGSN-signed and fork policy files are signed, both bound to the fork’s vault UUID.

Memory Safety

  • All private key material is wrapped in secrecy::Secret<T> which zeroes memory on drop via the zeroize crate.
  • Ephemeral slot keys (HKDF-derived) are wrapped in Zeroizing<> and zeroed immediately after use.
  • Ed25519 signing keys use ZeroizeOnDrop from ed25519-dalek.
  • Key fingerprint comparisons use constant-time equality (subtle::ConstantTimeEq) to prevent timing side-channels.
  • Decrypted secret values are never written to disk in plaintext.
  • The CLI never logs or prints private keys.
  • Clipboard copies are automatically cleared after 30 seconds via a background thread guarded by an AtomicBool to prevent double-clear races.
  • Passphrase strings are explicitly zeroized at CLI call sites after use.

Atomic File Writes

All file writes in the CLI 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, with lock files created at 0o600 permissions.

The secure_write() function on Unix creates a temporary file with 0o600 mode from the start (via OpenOptions::mode()), writes content, calls sync_all() for durability, and atomically renames to the target path. This eliminates the TOCTOU window where a file could be briefly readable with default permissions between creation and chmod.

Symlink protection: Before writing, atomic_write() walks every component of the target path and rejects the operation if any component is a symlink. This prevents both direct symlink files and symlinked parent directories from redirecting writes outside the vault. The check applies to both new and existing files.

Filesystem Permissions

Sigyn enforces restrictive Unix permissions on all files it manages:

  • ~/.sigyn/ directory: created with 0o700 (owner only) to prevent other local users from reading vault metadata, config, or manifests.
  • Encrypted files (policy, secrets, identities): written via atomic_write() with 0o600 permissions (owner read/write only).
  • Audit log files: 0o600 permissions enforced on creation via OpenOptions.
  • Witness log files: 0o600 permissions enforced after atomic persist.
  • Lock files: 0o600 permissions enforced (errors propagated, not silently dropped).
  • All other files (manifests, config, context, notifications): also encrypted and written via secure_write() with 0o600 permissions.

Encrypted File Protection

Every file under ~/.sigyn/ is either encrypted (AEAD) or cryptographically signed. A three-tier key hierarchy ensures each file is protected with the appropriate key available at read time:

Tier A: Device Key

A 32-byte random key stored at ~/.sigyn/.device_key (mode 0o400). Generated on first use. The file is 64 bytes: the key followed by a BLAKE3 integrity hash (BLAKE3(key || "sigyn-device-key-v1")). The hash is verified on every load; tampered files are rejected. Protects files that must be readable before any identity is loaded:

FileHKDF Context
config.tomlsigyn-config-v1
context.tomlsigyn-context-v1
notifications.tomlsigyn-notifications-v1
org-manifest.tomlsigyn-org-manifest-v1
forks.cborsigyn-forks-v1

Tier B: Identity Signing Key (Ed25519)

The members.cbor envelope header is wrapped in a signed file format (SGSN magic) with an Ed25519 signature covering blake3(cbor_data \|\| vault_id_bytes). This provides integrity verification before the vault master key is available.

The EnvelopeHeader includes a vault_id field, allowing the vault ID to be read from members.cbor before decrypting vault.toml — breaking the circular dependency that would otherwise require the manifest to be plaintext. The signature is verified after vault unlock using the owner’s signing key from the local identity store.

Policy-signer trust anchor. A vault policy is signed by the owner or an admin. Because every member holds the vault key, the authority of a non-owner signer is never taken from the freshly loaded policy itself (which the signer could rewrite) — it is checked against a device-local trust anchor recorded from the last policy accepted on this device (kept in the never-synced pinned_vaults.cbor). An owner-signed policy refreshes the trusted admin set; an admin-signed policy is accepted only if its signer was an admin in that recorded set, and it may not add or elevate Admin+ members. This closes a self-escalation path where a member could sign a policy granting themselves admin and have every other device accept it. The same check is applied by both the CLI and the local web GUI.

Invitation acceptance requires the inviter’s identity to be present locally for signature verification. This prevents accepting tampered invitations even when the inviter is offline. Invitations use a length-prefixed signing payload (v3 format) where every variable-length field is preceded by its byte length as a little-endian u32, and list fields include an element count prefix. This prevents field boundary ambiguity attacks where crafted values in one field could be interpreted as part of another. As of v3 the created_at and expires_at timestamps are part of the signed payload, so an invitee cannot extend or remove the expiry.

Hierarchy node members.cbor files also use the SGSN signed format with Ed25519 signatures, always verified on read. Hierarchy policy files (policy.cbor) at every org node level are signed and bound to the node UUID, identical to vault-level policies.

Tier C: Vault Key and Per-Environment Keys

After vault unlock, vault-level files are protected by a cipher derived from the vault key, and each environment file is protected by its own independent key:

Vault key – protects metadata files via HKDF-derived ciphers:

FileHKDF ContextHKDF Salt
vault.toml (manifest)sigyn-manifest-v1(AAD = vault_id)
rotation_schedules.tomlsigyn-rotation-v1vault_id
audit.log.json entriessigyn-audit-v1vault_id
witnesses.jsonsigyn-witness-v1vault_id
deploy_key.sealedsigyn-deploy-key-v1 (AAD)—

Per-environment keys – each envs/<name>.vault file is encrypted with that environment’s dedicated 256-bit key, using the environment name as AEAD AAD. Members only receive env key slots for environments in their allowed_envs list.

Sealed File Format

Encrypted files use the SGYN sealed format:

[magic: "SGYN" 4B] [version: 0x01 1B] [nonce: 12B] [ciphertext + Poly1305 tag]

Signed files use the SGSN signed format:

[magic: "SGSN" 4B] [version: 0x01 1B] [cbor_data] [signature: 64B Ed25519]

No Plaintext Fallbacks

All file readers strictly require their expected format (SGYN magic for encrypted files, SGSN magic for signed files). There are no legacy plaintext fallbacks. If a file does not have the correct magic header, it is rejected with a clear error. This prevents downgrade attacks where an attacker replaces an encrypted file with a plaintext version to bypass integrity checks.

Rollback Protection

An attacker with push access to a vault’s git remote could force-push older commits, restoring a pre-revocation members.cbor that still includes a revoked member’s slot. Sigyn prevents this with commit-level rollback detection:

Sync Checkpoints

After every successful sync pull or sync push, the current HEAD commit OID is recorded in the device-local pinned_vaults.cbor store (encrypted with the device key). On the next sync pull, Sigyn verifies that the fetched remote HEAD is a descendant of the stored checkpoint using git2::graph_descendant_of().

If the remote HEAD does not descend from the checkpoint, the pull is aborted with an SSH-style warning banner:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: POSSIBLE ROLLBACK ATTACK   @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

The user must explicitly pass --force to accept the new remote state.

Audit Checkpoints (tail-truncation defense)

The hash chain makes any prefix of the audit log verify, so simply dropping the most recent entries — for example a sync remote deleting the entry that recorded a sensitive access — would otherwise pass verification. To detect this, Sigyn records the audit chain tip (sequence, entry_hash) after every append in device-local state (pinned_vaults.cbor), which is encrypted with the device key and never synced through the vault’s git repo. An attacker who truncates the shared log therefore cannot also rewrite the recorded tip.

On sigyn audit verify (and after a pull), the log must still contain the recorded tip sequence with the recorded hash; if it no longer does, verification fails with a non-zero exit. The tip is a monotonic low-water mark: because other members also append entries, the check requires the chain to still contain the recorded tip, then advances it to the current head. A signed AuditCheckpoint (CBOR + Ed25519, SGSN, bound to the vault UUID) is also available for exchanging a checkpoint out-of-band.

Git Commit Signing

Each sync commit is accompanied by a sidecar signature file (.sigyn-commit-sig) containing the commit OID signed with the user’s Ed25519 key. On pull, the signature is verified against the committer’s public key. Missing signature files are rejected.

Vault Origin Pinning (TOFU)

Sigyn uses Trust-On-First-Use to prevent a vault copy-and-rehost attack:

  1. First access: When a vault is unlocked for the first time on a device, the vault ID and owner fingerprint are recorded in the device-local pinned_vaults.cbor. A prominent warning banner is displayed urging the user to verify the owner fingerprint out-of-band. For automated workflows, set SIGYN_VERIFY_OWNER=<fingerprint> to require a match before the pin is saved — a mismatch aborts the unlock.
  2. Subsequent accesses: The vault ID and owner fingerprint are verified against the pin. If either has changed, the unlock is aborted with an SSH-style warning:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: VAULT OWNER CHANGED        @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

This prevents an attacker from copying vault files, re-signing members.cbor with their own key, and inviting victims to a different repo. Even though the entire cryptographic chain passes, the pin check catches the owner substitution.

To accept a legitimate ownership change: sigyn vault trust <name> --accept-new-owner.

Vault/Audit Repo Split

By default, vault data and audit data share a single git repo. This means an auditor who needs push access to write witness entries also has push access to members.cbor. Git hosting cannot enforce per-file permissions.

Sigyn supports an optional split-repo layout where audit data lives in a separate git repository:

vaults/<name>/          # vault repo (members, policy, secrets)
  vault.toml
  members.cbor
  policy.cbor
  envs/
  .gitignore            # ignores audit/
  audit/                # separate git repo (audit log, witnesses)
    .git/
    audit.log.json
    witnesses.json

Created with sigyn vault create <name> --split-audit. This allows git hosting to grant auditors read access to the vault repo (to unseal the audit cipher) and write access only to the audit repo. Sync operations coordinate both repos automatically, with independent rollback protection for each.

Local State Store

Device-local state that is never synced is stored in ~/.sigyn/pinned_vaults.cbor, encrypted with a cipher derived from the device key (HKDF context b"sigyn-pinned-vaults-v1"). This store holds:

  • Vault pins: TOFU owner fingerprint and vault ID, recorded on first access.
  • Sync checkpoints: last-known HEAD commit OIDs for rollback detection.
  • Audit checkpoints: last-known audit sequence and entry hash.

The .org_link metadata file (which records a vault’s org hierarchy membership) is encrypted with a device-key-derived cipher (HKDF context b"sigyn-org-link-v1") using the sealed file format.

Project Config Trust

The .sigyn.toml file provides per-project defaults (vault name, environment, identity). Because it is typically committed to a git repository, it can be modified by anyone with push access. Sigyn applies the following safeguards:

  • Git-tracked warning: when .sigyn.toml is loaded from a directory that contains a .git directory (or any parent does), a warning is printed: warning: using .sigyn.toml from git-tracked directory — verify this file was not modified by an untrusted party.
  • Identity override protection: if .sigyn.toml sets the identity field (which controls which identity is used for all operations), Sigyn prints an additional warning and requires the SIGYN_TRUST_PROJECT_CONFIG environment variable to be set. Without it, the identity override is ignored with a warning. This prevents an attacker from silently redirecting vault operations to a compromised identity by modifying a committed config file.

AI Coding Agent Threat Model

AI coding assistants (Claude Code, Cursor, GitHub Copilot, Windsurf, etc.) present a new secret exposure surface: they run in your terminal with access to environment variables, .env files, shell history, and process listings.

Attack vectors

VectorHow agent sees secrets
Environment variablesenv, printenv, /proc/self/environ
.env filescat .env, file reading tools
Shell historyhistory, ~/.bash_history
Process listingps aux shows command-line args with inline secrets
Conversation contextSecrets pasted into prompts or visible in tool output

Sigyn mitigations

MethodProtection levelHow it works
sigyn run execStrongSecrets injected as env vars into child process only; parent shell (where agent runs) is clean
sigyn run --cleanStrongerChild process gets a clean environment with only vault secrets; no inherited vars leak
sigyn run serveStrongestSecrets served over a Unix socket with 0600 permissions; never appear in env vars or process listings
.sigyn.toml named commandsConveniencesigyn run dev — developers never type or see secrets
sigyn webGUI alternativeManage secrets in a browser; no terminal exposure at all
  1. Never export secrets or use source .env in terminals where AI agents run.
  2. Use sigyn run exec or named commands in .sigyn.toml to inject secrets only into the processes that need them.
  3. For maximum isolation, use sigyn run serve — secrets are accessible only via the Unix socket.
  4. Run your AI coding agent in a separate terminal from your application.

Allowing agents to run and debug safely

If you need the agent to run your app for debugging, use non-sensitive dev credentials and environment separation:

  1. Dev environment: localhost databases, test API keys — not worth protecting.
  2. Staging/prod environments: real credentials — agent never touches these.
  3. Named commands: .sigyn.toml defines an allowlist of commands the agent can run.
  4. No passphrase caching: don’t run sigyn agent start in the agent’s terminal — it can only use pre-configured sigyn run commands.

See Letting AI Agents Run and Debug Your App for detailed workflows.

What Sigyn does NOT protect against

  • An AI agent that has been explicitly granted access to run sigyn commands with a cached passphrase
  • Secrets that your application logs to stdout/stderr (visible if the agent reads log files)
  • Secrets embedded in source code (use Sigyn instead of hardcoding)

Threat Model Summary

ThreatMitigation
Stolen vault filesChaCha20-Poly1305 encryption; attacker needs a member’s private key
Compromised memberRevoke member; affected environment keys are rotated; cascade revocation removes their delegates
Tampered audit logHash chain breaks on tamper; Ed25519 signatures prove authorship
Tampered config/manifestAEAD authentication; device key or master key required to forge
Tampered members.cborEd25519 signature over content + vault_id; forgery requires signing key
Downgrade to plaintextNo fallbacks; all readers reject data without correct magic header
Forged MFA sessionSession HMAC uses device-key-derived secret, not public fingerprint
Tampered invitationAcceptance requires inviter’s identity for signature verification
Lost access (key loss)Shamir K-of-N recovery shards
Unauthorized access escalationPolicyEngine::evaluate() on every operation; no bypass path
Brute-force passphraseArgon2id with tuned memory/time cost
Stolen credentials (passphrase only)Optional TOTP-based MFA as second factor; session grace period limits exposure window
AI agent reads env varssigyn run exec injects into child only; sigyn run serve uses Unix socket with no env exposure
Replay of old ciphertextUnique nonces per encryption; vault UUID bound into HKDF salt
Sensitive data in memorysecrecy::Secret + zeroize on drop; constant-time fingerprint comparison
Partial file writesAtomic writes via tempfile::persist()
Symlink traversalPer-component symlink check rejects any symlink in path (parent dirs included)
Local filesystem snooping~/.sigyn/ directory 0o700; all files 0o600; all contents encrypted
Device key compromiseLimits exposure to Tier A files only; vault secrets require identity key
Rollback attack (force-push)Commit OID checkpoints; descendant verification on pull
Copy-and-rehost attackTOFU vault origin pinning; owner fingerprint checked on every unlock
Auditor privilege escalationOptional split-repo layout; separate git permissions for vault and audit data
Org link metadata leak.org_link encrypted with device key
MFA file swapping between identitiesFingerprint bound as AAD in AEAD encryption
Invitation replay/theft7-day expiry; Ed25519 signature with length-prefixed payload
Signing payload field confusionLength-prefixed v2 format prevents boundary ambiguity
Hierarchy node header forgerySGSN signed format with Ed25519 verification
Forged org/fork policy fileAll policy files signed and bound to vault/node UUID; unsigned rejected
Crash during audit appendTrailing corrupt line tolerated; mid-file tampering detected
File permission TOCTOUAtomic temp+rename with 0o600 mode set at creation
Predictable temp file attackEditor temp files use cryptographically random names via tempfile crate
Secret leaking via process listInline {{KEY}} substitution requires explicit --allow-inline-secrets flag
Secret leaking via rotation hooksHook secrets passed via stdin, not environment variables; avoids /proc/<pid>/environ visibility
Hook command injectionHooks executed directly (no shell); shell metacharacters and path traversal rejected at save time
Unaudited secret accesssigyn run exec/export/serve and sigyn secret list generate audit entries
Malicious project configWarning when .sigyn.toml is in a git-tracked directory; identity override requires SIGYN_TRUST_PROJECT_CONFIG
Vault key retained after revocationVault key rotated on member revocation; new key re-sealed to remaining members only
Agent socket hijackingSession token required for CACHE command; socket ownership verified before connecting
Device key tamperingStored with BLAKE3 integrity hash; tampered files rejected on load
Identity file tamperingBLAKE3 keyed MAC (using device key) appended and verified on every load
Weak KDF parametersArgon2id parameters validated against minimums (m_cost >= 64 MiB, t_cost >= 3, p_cost >= 1)
Audit log gap injectionSequence continuity verified — gaps in sequence numbers detected and rejected
Unsigned witness logWitness log optionally signed with Ed25519; signature verified on load when verifying key available
Force-push on sync pushPush verifies local HEAD descends from remote HEAD; non-fast-forward rejected unless --force
TOFU pin poisoning on first accessProminent warning banner on first vault access; SIGYN_VERIFY_OWNER env var for automated verification
Malicious EDITOR env varWarning emitted when $EDITOR/$VISUAL points outside standard system paths
Rollback via malformed checkpointInvalid checkpoint OIDs produce an error instead of silently skipping validation
Cloud import argument injectionCloud resource names validated: no leading dashes, control chars, or shell metacharacters
Generated secret leaking to stdoutsecret generate hides value by default; requires --reveal flag
Passphrase lingering in memoryPassphrase strings zeroized at CLI call sites after use
  • Architecture – project structure and module overview
  • CLI Reference – complete command reference
  • Delegation – invitation and revocation deep dive
  • Sync – synchronization and conflict resolution

Multi-Factor Authentication (MFA)

Sigyn supports optional TOTP-based multi-factor authentication to add a second layer of security beyond the identity passphrase.

How It Works

  1. Enrollment – sigyn mfa setup generates a TOTP secret, displays an otpauth:// URI for your authenticator app, and produces 8 single-use backup codes.
  2. Policy enforcement – when require_mfa: true is set on global or per-member constraints, the policy engine requires MFA verification before granting access.
  3. Session grace period – after successful verification, a session is created that lasts 1 hour by default, so you aren’t re-prompted on every operation.
  4. Backup codes – if you lose access to your authenticator, any of the 8 backup codes can be used once in place of a TOTP code.

Enrollment

sigyn mfa setup -i alice

This will:

  • Generate a TOTP secret and display a QR code in the terminal for scanning.
  • The base32 secret is also printed for manual entry if needed.
  • Ask you to enter a code from your authenticator app to verify setup.
  • Print 8 backup codes. Save these in a secure location.

Checking Status

sigyn mfa status -i alice

Shows whether MFA is enrolled, when it was enabled, how many backup codes remain, and whether a session is currently active.

Generating New Backup Codes

sigyn mfa backup -i alice

Requires a valid TOTP code. Generates a fresh set of 8 backup codes and invalidates all previous ones.

Disabling MFA

sigyn mfa disable -i alice

Requires a valid TOTP code or backup code to confirm. Removes the .mfa file and clears any active session.

Enforcing MFA via Policy

Global Enforcement

Set require_mfa: true in the vault’s global_constraints to require MFA for all non-owner members:

{
  "global_constraints": {
    "require_mfa": true,
    "time_windows": [],
    "expires_at": null
  }
}

Per-Member Enforcement

Set require_mfa: true on an individual member’s constraints to require MFA only for that member:

{
  "constraints": {
    "require_mfa": true,
    "time_windows": [],
    "expires_at": null
  }
}

Cryptographic Details

  • MFA state encryption: the .mfa file is encrypted with ChaCha20-Poly1305 using a key derived via HKDF-SHA256 from the identity’s X25519 private key with info context b"mfa-state". No additional passphrase is needed – unlocking the identity is sufficient.
  • Backup code storage: codes are hashed with blake3 before being stored. Each code is consumed (removed) on use.
  • Session tamper detection: session files contain a blake3 keyed hash (using the identity fingerprint as key material) over the verification timestamp. Tampered sessions are rejected.

Audit System

Every operation in Sigyn is recorded in an append-only, hash-chained audit log. Each entry is signed with the acting member’s Ed25519 key.

Audit Entries

Each log entry contains:

FieldDescription
sequenceMonotonic sequence number
timestampISO 8601 UTC timestamp
actorFingerprint of the acting member
actionOperation type (see below)
envEnvironment affected (if applicable)
keySecret key affected (if applicable)
outcomeSuccess or failure with reason
prev_hashblake3 hash of the previous entry
entry_hashblake3 hash of this entry
signatureEd25519 signature over entry_hash

Tracked Actions

  • VaultCreated, VaultDeleted, VaultExported
  • SecretRead, SecretWritten, SecretDeleted
  • SecretsCopied — sigyn secret copy (records keys, source/dest envs)
  • SecretsExported — sigyn run export (records env and format)
  • SecretsInjected — sigyn run exec (records env and command)
  • SecretsServed — sigyn run serve (records env)
  • SecretsListed — sigyn secret list (records env)
  • MemberInvited, MemberRevoked
  • PolicyChanged
  • MasterKeyRotated
  • OwnershipTransferred — records from/to fingerprints
  • OwnershipTransferAccepted — records accepting fingerprint
  • ForkCreated
  • EnvironmentCreated, EnvironmentDeleted, EnvironmentPromoted
  • BreakGlassActivated

Commands

# View recent entries
sigyn audit tail -n 20

# Query by actor, action, or time range
sigyn audit query --actor <fingerprint> --action SecretWritten

# Verify the entire chain (hashes + signatures)
sigyn audit verify

# Export audit log
sigyn audit export --format json > audit-export.json

# Add a witness countersignature to the latest entry
sigyn audit witness

# Anchor to git (embed hash in commit message)
sigyn audit anchor

Tamper Detection

The audit chain is tamper-evident by construction:

  1. Each entry’s prev_hash must match the entry_hash of the preceding entry.
  2. Each entry’s entry_hash must be a valid blake3 hash of the entry content.
  3. Each entry’s signature must be a valid Ed25519 signature from the claimed actor. A signature that fails against a known key is treated as tampering; an entry whose signing key is merely not available on this device is reported as unverifiable (a warning), not as tampering.
  4. All sequence numbers from 0 to N must be present with no gaps, detecting selective deletion of individual entries.
  5. Tail truncation — dropping the most recent entries leaves an otherwise-valid prefix, so Sigyn also records the audit chain tip (sequence, hash) in device-local state that is never synced through the vault’s git repo. On verify (and after pull), the log must still contain that tip; if it no longer does, verification fails.

If any check fails, sigyn audit verify prints the broken link with the sequence number and reason and exits with a non-zero status, so it is safe to use in a gate such as sigyn audit verify && deploy. Unverifiable-key warnings do not cause a non-zero exit.

Witness Countersigning

Witnesses provide independent verification. Any member can countersign an audit entry, creating a witness record that attests they observed the entry at a specific time:

sigyn audit witness

This is useful for compliance workflows where multiple parties must acknowledge sensitive operations.

The witness log itself is encrypted and optionally signed. When signed, the format is SGNW || sig_len(4 LE) || Ed25519_signature || ciphertext. The signature is verified on load when the owner’s verifying key is available, preventing tampering with witness records.

Audit Push Modes

Vaults have a configurable audit push mode, set in the signed policy (owner/admin only):

ModeBehavior
offlineDefault. Audit entries are appended locally; push when convenient via sigyn sync push.
onlineAudit entries must be pushed to the remote after each operation. Operations fail if the push fails (e.g., SSH key locked, network down). Only enforced when a git remote is configured.
best-effortAttempts to push audit entries after each operation. Warns on failure but doesn’t block the operation.
# Set audit mode (requires ManagePolicy access — owner/admin)
sigyn policy audit-mode online
sigyn policy audit-mode best-effort
sigyn policy audit-mode offline

# View current mode
sigyn policy show

Design notes:

  • The mode is part of the signed policy.cbor, so members cannot tamper with it.
  • New vaults default to offline — you must configure a git remote before online mode has any effect.
  • online mode is a compliance guarantee: if audit can’t reach the remote, the operation is rejected. This prevents exfiltration without audit visibility.

Deploy Keys

For online and best-effort modes, you can generate a sealed deploy key so audit push works even when the user’s SSH key is locked or unavailable:

# Generate a deploy key (sealed with vault cipher, stored in vault dir)
sigyn sync deploy-key generate

# Show the public key (add this to your git remote with push access)
sigyn sync deploy-key show-pubkey

# Remove the deploy key
sigyn sync deploy-key remove

The deploy key is:

  • Encrypted at rest with the vault cipher — only vault members can use it
  • Ed25519 — passwordless, no passphrase prompt
  • Scoped — should be added as a deploy key with push-only access to the audit/vault repo, not as a personal key
  • Not stored in plaintext — the raw key bytes are sealed in deploy_key.sealed

When a deploy key exists, audit push uses it automatically. When absent, it falls back to the user’s SSH agent / credential helper.

Storage

The audit log is stored as JSON Lines in audit.log.json within the vault directory. Each line is individually encrypted with a vault-derived cipher (HKDF from the vault key with context sigyn-audit-v1) and base64-encoded. This format is streamable and compatible with external log analysis tools after decryption.

Disaster Recovery

Sigyn includes a standalone recovery binary (sigyn-recovery) and Shamir secret sharing for disaster recovery scenarios.

Shamir Secret Sharing

Sigyn can split the vault master key (or identity private key) into K-of-N shards using Shamir’s Secret Sharing over GF(256). Any K shards are sufficient to reconstruct the secret; fewer than K reveal nothing.

Creating Recovery Shards

# Split into 5 shards, requiring any 3 to recover
sigyn recovery shards print --threshold 3 --total 5

This outputs shard data that can be:

  • Printed on paper and stored in separate secure locations
  • Encoded as QR codes for easy scanning
  • Distributed to trusted parties

Recovering from Shards

sigyn-recovery restore --shards shard1.json,shard2.json,shard3.json

The standalone sigyn-recovery binary reconstructs the identity from K shards without needing an unlocked identity. On success it prompts for a new passphrase and installs the recovered identity directly into the identity store at ~/.sigyn/identities/<fingerprint>.identity (honoring SIGYN_HOME), so it is immediately usable by the sigyn CLI — no separate import step. It refuses to overwrite an existing identity file unless --force is given.

To write the recovered identity somewhere else instead of installing it, pass --output <path> (also guarded against overwriting).

Reconstruction is verified: duplicate or zero shard indices, an inconsistent threshold, and a mismatched authenticator are all rejected rather than silently producing a wrong key.

Vault Snapshots

Since vaults are stored as files in a git repository, every commit is a recoverable snapshot:

# List available snapshots
sigyn recovery snapshots

# Restore from a specific snapshot
sigyn recovery snapshots --restore <commit-hash>

Recovery Binary

The sigyn-recovery binary is a minimal standalone tool that can:

  1. Reconstruct secrets from Shamir shards
  2. Restore a vault from a git snapshot
  3. Operate without an existing Sigyn installation

It is intentionally kept separate from the main CLI to reduce the attack surface and ensure it works even if the primary installation is compromised.

Best Practices

  • Store shards in geographically separate locations
  • Use a threshold that balances availability and security (e.g., 3-of-5)
  • Test recovery procedures periodically
  • Keep the sigyn-recovery binary alongside your shard backups
  • Document your recovery plan and shard holder contacts outside of Sigyn

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.

Configuration

Sigyn stores its configuration and data under ~/.sigyn/.

Directory Layout

~/.sigyn/
├── config.toml                    # Global configuration
├── project.toml                   # User-level project defaults (optional)
├── identities/
│   └── <fingerprint>.identity     # Encrypted identity files
└── vaults/
    └── <vault-name>/
        ├── vault.toml             # Vault manifest (unencrypted metadata)
        ├── members.cbor           # Envelope header (encrypted master key slots)
        ├── policy.cbor            # Encrypted policy blob
        ├── envs/
        │   ├── dev.vault          # Encrypted environment files (CBOR)
        │   ├── staging.vault
        │   └── prod.vault
        ├── audit.log.json         # Append-only audit log (JSON Lines)
        └── forks.cbor             # Fork registry

config.toml

The global configuration file is created with sigyn init:

sigyn init --identity alice --vault myapp
default_identity = "alice"
default_vault = "myapp"

Project Config (.sigyn.toml)

For per-project defaults, create a .sigyn.toml file in your project root. This eliminates the need to pass --vault, --env, and --identity flags on every command.

Sigyn searches for this file starting in the current directory and walking up to parent directories, so it works from any subdirectory of your project.

You can also place the same file at ~/.sigyn/project.toml as a user-level fallback (useful for defaults you don’t want to commit to a repo). The project-local file takes precedence over the user-level one.

Creating a project config

The easiest way is to use sigyn project init:

# Interactive — prompts for vault and identity
sigyn project init

# Non-interactive
sigyn project init -v myapp -e dev -i alice

# Write to ~/.sigyn/project.toml instead (user-level fallback)
sigyn project init --global

Or create .sigyn.toml by hand:

[project]
vault = "myapp"
env = "dev"
identity = "alice"

[commands]
dev = "npm run dev"
app = "./start-server"
migrate = "python manage.py migrate"

This file is safe to commit to git — it contains no secrets, only references to vault and identity names.

[project] table

KeyTypeDescription
vaultstringDefault vault for this project
envstringDefault environment for this project
identitystringDefault identity for this project

All fields are optional. You can specify just the ones you want as defaults. For example, if everyone on your team uses the same vault but different identities:

[project]
vault = "myapp"
env = "dev"

[commands] table

Named commands that can be invoked with sigyn run <name>. The value is the command string to execute with secrets injected as environment variables. Extra arguments passed after the command name are appended to the command string.

[commands]
dev = "npm run dev"
app = "./start-server"
migrate = "python manage.py migrate"
test = "cargo test"
db = "psql"
sigyn run dev              # runs 'npm run dev' with secrets
sigyn run app --prod       # runs './start-server' with prod env secrets
sigyn run migrate          # runs 'python manage.py migrate' with secrets
sigyn run test -- --lib    # runs 'cargo test --lib' with secrets

The --prod and --staging flags are shorthand for --env prod and --env staging:

sigyn run dev --prod       # overrides the default env to prod

Resolution priority

Settings are resolved in this order (highest to lowest):

  1. CLI flags (--vault, -v, --env, -e, --identity, -i)
  2. Project config (.sigyn.toml in current or parent directory)
  3. User project config (~/.sigyn/project.toml)
  4. Global config (~/.sigyn/config.toml)
  5. Hardcoded defaults

Example workflows

Zero-flag daily usage:

# With .sigyn.toml in your project root, just:
sigyn secret list              # lists secrets in default vault/env
sigyn secret get DATABASE_URL  # gets a secret from default vault/env
sigyn run dev                  # starts dev server with secrets injected

Multiple environments:

# .sigyn.toml defaults to dev, override per-command:
sigyn secret list --prod
sigyn run app --staging
sigyn secret get API_KEY -e prod

Monorepo with multiple services:

monorepo/
├── .sigyn.toml          # vault = "shared", env = "dev"
├── services/
│   ├── api/
│   │   └── .sigyn.toml  # vault = "api-service", env = "dev"
│   └── web/
│       └── .sigyn.toml  # vault = "web-app", env = "dev"

Each service can have its own .sigyn.toml pointing to a different vault. Sigyn finds the nearest config file by walking up from the current directory.

Global CLI Flags

These flags override config file defaults for a single invocation:

FlagShortDescription
--vault <name>-vOverride the default vault
--env <name>-eOverride the default environment
--identity <name>-iOverride the default identity
--jsonOutput as JSON instead of formatted text
--quietSuppress non-essential output
--dry-runPreview changes without applying

vault.toml

Each vault has a manifest file with unencrypted metadata:

vault_id = "550e8400-e29b-41d4-a716-446655440000"
name = "myapp"
owner = "a1b2c3d4e5f6..."
environments = ["dev", "staging", "prod"]
created_at = "2025-03-15T10:30:00Z"

This file is safe to commit to git — it contains no secrets.

Shell Completions

Generate completions for your shell:

# Bash
sigyn completions bash >> ~/.bashrc

# Zsh
sigyn completions zsh >> ~/.zshrc

# Fish
sigyn completions fish > ~/.config/fish/completions/sigyn.fish

# PowerShell
sigyn completions powershell >> $PROFILE

Health Check

Run sigyn doctor to verify your installation:

sigyn doctor

This checks:

  • Identity files exist and are readable
  • Default vault is accessible
  • Audit chain integrity
  • Expiring secrets
  • Sync status

Development Guide

This guide is for developers who want to hack on Sigyn’s codebase.

Technical Principles

  1. I/O Separation: sigyn-core MUST NOT perform any I/O (filesystem, network). All I/O lives in sigyn-engine. This makes the core library publishable, testable, and portable.
  2. Safety First: Always use secrecy::Secret<T> for sensitive data. Ensure zeroization on drop.
  3. Atomic Operations: Filesystem operations in the CLI should be atomic. Use tempfile and persist().
  4. Typed Errors: Use thiserror in the core library for precise error handling. Use anyhow in the CLI for context-rich error reporting.
  5. Synchronous Core: The core library is synchronous to keep it simple and avoid async overhead where not needed. The CLI handles async tasks (like networking) using tokio.

Codebase Walkthrough

sigyn-core (Pure Library)

Zero I/O dependencies. Publishable to crates.io. Contains:

  • crypto/: Low-level cryptographic primitives. We use x25519-dalek for key exchange and chacha20poly1305 for symmetric encryption.
  • policy/: The RBAC and ACL engine. This is the “brain” that decides if an operation is allowed.
  • sync/: CRDT-based synchronization logic. It uses vector clocks to detect conflicts.
  • vault/: Vault manifest types and env file encryption/decryption (pure computation only).
  • secrets/, identity/, delegation/, forks/, rotation/, audit/, hierarchy/, environment/: Types, algorithms, and pure logic.

sigyn-engine (I/O Layer)

Depends on and re-exports sigyn-core. Contains all filesystem and git operations:

  • vault/: Env file read/write, vault locking (fd-lock), vault path resolution.
  • sync/: Git-based sync engine (git2).
  • audit/: File-based audit log, git anchoring, witness log persistence.
  • identity/: Identity store, MFA store, session store (all filesystem).
  • hierarchy/: Hierarchy paths, slot management, git remote resolution.
  • forks/: Fork creation with filesystem operations.

sigyn-cli

The CLI uses clap for argument parsing.

  • commands/: Each file here corresponds to a major subcommand (e.g., secret, vault, identity).
  • inject/: Logic for injecting secrets into processes via environment variables or Unix sockets.
  • tui/: The interactive TUI built with ratatui.

How-To Guides

Adding a New Secret Type

  1. Update the SecretValue enum in crates/sigyn-core/src/secrets/types.rs.
  2. Implement validation logic in crates/sigyn-core/src/secrets/validation.rs.
  3. Update the CLI to support the new type in crates/sigyn-cli/src/commands/secret.rs.

Adding a New Import Source

  1. Add a new module in crates/sigyn-cli/src/importexport/.
  2. Implement the Importer trait (or follow the pattern of existing importers like doppler.rs).
  3. Register the new source in crates/sigyn-cli/src/commands/import.rs.

Working on the TUI

The TUI uses ratatui with crossterm as the backend.

  • The entry point is crates/sigyn-cli/src/tui/mod.rs.
  • We use a component-based approach for different screens (Vault list, Secret list, etc.).

To test the TUI during development, you can run:

cargo run -- tui

Testing Strategy

Sigyn uses 5 test layers: unit tests, integration tests, CLI workflow tests, E2E tests, and adversarial security tests.

# Run all tests (fast KDF for speed)
cargo test --all --features sigyn-cli/fast-kdf

# E2E test (requires release build)
cargo build --release && bash tests/e2e_test.sh

# Adversarial security test
bash tests/adversarial_test.sh

See the Testing page for the full testing guide, including how to write tests and CI integration.

Debugging

You can use the RUST_LOG environment variable to see detailed logs (if implemented):

RUST_LOG=debug sigyn vault list

Benchmarking

We use criterion for benchmarking performance-critical parts like the CRDT merge and encryption.

cargo bench

Testing

Sigyn uses a multi-layered testing strategy: unit tests, integration tests, CLI workflow tests, end-to-end tests, and adversarial security tests.

Quick Reference

# Unit + integration tests (fast, recommended during development)
cargo test --all --features sigyn-cli/fast-kdf

# Single crate
cargo test -p sigyn-core
cargo test -p sigyn-engine
cargo test -p sigyn-integration-tests

# Lint
cargo clippy -- -D warnings

# Format check
cargo fmt --all -- --check

# E2E test (requires release build + GitHub repos)
cargo build --release
bash tests/e2e_test.sh

# Adversarial security test (requires release build)
cargo build --release
bash tests/adversarial_test.sh

Test Layers

1. Unit Tests

Colocated with source code (#[cfg(test)] modules). Run with cargo test.

Focus areas:

  • Cryptographic roundtrips (encrypt→decrypt, sign→verify)
  • Policy evaluation edge cases (role hierarchy, delegation depth)
  • CRDT merge logic and conflict resolution
  • Validation functions (key names, vault names, env names)
  • Serialization/deserialization (CBOR, TOML, JSON)

Key flag: Use --features sigyn-cli/fast-kdf to use lightweight Argon2 parameters during tests. Without this flag, identity creation takes ~650ms per test (production KDF strength).

# All unit tests with fast KDF
cargo test --all --features sigyn-cli/fast-kdf

# Run a specific test
cargo test --all --features sigyn-cli/fast-kdf test_valid_delegation

2. Integration Tests

Located in tests/integration/src/. These test cross-crate interactions using sigyn-engine directly (no CLI).

ModuleCoverage
vault_lifecycle.rsCreate, open, delete vaults
member_access.rsMember key slots, access control
delegation_invite.rsInvitation create, accept, verify
revocation.rsRevoke, cascade revoke, key rotation
policy_constraints.rsRole checks, delegation depth, delegatee limits
audit_chain.rsHash chain integrity, signature verification
secret_types.rsSecret value types and validation
env_promotion.rsEnvironment copy, promote, diff
forks_leash.rsFork creation and management
cargo test -p sigyn-integration-tests

3. CLI Workflow Tests

Located in crates/sigyn-cli/tests/cli_workflows.rs. Uses assert_cmd to test the compiled binary.

Tests actual CLI commands in an isolated SIGYN_HOME:

  • Identity CRUD
  • Vault creation and listing
  • Secret set/get/list/remove
  • Delegation invite/accept/revoke
  • Error messages and edge cases
cargo test -p sigyn-cli --test cli_workflows

4. End-to-End Test (tests/e2e_test.sh)

A comprehensive bash script that tests all Sigyn features with 7 team personas across 3 vaults. Runs ~170 tests across 19 phases.

Prerequisites:

  • Release build: cargo build --release
  • gh CLI authenticated (for GitHub repo operations)
  • Git SSH access configured
  • python3 available

What it tests:

PhaseTestsCoverage
Identity9Create 7 personas, list, show
Vaults93 vaults, custom envs, TOFU
Delegation12Direct, chained, bulk invite
Secrets34CRUD, RBAC, generate, search, import
Env Ops7Diff, clone, promote, copy
Sync9Configure, push, pull, status
Run & Export105 export formats, injection
Audit6Tail, verify, export
Rotation6Manual, schedule, due
Advanced Delegation10Cascade, env revoke, policy
Security22Tamper, traversal, RBAC boundaries

Running:

# Full run (creates isolated SIGYN_HOME in /tmp)
bash tests/e2e_test.sh

# The script is idempotent — safe to re-run

Output: Color-coded results with pass/fail/skip counts and a summary at the end.

Test helpers in the script:

HelperUsage
assert_ok "desc" cmd...Assert command succeeds (exit 0, no error text)
assert_fail "desc" cmd...Assert command fails (non-zero exit or error text)
assert_contains "desc" "needle" cmd...Assert stdout contains text
assert_not_contains "desc" "needle" cmd...Assert stdout does NOT contain text

5. Adversarial Security Test (tests/adversarial_test.sh)

Simulates a malicious actor attempting to break vault security. Tests 32+ attack vectors across 10 categories.

Prerequisites:

  • Release build: cargo build --release
  • python3 (for bit-flip tampering)

Attack categories:

CategoryAttacksDescription
Unauthorized access4Rogue identity, wrong passphrase, env boundaries
File tampering7Bit-flip on all file types, zero-fill, truncate, trailing junk
Privilege escalation5Contributor invite/revoke/cross-env, non-member write/list
Stolen files2Copied vault, copied identity + wrong passphrase
Replay attacks3Replay used invitation, tamper invitation, forge invitation
Audit attacks2Delete audit log, corrupt entries
Path traversal3Symlink, vault name traversal, env name traversal
Crypto exposure3Plaintext in files, plaintext keys, passphrase leak to child
Cross-vault3Swap headers, policies, env files between vaults
Post-revocation2Read/write after revocation

Running:

# Run with default binary location
bash tests/adversarial_test.sh

# Run with custom binary
bash tests/adversarial_test.sh ./target/debug/sigyn

Exit code: 0 if all attacks defended, non-zero = number of breaches found.


Writing Tests

Unit test conventions

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    // Helper to create test fingerprints
    fn test_fp(byte: u8) -> KeyFingerprint {
        KeyFingerprint([byte; 16])
    }

    #[test]
    fn test_my_feature() {
        // Arrange → Act → Assert
    }
}
}

CLI workflow test pattern

#![allow(unused)]
fn main() {
#[test]
fn test_something() {
    let home = fresh_home();       // Creates isolated SIGYN_HOME in /tmp
    setup_identity(&home);         // Creates "alice" identity
    setup_vault(&home);            // Creates "test-vault" with dev env

    sigyn(&home)
        .args(["secret", "set", "KEY", "VALUE", "-v", "test-vault", "-e", "dev"])
        .assert()
        .success();

    sigyn(&home)
        .args(["secret", "get", "KEY", "-v", "test-vault", "-e", "dev"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALUE"));
}
}

E2E test pattern

# In tests/e2e_test.sh — add to the appropriate phase section:

assert_ok "My new feature works" \
    "$SIGYN" my-command --flag value -v vault-name -i alice

assert_fail "My new feature rejects bad input" \
    "$SIGYN" my-command --flag invalid -v vault-name -i alice

assert_contains "Output includes expected text" "expected-text" \
    "$SIGYN" my-command -v vault-name -i alice

CI Integration

Tests run automatically in CI. The recommended CI configuration:

jobs:
  test:
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - run: cargo test --all --features sigyn-cli/fast-kdf
      - run: cargo clippy -- -D warnings
      - run: cargo fmt --all -- --check

For E2E and adversarial tests in CI, build the release binary first:

  e2e:
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - run: cargo build --release
      - run: bash tests/adversarial_test.sh

Test Files

tests/
├── e2e_test.sh              # End-to-end bash test (170 tests, 19 phases)
├── adversarial_test.sh       # Adversarial security test (32+ attacks)
├── E2E_TEST_PLAN.md          # Detailed test plan specification
├── E2E_TEST_RESULTS.md       # Latest test results and bug tracking
└── integration/
    └── src/
        ├── lib.rs            # Module registry
        ├── audit_chain.rs
        ├── delegation_invite.rs
        ├── env_promotion.rs
        ├── forks_leash.rs
        ├── member_access.rs
        ├── policy_constraints.rs
        ├── revocation.rs
        ├── secret_types.rs
        └── vault_lifecycle.rs

crates/sigyn-cli/tests/
    └── cli_workflows.rs      # CLI command workflow tests (assert_cmd)

Contributing to Sigyn

First off, thank you for considering contributing to Sigyn! It’s people like you who make Sigyn such a great tool.

Code of Conduct

Please be respectful and professional in all interactions.

How Can I Contribute?

Reporting Bugs

  • Use the GitHub issue tracker.
  • Check if the bug has already been reported.
  • Include a clear description, steps to reproduce, and your environment (OS, Rust version).

Suggesting Enhancements

  • Open a GitHub issue to discuss the enhancement.
  • Provide a clear use case for why this feature is needed.

Pull Requests

  • Fork the repository.
  • Create a new branch for your feature or bugfix.
  • Ensure your code follows the existing style.
  • Write tests for your changes.
  • Ensure all tests pass: cargo test --all --features sigyn-cli/fast-kdf
  • Ensure the code is linted: cargo clippy -- -D warnings
  • Ensure the code is formatted: cargo fmt --all -- --check
  • Submit a pull request.

Development Setup

Prerequisites

  • Rust 1.75 or later.
  • Git.

Building from Source

git clone https://github.com/tonybenoy/sigyn.git
cd sigyn
cargo build

Running Tests

Run all tests (the fast-kdf feature uses lightweight Argon2 params to speed up tests):

cargo test --all --features sigyn-cli/fast-kdf

Run integration tests:

cargo test -p sigyn-integration-tests

Linting and Formatting

We use clippy and rustfmt to maintain code quality.

cargo clippy -- -D warnings
cargo fmt --all

Project Structure

  • crates/sigyn-core: Pure library (publishable) – cryptography, policy, CRDT, types. Zero I/O dependencies.
  • crates/sigyn-engine: I/O layer – filesystem, git sync, audit persistence. Depends on and re-exports sigyn-core.
  • crates/sigyn-cli: The main CLI application.
  • crates/sigyn-recovery: A standalone recovery tool.
  • tests/integration: Integration tests that exercise the full CLI.

Adding a New Command

  1. Add the command definition in crates/sigyn-cli/src/commands/mod.rs (if it’s a new group) or in the relevant subcommand file (e.g., crates/sigyn-cli/src/commands/secret.rs).
  2. Implement the logic in the command file.
  3. Add an integration test in tests/integration/ to verify the new command works as expected.

Documentation

The documentation is located in the docs/ directory and is served via mdBook from docs-site/.

To build the docs site locally:

cd docs-site
mdbook serve

Thank you for contributing!