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 -- ./appinjects 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 servefor zero-exposure secret access. - Web GUI. Browser-based dashboard for visual secret management (
sigyn web). - Project config. Drop a
.sigyn.tomlin 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
.envfiles 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 – install Sigyn and create your first vault
- CLI Reference – complete command documentation
- Configuration – global and project config options
- Security Model – cryptographic design and threat model
- Examples – CI/CD integration, Docker, and team workflows
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
One-liner (recommended)
# 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:
- Generate a TOTP secret and display a QR code in the terminal (plus the base32 secret for manual entry).
- Ask you to enter a code from your authenticator app to verify.
- 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
- CLI Reference – complete command documentation
- Security Model – cryptographic primitives, MFA, and threat model
- Delegation – invitation flow and cascade revocation
- Sync – conflict resolution and CRDT merge
- Set up Shamir recovery shards for disaster recovery:
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:
| Flag | Short | Description |
|---|---|---|
--vault <NAME> | -v | Vault name (overrides default from config) |
--env <NAME> | -e | Environment name (overrides default from config) |
--identity <NAME> | -i | Identity name or fingerprint (overrides default) |
--json | Output as JSON | |
--quiet | Suppress non-essential output | |
--dry-run | Preview changes without applying | |
--verbose | Show detailed config resolution and debug output | |
--no-project-config | Skip 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:
| Command | Alias |
|---|---|
identity | id |
vault | v |
secret | s |
env | e |
policy | p |
sync | sy |
audit | a |
fork | f |
run | r |
rotate | rot |
import | imp |
delegation | member |
notification | notif |
context | ctx |
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
| Variable | Description |
|---|---|
SIGYN_HOME | Override the Sigyn home directory (default: ~/.sigyn) |
SIGYN_NON_INTERACTIVE | Disable all interactive prompts (equivalent to piping to a non-TTY) |
CI | When 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
| Flag | Short | Description |
|---|---|---|
--copy | -c | Copy 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
| Flag | Short | Description |
|---|---|---|
--vaults | List vaults instead of secrets | |
--envs | List environments instead of secrets | |
--reveal | -r | Show 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]
| Flag | Short | Description |
|---|---|---|
--name <NAME> | -n | Required. Name for this identity |
--email <EMAIL> | -E | Optional 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
| Flag | Description |
|---|---|
--force | Skip 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 importcommand. Public keys are exchanged through delegation: the owner runssigyn delegation invite --pubkey <fingerprint> …and the invitee accepts withsigyn 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
| Flag | Description |
|---|---|
--org <PATH> | Link vault to an org hierarchy path |
--split-audit | Create 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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--force | Skip 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
| Flag | Description |
|---|---|
--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
| Flag | Short | Description |
|---|---|---|
--output | -o | Output file path for the tar.gz archive |
--force | Overwrite 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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--env, -e <ENV> | Target environment (default: dev) |
--force | Overwrite 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
| Flag | Short | Description |
|---|---|---|
--env | -e | Target environment (default: dev) |
--copy | -c | Copy 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
| Flag | Short | Description |
|---|---|---|
--env | -e | Target environment (default: dev) |
--reveal | -r | Show 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
| Flag | Short | Description |
|---|---|---|
--length | -l | Length of generated value (default: 32) |
--type | -t | Generation type: password, uuid, hex, base64, alphanumeric (default: password) |
--env | -e | Target 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
| Flag | Short | Description |
|---|---|---|
--env | -e | Target environment (default: dev) |
secret search
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
| Flag | Short | Description |
|---|---|---|
--reveal | -r | Show 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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--reveal | Show 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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--force | Skip 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_*'
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Short | Description |
|---|---|---|
-n | Number 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
| Mode | Behavior |
|---|---|
offline | Default. Audit entries appended locally; push when convenient. |
online | Audit entries must be pushed after each operation. Operations fail if push fails. |
best-effort | Try 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
| Flag | Description |
|---|---|
--identity, -i <NAME> | Identity to enroll (uses default if omitted) |
--json | Output 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
| Flag | Description |
|---|---|
--pubkey <FP> | Invitee’s public key fingerprint |
--role <ROLE> | Role to assign (default: readonly) |
--envs <E1,E2> | Allowed environments, comma-separated (default: *) |
--save-to-vault | Also 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
| Flag | Description |
|---|---|
--file <PATH> | Path to JSON file with member definitions |
--force | Skip 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
| Flag | Description |
|---|---|
--file <PATH> | Path to JSON file with fingerprint list |
--cascade | Also revoke all members they transitively invited |
--force | Skip 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
| Flag | Description |
|---|---|
-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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Short | Description |
|---|---|---|
--vault | -v | Vault name (skips prompt) |
--env | -e | Environment name (default: dev) |
--identity | -i | Identity name (skips prompt) |
--global | Write 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
| Flag | Short | Description |
|---|---|---|
--env | -e | Environment to load secrets from |
--clean | -c | Do not inherit the parent process environment |
--prod | Shorthand for --env prod | |
--staging | Shorthand 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
| Flag | Short | Description |
|---|---|---|
--env | -e | Environment to export |
--format | -f | Output format: dotenv, json, shell, docker, k8s (default: dotenv) |
--name | Resource 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
| Flag | Short | Description |
|---|---|---|
--interval <SECS> | Poll interval in seconds (default: 2) | |
--clean | -c | Don’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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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).
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--port <PORT> | Port to listen on (default: 9847) |
--timeout <MINUTES> | Session timeout in minutes (default: 30) |
--open | Open 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.
Related Documentation
- Getting Started – step-by-step tutorial
- Architecture – project structure and design decisions
- Security Model – cryptographic primitives and threat model
- Organizations – hierarchical org structure and inherited RBAC
- Delegation – invitation and revocation system
- Sync – synchronization and conflict resolution
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
| Flag | Description |
|---|---|
--port <PORT> | Port to listen on (default: 9847) |
--timeout <MINUTES> | Session timeout in minutes (default: 30) |
--open | Open 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-enginedirectly - 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=Strictcookies (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:
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/auth/login | No | {fingerprint, passphrase} – create session |
POST | /api/auth/logout | Yes | Destroy session |
GET | /api/auth/status | Yes | Check session, return identity info |
GET | /api/identities | No | List identities (public info only) |
GET | /api/vaults | Yes | List vault names |
GET | /api/vaults/:name | Yes | Vault info (triggers unlock) |
GET | /api/vaults/:vault/envs/:env/secrets | Yes | List secrets (values masked) |
GET | /api/vaults/:vault/envs/:env/secrets/:key | Yes | Read secret value |
POST | /api/vaults/:vault/envs/:env/secrets | Yes | Set secret {key, value, secret_type} |
DELETE | /api/vaults/:vault/envs/:env/secrets/:key | Yes | Delete secret |
GET | /api/vaults/:vault/audit?limit=50 | Yes | Audit log entries |
Comparison with TUI
| Feature | TUI (sigyn tui) | Web GUI (sigyn web) |
|---|---|---|
| Read secrets | View keys (values masked) | View keys + reveal values |
| Write secrets | Not supported | Full CRUD |
| Members | Read-only list | Read-only list |
| Audit log | Last 20 entries | Paginated (up to 100) |
| Environments | Fixed at launch | Switchable via dropdown |
| Build requirement | Default build | --features web |
| External deps | None | Browser |
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 decryptprodsecrets, 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 keysGET <key>— returns the value for a keyQUIT— 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
Related Documentation
- Getting Started – basic setup tutorial
- CLI Reference – full command reference
- Sync – git sync and conflict resolution
- Configuration – config file details
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:
- Highest role wins: when a member appears at multiple levels, the highest role is used for access decisions.
- Permission union: allowed environments and secret patterns are unioned across all levels in the chain.
- 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:
| Field | Description |
|---|---|
node_id | Unique UUID |
name | Human-readable node name |
node_type | Node kind: org, division, team, or any custom string |
parent_id | UUID of the parent node (None for root orgs) |
owner | BLAKE3 fingerprint of the node creator |
children | List of ChildRef entries (id, name, type) |
created_at | UTC creation timestamp |
description | Optional description |
git_remote | Optional 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
| Flag | Description |
|---|---|
--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
| Flag | Description |
|---|---|
--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 *.
| Level | Envs | Merged |
|---|---|---|
acme | staging, prod | |
acme/platform | – | |
acme/platform/web | dev | dev, 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
acmegives them a key slot for theacmenode only. - The member must also have slots at child nodes to decrypt those policies.
- The
org policy member-addcommand 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
| Flag | Description |
|---|---|
--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
| Command | Description |
|---|---|
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 |
Related Documentation
- Delegation – invitation and revocation system
- Sync – git-based synchronization
- Security Model – encryption and access control
- CLI Reference – complete command reference
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:
- CI identity — a dedicated Sigyn identity for your pipeline (least-privilege, scoped to specific environments)
- CI bundle — a single base64 string containing the identity file, device key, and fingerprint
- 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 Secret | Source | Description |
|---|---|---|
SIGYN_CI_BUNDLE | Output of sigyn ci setup | Base64-encoded JSON containing identity file, device key, and fingerprint |
SIGYN_PASSPHRASE | Your CI identity’s passphrase | Used to decrypt the identity’s private key |
VAULT_SSH_KEY | SSH deploy key for the vault repo | Must 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
| Input | Required | Default | Description |
|---|---|---|---|
bundle | Yes | — | CI bundle from sigyn ci setup |
passphrase | Yes | — | Identity passphrase |
vault-ssh-key | Yes | — | SSH private key for cloning the vault repo |
vault-repo | Yes | — | Git URL for the vault repository (e.g., [email protected]:myorg/vaults.git) |
vault | Yes | — | Vault name |
environment | Yes | — | Environment name (e.g., dev, staging, prod) |
export | No | env | Export mode: env, dotenv, json, or mask-only |
dotenv-path | No | .env | Output path for dotenv or json export modes (must be a relative path) |
keys | No | "" (all) | Comma-separated list of specific secret keys to export |
mask | No | true | Mask all secret values in GitHub Actions logs |
version | No | latest | Sigyn version to install |
Outputs
| Output | Description |
|---|---|
secrets-json | Compact JSON of exported secrets (env/json modes only). Warning: accessible to all subsequent steps in the job — use key filtering to limit exposure. |
count | Number of secrets exported |
Export Modes
| Mode | Behavior |
|---|---|
env (default) | Writes secrets to $GITHUB_ENV — available as environment variables in all subsequent steps |
dotenv | Writes a .env file to the path specified by dotenv-path |
json | Writes a JSON file to the path specified by dotenv-path; also sets secrets-json output |
mask-only | Masks secret values in GitHub Actions logs but does not export them anywhere |
What the Action Does
The composite action performs five steps:
- Install Sigyn — downloads and installs the Sigyn binary (prefers the install script bundled with the action ref; version controlled via the
versioninput) - Restore identity — decodes the CI bundle, validates the fingerprint format, writes the identity file and device key to
$SIGYN_HOME - 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
- Export secrets — runs
sigyn run export --format json, optionally filters by key, masks values in logs, and writes to the chosen destination - 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:
| Variable | Description |
|---|---|
SIGYN_CI_BUNDLE | Output of sigyn ci setup (base64-encoded JSON) |
SIGYN_PASSPHRASE | CI identity passphrase |
VAULT_SSH_KEY | SSH private key for cloning the vault repo |
Template variables
| Variable | Required | Default | Description |
|---|---|---|---|
SIGYN_VAULT | Yes | — | Vault name |
SIGYN_ENVIRONMENT | Yes | — | Environment name (e.g. dev, staging, prod) |
SIGYN_VAULT_REPO | Yes | — | Git URL for the vault repository |
SIGYN_EXPORT_MODE | No | dotenv | Export mode: env, dotenv, or json |
SIGYN_DOTENV_PATH | No | .env | Output path for dotenv/json export |
SIGYN_KEYS | No | "" (all) | Comma-separated list of specific keys to export |
SIGYN_VERSION | No | latest | Sigyn version to install |
SIGYN_INSTALL_URL | No | GitHub | Override URL for install.sh (for self-hosted or mirrored setups) |
Note: The template downloads
install.shand release binaries from GitHub by default. If your GitLab runners cannot reachgithub.com, setSIGYN_INSTALL_URLto a self-hosted mirror, or pre-install thesigynbinary in your CI image.
Export modes
| Mode | Behavior |
|---|---|
env | Sources 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 |
json | Writes 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:
- Install Sigyn — downloads and installs the Sigyn binary
- Restore identity — decodes the CI bundle, validates the fingerprint, writes identity and device key
- Clone vault — validates the vault name, sets up SSH with strict host key checking, clones the vault
- Export secrets — runs
sigyn run export --format json, optionally filters by key, writes to the chosen destination - 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:
| Variable | Description |
|---|---|
SIGYN_CI_BUNDLE | Output of sigyn ci setup (base64-encoded JSON) |
SIGYN_PASSPHRASE | CI identity passphrase |
VAULT_SSH_KEY | SSH private key for cloning the vault repo |
Pipe variables
| Variable | Required | Default | Description |
|---|---|---|---|
VAULT_REPO | Yes | — | Git URL for the vault repository |
VAULT | Yes | — | Vault name |
ENVIRONMENT | Yes | — | Environment name (e.g. dev, staging, prod) |
EXPORT_MODE | No | dotenv | Export mode: env, dotenv, or json |
DOTENV_PATH | No | .env | Output path for dotenv/json export |
KEYS | No | "" (all) | Comma-separated list of specific keys to export |
VERSION | No | latest | Sigyn version to install |
SIGYN_INSTALL_URL | No | GitHub | Override URL for install.sh (for self-hosted or mirrored setups) |
Note: The pipe downloads
install.shand release binaries from GitHub by default. If your Bitbucket runners cannot reachgithub.com, setSIGYN_INSTALL_URLto a self-hosted mirror, or use a custom Docker image withsigynpre-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:
- Install Sigyn:
curl -fsSL https://raw.githubusercontent.com/tonybenoy/sigyn/main/install.sh | sh - Decode the bundle and restore identity files
- Clone the vault repo
- Run
sigyn run exportorsigyn run execto inject secrets - 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=yeswith 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-pathmust be a relative path with no..components to prevent writing secrets to arbitrary locations - Stderr suppression —
sigyn run exportstderr 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,prodinstead 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_KEYsecret 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_PASSPHRASEmatches the passphrase set when the CI identity was created - Ensure
SIGYN_CI_BUNDLEhasn’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
vaultinput 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 listthat the identity has access to the environment - If the delegation was created with
--envs staging, the identity cannot accessprod
Secrets not available in subsequent steps
- GitHub Actions:
envmode writes to$GITHUB_ENV, which is only available in subsequent steps, not the same step - GitLab CI:
envmode uses dotenv artifact reports — downstream jobs must declareneeds:to receive the variables - Bitbucket Pipelines: Use
source .envafter the pipe step to load secrets into the current shell - Verify the export step completed successfully (check for errors in the logs)
- Check the
countoutput — if it’s0, 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 --jsonand 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: latestfails to download, the install script may be unreachable. Pin a specific version as a fallback. - All integrations require
curl,jq, andgit— 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.
Solution 1: Process Injection (Recommended)
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
| Method | Secrets in env? | Secrets on disk? | Agent can read? |
|---|---|---|---|
.env + source | Yes | Yes | Yes |
export KEY=val | Yes | No | Yes |
sigyn run exec | Child only | No | No |
sigyn run --clean | Child only | No | No |
sigyn run serve | Never | No | No |
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.
Recommended Setup
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
- Rotate the secret in Sigyn:
sigyn rotate key DB_PASSWORD --env prod - 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:
- Creating an identity (if none exists)
- Creating a vault (auto-detects project name)
- Importing
.envfiles found in the current directory - 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
Batch Editing and Cross-Environment Search
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 justcdinto a repo andsigyn run devpicks up the right vault, env, and identity automatically. - Cascading RBAC — add someone at
acmeand they get access everywhere; add them atacme/frontendand they only see frontend vaults. - Independent environments —
api-servicecan be onstagingwhiledashboardis still ondev.
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
- Create 5 shards, requiring 3 to recover:
sigyn-recovery split --identity alice --threshold 3 --total 5 --output ./shards - 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.
| Sigyn | Doppler | AWS Secrets Manager | HashiCorp Vault | |
|---|---|---|---|---|
| Self-hosted | Yes (git) | No | No | Yes |
| Server required | No | Yes (SaaS) | Yes (AWS) | Yes |
| Cost | Free | Paid | Per-secret pricing | Free (OSS) / Paid |
| Encryption | E2E (client-side) | Server-side | Server-side | Server-side |
| Sync | Git | API | API | API |
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):
- CLI flags (
--vault,--env,--identity) .sigyn.tomlin current or parent directory~/.sigyn/project.toml~/.sigyn/config.toml- 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:
- Least privilege: members can only grant roles at or below their own level.
- Traceability: every delegation is recorded, forming an auditable tree.
- 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:
| Field | Description |
|---|---|
id | Unique UUID for this invitation |
vault_name | Name of the vault being shared |
vault_id | UUID of the vault |
inviter_fingerprint | BLAKE3 fingerprint of the inviter |
proposed_role | Role offered to the invitee |
allowed_envs | Environments the invitee will have access to |
signature | Ed25519 signature over the canonical payload |
created_at | UTC 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:
- Signature verification: the invitation’s Ed25519 signature is verified against the inviter’s public key via
InvitationFile::verify(). If invalid, acceptance is rejected. - Role validation: the system confirms the inviter has sufficient privileges to delegate the proposed role.
- Envelope update: the invitee’s X25519 public key is registered in the vault’s
EnvelopeHeader. Avault_key_slotis created (encrypting the vault key via X25519 ECDH + HKDF + ChaCha20-Poly1305), andenv_slotsare created for each environment in the invitee’sallowed_envslist, each encrypting that environment’s independent key. - Policy update: a
MemberPolicyentry is created with the proposed role, allowed environments, anddelegated_byset to the inviter’s fingerprint. - Audit entry: the acceptance is recorded in the hash-chained audit log.
Invitation Lifecycle
Invitations have the following statuses:
| Status | Meaning |
|---|---|
Pending | Created but not yet accepted |
Accepted | Successfully accepted by the invitee |
Rejected | Declined by the invitee |
Expired | Past the expires_at timestamp |
Revoked | Canceled 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):
| Level | Role | Can Delegate? |
|---|---|---|
| 1 | ReadOnly | No |
| 2 | Auditor | No |
| 3 | Operator | No |
| 4 | Contributor | No |
| 5 | Manager | Yes (roles 1–5) |
| 6 | Admin | Yes (roles 1–6) |
| 7 | Owner | Yes (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:
- BFS starts from bob.
- Finds carol (delegated_by: bob) – added to revoked set.
- Finds dave (delegated_by: bob) – added to revoked set.
- Checks carol’s children – none.
- Checks dave’s children – none.
- 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
- The
revoke_member_v2()function builds the full list of fingerprints to remove (target + cascade, if applicable). - All affected members are removed from the
VaultPolicy. - The revoked members’
vault_key_slotsandenv_slotsare removed from theEnvelopeHeader. - For each environment the revoked members had access to, a new 256-bit environment key is generated and sealed to the remaining authorized members.
- 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.
- The updated
EnvelopeHeaderandVaultPolicyare 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:
- Rotates the vault key and re-encrypts vault-level metadata (manifest, policy, audit).
- Rotates every per-environment key and re-encrypts every environment file.
- Rotates every secret in every environment to a new random value.
- Removes all delegated members from all slots.
- Saves the updated policy.
- 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
- Use cascade revocation when removing someone who has invited others, unless you specifically want to preserve their subtree.
- Set expiry on contractor access to ensure temporary members lose access automatically.
- Use time windows for CI/CD to limit automated access to deployment hours.
- Audit the delegation tree regularly with
sigyn delegation treeto understand the access structure. - Prefer Contributor over Admin when in doubt. Follow the principle of least privilege.
- Set up Shamir recovery shards before distributing vault access widely, so that key loss does not lock out the entire team.
Related Documentation
- 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, andforks.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:
| Method | Description |
|---|---|
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:
| Status | Meaning |
|---|---|
NeverSynced | No remote configured or no commits yet |
UpToDate | Local 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 |
Diverged | Both local and remote have new commits |
Pull Results
A pull operation can result in:
| Result | Meaning |
|---|---|
UpToDate | Nothing to pull |
FastForward | Remote changes applied cleanly |
Conflict | Diverged 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
| Operation | Description |
|---|---|
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:
| Strategy | Description |
|---|---|
TakeLocal | Keep the local value, discard remote |
TakeRemote | Keep the remote value, discard local |
TakeLatestTimestamp | Keep whichever value has the later UTC timestamp (LWW) |
TakeHigherRole | Keep the value written by the member with the higher RBAC role level |
Merge(value) | Provide a custom merged value |
Defer | Skip 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 resolvecurrently 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
Related Documentation
- Architecture – on-disk layout and module overview
- Security Model – encryption and access control
- Delegation – how revocations affect sync
- CLI Reference – complete command reference
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
| Primitive | Algorithm | Purpose |
|---|---|---|
| Key exchange | X25519 (Curve25519 Diffie-Hellman) | Derive shared secrets for envelope encryption |
| Signatures | Ed25519 | Sign audit entries, invitation files, identity verification |
| Symmetric encryption | ChaCha20-Poly1305 (AEAD) | Encrypt secret data and master key slots |
| Key derivation (passphrase) | Argon2id | Derive wrapping key from user passphrase |
| Key derivation (context) | HKDF-SHA256 | Derive per-slot keys from DH shared secrets |
| Hashing / fingerprints | BLAKE3 | Audit 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:
- Vault key (256-bit) – encrypts vault-level metadata: manifest, policy, audit log, rotation schedules.
- 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)
- Generate an ephemeral X25519 keypair.
- Perform Diffie-Hellman:
shared_secret = ephemeral_private * recipient_public. - Derive a slot key via HKDF-SHA256:
slot_key = HKDF(salt=vault_id, ikm=shared_secret, info="sigyn-envelope-v1"). - Encrypt the key with ChaCha20-Poly1305 using the slot key and a random nonce.
- 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
- Find the
RecipientSlotmatching your key fingerprint. - Perform Diffie-Hellman:
shared_secret = my_private * slot.ephemeral_public. - Derive the same slot key via HKDF-SHA256 with the vault UUID as salt.
- 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:
| Keypair | Algorithm | Purpose |
|---|---|---|
| Encryption keypair | X25519 | Receive sealed vault and environment key slots |
| Signing keypair | Ed25519 | Sign 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:
| Level | Role | Read | Write | Manage Members | Manage Policy | Delegate |
|---|---|---|---|---|---|---|
| 1 | ReadOnly | yes | no | no | no | no |
| 2 | Auditor | yes | no | no | no | no |
| 3 | Operator | yes | no | no | no | no |
| 4 | Contributor | yes | yes | no | no | no |
| 5 | Manager | yes | yes | yes | no | yes |
| 6 | Admin | yes | yes | yes | yes | yes |
| 7 | Owner | yes | yes | yes | yes | yes |
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:
- Owner bypass: if the actor is the vault owner, access is always granted.
- Membership check: the actor must be a registered vault member.
- Constraint check: if the member has constraints (time windows, expiry, MFA), they are all evaluated. Any failure results in denial.
- Environment check: the member must have explicit access to the requested environment (or wildcard
*). - Role check: the member’s role must have the required permission for the action (read, write, delete, manage members, manage policy, create env, promote).
- 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:
- Checks for a valid MFA session (default grace period: 1 hour).
- If no session, loads the encrypted
.mfafile (decrypted with a key derived via HKDF-SHA256 from the identity’s X25519 private key with contextb"mfa-state"). - Prompts for a TOTP code or single-use backup code.
- 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 — copyingalice.sessiontobob.sessionno 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:
| Variant | Meaning |
|---|---|
Everyone | All members with sufficient role can access (default) |
Roles(set) | Only members with one of the listed roles |
Fingerprints(set) | Only the listed key fingerprints |
Deny | Explicitly 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:SuccessorFailure(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 overentry_hashby 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 aWitnessLog. - 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:
- Hash linkage: each entry’s
prev_hashmatches the preceding entry’sentry_hash. - Hash integrity: each entry’s
entry_hashis recomputed and compared. - 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.
- 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^254in 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:
| Mode | Description | Upstream Access |
|---|---|---|
| Leashed | Fork 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 |
| Unleashed | Fork 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 thezeroizecrate. - Ephemeral slot keys (HKDF-derived) are wrapped in
Zeroizing<>and zeroed immediately after use. - Ed25519 signing keys use
ZeroizeOnDropfromed25519-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
AtomicBoolto 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 with0o700(owner only) to prevent other local users from reading vault metadata, config, or manifests.- Encrypted files (policy, secrets, identities): written via
atomic_write()with0o600permissions (owner read/write only). - Audit log files:
0o600permissions enforced on creation viaOpenOptions. - Witness log files:
0o600permissions enforced after atomic persist. - Lock files:
0o600permissions enforced (errors propagated, not silently dropped). - All other files (manifests, config, context, notifications): also encrypted and
written via
secure_write()with0o600permissions.
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:
| File | HKDF Context |
|---|---|
config.toml | sigyn-config-v1 |
context.toml | sigyn-context-v1 |
notifications.toml | sigyn-notifications-v1 |
org-manifest.toml | sigyn-org-manifest-v1 |
forks.cbor | sigyn-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:
| File | HKDF Context | HKDF Salt |
|---|---|---|
vault.toml (manifest) | sigyn-manifest-v1 | (AAD = vault_id) |
rotation_schedules.toml | sigyn-rotation-v1 | vault_id |
audit.log.json entries | sigyn-audit-v1 | vault_id |
witnesses.json | sigyn-witness-v1 | vault_id |
deploy_key.sealed | sigyn-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:
- 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, setSIGYN_VERIFY_OWNER=<fingerprint>to require a match before the pin is saved — a mismatch aborts the unlock. - 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.
Encrypted Org Links
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.tomlis loaded from a directory that contains a.gitdirectory (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.tomlsets theidentityfield (which controls which identity is used for all operations), Sigyn prints an additional warning and requires theSIGYN_TRUST_PROJECT_CONFIGenvironment 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
| Vector | How agent sees secrets |
|---|---|
| Environment variables | env, printenv, /proc/self/environ |
.env files | cat .env, file reading tools |
| Shell history | history, ~/.bash_history |
| Process listing | ps aux shows command-line args with inline secrets |
| Conversation context | Secrets pasted into prompts or visible in tool output |
Sigyn mitigations
| Method | Protection level | How it works |
|---|---|---|
sigyn run exec | Strong | Secrets injected as env vars into child process only; parent shell (where agent runs) is clean |
sigyn run --clean | Stronger | Child process gets a clean environment with only vault secrets; no inherited vars leak |
sigyn run serve | Strongest | Secrets served over a Unix socket with 0600 permissions; never appear in env vars or process listings |
.sigyn.toml named commands | Convenience | sigyn run dev — developers never type or see secrets |
sigyn web | GUI alternative | Manage secrets in a browser; no terminal exposure at all |
Recommended workflow
- Never
exportsecrets or usesource .envin terminals where AI agents run. - Use
sigyn run execor named commands in.sigyn.tomlto inject secrets only into the processes that need them. - For maximum isolation, use
sigyn run serve— secrets are accessible only via the Unix socket. - 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:
- Dev environment: localhost databases, test API keys — not worth protecting.
- Staging/prod environments: real credentials — agent never touches these.
- Named commands:
.sigyn.tomldefines an allowlist of commands the agent can run. - No passphrase caching: don’t run
sigyn agent startin the agent’s terminal — it can only use pre-configuredsigyn runcommands.
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
sigyncommands 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
| Threat | Mitigation |
|---|---|
| Stolen vault files | ChaCha20-Poly1305 encryption; attacker needs a member’s private key |
| Compromised member | Revoke member; affected environment keys are rotated; cascade revocation removes their delegates |
| Tampered audit log | Hash chain breaks on tamper; Ed25519 signatures prove authorship |
| Tampered config/manifest | AEAD authentication; device key or master key required to forge |
| Tampered members.cbor | Ed25519 signature over content + vault_id; forgery requires signing key |
| Downgrade to plaintext | No fallbacks; all readers reject data without correct magic header |
| Forged MFA session | Session HMAC uses device-key-derived secret, not public fingerprint |
| Tampered invitation | Acceptance requires inviter’s identity for signature verification |
| Lost access (key loss) | Shamir K-of-N recovery shards |
| Unauthorized access escalation | PolicyEngine::evaluate() on every operation; no bypass path |
| Brute-force passphrase | Argon2id 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 vars | sigyn run exec injects into child only; sigyn run serve uses Unix socket with no env exposure |
| Replay of old ciphertext | Unique nonces per encryption; vault UUID bound into HKDF salt |
| Sensitive data in memory | secrecy::Secret + zeroize on drop; constant-time fingerprint comparison |
| Partial file writes | Atomic writes via tempfile::persist() |
| Symlink traversal | Per-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 compromise | Limits 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 attack | TOFU vault origin pinning; owner fingerprint checked on every unlock |
| Auditor privilege escalation | Optional 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 identities | Fingerprint bound as AAD in AEAD encryption |
| Invitation replay/theft | 7-day expiry; Ed25519 signature with length-prefixed payload |
| Signing payload field confusion | Length-prefixed v2 format prevents boundary ambiguity |
| Hierarchy node header forgery | SGSN signed format with Ed25519 verification |
| Forged org/fork policy file | All policy files signed and bound to vault/node UUID; unsigned rejected |
| Crash during audit append | Trailing corrupt line tolerated; mid-file tampering detected |
| File permission TOCTOU | Atomic temp+rename with 0o600 mode set at creation |
| Predictable temp file attack | Editor temp files use cryptographically random names via tempfile crate |
| Secret leaking via process list | Inline {{KEY}} substitution requires explicit --allow-inline-secrets flag |
| Secret leaking via rotation hooks | Hook secrets passed via stdin, not environment variables; avoids /proc/<pid>/environ visibility |
| Hook command injection | Hooks executed directly (no shell); shell metacharacters and path traversal rejected at save time |
| Unaudited secret access | sigyn run exec/export/serve and sigyn secret list generate audit entries |
| Malicious project config | Warning when .sigyn.toml is in a git-tracked directory; identity override requires SIGYN_TRUST_PROJECT_CONFIG |
| Vault key retained after revocation | Vault key rotated on member revocation; new key re-sealed to remaining members only |
| Agent socket hijacking | Session token required for CACHE command; socket ownership verified before connecting |
| Device key tampering | Stored with BLAKE3 integrity hash; tampered files rejected on load |
| Identity file tampering | BLAKE3 keyed MAC (using device key) appended and verified on every load |
| Weak KDF parameters | Argon2id parameters validated against minimums (m_cost >= 64 MiB, t_cost >= 3, p_cost >= 1) |
| Audit log gap injection | Sequence continuity verified — gaps in sequence numbers detected and rejected |
| Unsigned witness log | Witness log optionally signed with Ed25519; signature verified on load when verifying key available |
| Force-push on sync push | Push verifies local HEAD descends from remote HEAD; non-fast-forward rejected unless --force |
| TOFU pin poisoning on first access | Prominent warning banner on first vault access; SIGYN_VERIFY_OWNER env var for automated verification |
| Malicious EDITOR env var | Warning emitted when $EDITOR/$VISUAL points outside standard system paths |
| Rollback via malformed checkpoint | Invalid checkpoint OIDs produce an error instead of silently skipping validation |
| Cloud import argument injection | Cloud resource names validated: no leading dashes, control chars, or shell metacharacters |
| Generated secret leaking to stdout | secret generate hides value by default; requires --reveal flag |
| Passphrase lingering in memory | Passphrase strings zeroized at CLI call sites after use |
Related Documentation
- 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
- Enrollment –
sigyn mfa setupgenerates a TOTP secret, displays anotpauth://URI for your authenticator app, and produces 8 single-use backup codes. - Policy enforcement – when
require_mfa: trueis set on global or per-member constraints, the policy engine requires MFA verification before granting access. - 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.
- 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
.mfafile is encrypted with ChaCha20-Poly1305 using a key derived via HKDF-SHA256 from the identity’s X25519 private key with info contextb"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.
Related Documentation
- Security Model – full cryptographic details and threat model
- CLI Reference – complete command reference
- Getting Started – initial setup including MFA enrollment
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:
| Field | Description |
|---|---|
sequence | Monotonic sequence number |
timestamp | ISO 8601 UTC timestamp |
actor | Fingerprint of the acting member |
action | Operation type (see below) |
env | Environment affected (if applicable) |
key | Secret key affected (if applicable) |
outcome | Success or failure with reason |
prev_hash | blake3 hash of the previous entry |
entry_hash | blake3 hash of this entry |
signature | Ed25519 signature over entry_hash |
Tracked Actions
VaultCreated,VaultDeleted,VaultExportedSecretRead,SecretWritten,SecretDeletedSecretsCopied—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,MemberRevokedPolicyChangedMasterKeyRotatedOwnershipTransferred— records from/to fingerprintsOwnershipTransferAccepted— records accepting fingerprintForkCreatedEnvironmentCreated,EnvironmentDeleted,EnvironmentPromotedBreakGlassActivated
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:
- Each entry’s
prev_hashmust match theentry_hashof the preceding entry. - Each entry’s
entry_hashmust be a valid blake3 hash of the entry content. - Each entry’s
signaturemust be a valid Ed25519 signature from the claimedactor. 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. - All sequence numbers from 0 to N must be present with no gaps, detecting selective deletion of individual entries.
- 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):
| Mode | Behavior |
|---|---|
offline | Default. Audit entries are appended locally; push when convenient via sigyn sync push. |
online | Audit 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-effort | Attempts 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 beforeonlinemode has any effect. onlinemode 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:
- Reconstruct secrets from Shamir shards
- Restore a vault from a git snapshot
- 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-recoverybinary 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
| Crate | Role | Key Trait |
|---|---|---|
| sigyn-core | Pure 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-engine | I/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-cli | The sigyn binary. Parses arguments (clap 4), performs network I/O, renders output (tables, JSON, TUI). | anyhow for ergonomic CLI errors |
| sigyn-recovery | A 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
| Format | Used For | Rationale |
|---|---|---|
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)
| Module | Responsibility |
|---|---|
| crypto | Key generation, X25519 Diffie-Hellman, envelope encryption/decryption (vault key + per-env key slots), ChaCha20-Poly1305 AEAD, Argon2id KDF, HKDF key derivation, nonce management |
| vault | Vault manifest types, env file encryption/decryption (pure computation) |
| secrets | Secret value types, validation rules, cross-references, random secret generation, per-key ACLs |
| identity | Identity/LoadedIdentity types, passphrase wrapping, Shamir secret sharing, MFA types and sessions |
| environment | Environment management, per-env policy, diffing, secret promotion |
| policy | 7-level RBAC, member policy, secret ACLs, PolicyEngine::evaluate(), constraint checking, policy serialization |
| delegation | Delegation tree structure, Ed25519-signed invitations, cascade revocation |
| forks | Fork types, approval workflows, fork expiry |
| audit | Audit entry and witness types |
| sync | Vector clocks, LWW-Map CRDT, conflict detection and resolution |
| rotation | Key rotation, cron-based scheduling, rotation history, hooks, breach mode, dead-check |
| hierarchy | Hierarchical policy engine, node manifests, OrgPath type |
sigyn-engine Modules (I/O Layer)
| Module | Responsibility |
|---|---|
| vault | Env file read/write, vault locking (fd-lock), vault path resolution and directory scanning |
| identity | Identity store (filesystem), MFA store, MFA session store |
| audit | Hash-chained audit log (file-based), git anchoring, witness log persistence |
| sync | Git-based sync engine (git2) |
| hierarchy | Hierarchy paths (filesystem), slot management (CBOR read/write), git remote resolution |
| forks | Fork creation with filesystem operations (leashed/unleashed) |
| policy | VaultPolicyExt trait for filesystem-based policy save/load |
sigyn-cli Modules
| Module | Files | Responsibility |
|---|---|---|
| commands | identity.rs, vault.rs, secret.rs, env.rs, policy.rs, sync.rs, audit.rs, fork.rs, run.rs, rotate.rs, delegation.rs, import.rs, status.rs | One file per CLI subcommand group |
| inject | mod.rs, dotenv.rs, export.rs, process.rs, socket.rs | Secret injection into child processes, dotenv export, Unix socket serving |
| importexport | mod.rs, cloud.rs | Import from dotenv/JSON/cloud providers (Doppler, AWS, GCP, 1Password) |
| tui | mod.rs | Interactive TUI dashboard (ratatui + crossterm) |
| notifications | mod.rs | Expiry warnings, rotation reminders |
| output | mod.rs | Formatting 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.
Related Documentation
- Security Model – cryptographic primitives, threat model, policy engine
- CLI Reference – complete command reference
- Getting Started – step-by-step tutorial
- Delegation – invitation and revocation system
- Sync – git-based synchronization and conflict resolution
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
| Key | Type | Description |
|---|---|---|
vault | string | Default vault for this project |
env | string | Default environment for this project |
identity | string | Default 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):
- CLI flags (
--vault,-v,--env,-e,--identity,-i) - Project config (
.sigyn.tomlin current or parent directory) - User project config (
~/.sigyn/project.toml) - Global config (
~/.sigyn/config.toml) - 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:
| Flag | Short | Description |
|---|---|---|
--vault <name> | -v | Override the default vault |
--env <name> | -e | Override the default environment |
--identity <name> | -i | Override the default identity |
--json | Output as JSON instead of formatted text | |
--quiet | Suppress non-essential output | |
--dry-run | Preview 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
- I/O Separation:
sigyn-coreMUST NOT perform any I/O (filesystem, network). All I/O lives insigyn-engine. This makes the core library publishable, testable, and portable. - Safety First: Always use
secrecy::Secret<T>for sensitive data. Ensure zeroization on drop. - Atomic Operations: Filesystem operations in the CLI should be atomic. Use
tempfileandpersist(). - Typed Errors: Use
thiserrorin the core library for precise error handling. Useanyhowin the CLI for context-rich error reporting. - 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 usex25519-dalekfor key exchange andchacha20poly1305for 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 withratatui.
How-To Guides
Adding a New Secret Type
- Update the
SecretValueenum incrates/sigyn-core/src/secrets/types.rs. - Implement validation logic in
crates/sigyn-core/src/secrets/validation.rs. - Update the CLI to support the new type in
crates/sigyn-cli/src/commands/secret.rs.
Adding a New Import Source
- Add a new module in
crates/sigyn-cli/src/importexport/. - Implement the
Importertrait (or follow the pattern of existing importers likedoppler.rs). - 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).
| Module | Coverage |
|---|---|
vault_lifecycle.rs | Create, open, delete vaults |
member_access.rs | Member key slots, access control |
delegation_invite.rs | Invitation create, accept, verify |
revocation.rs | Revoke, cascade revoke, key rotation |
policy_constraints.rs | Role checks, delegation depth, delegatee limits |
audit_chain.rs | Hash chain integrity, signature verification |
secret_types.rs | Secret value types and validation |
env_promotion.rs | Environment copy, promote, diff |
forks_leash.rs | Fork 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 ghCLI authenticated (for GitHub repo operations)- Git SSH access configured
python3available
What it tests:
| Phase | Tests | Coverage |
|---|---|---|
| Identity | 9 | Create 7 personas, list, show |
| Vaults | 9 | 3 vaults, custom envs, TOFU |
| Delegation | 12 | Direct, chained, bulk invite |
| Secrets | 34 | CRUD, RBAC, generate, search, import |
| Env Ops | 7 | Diff, clone, promote, copy |
| Sync | 9 | Configure, push, pull, status |
| Run & Export | 10 | 5 export formats, injection |
| Audit | 6 | Tail, verify, export |
| Rotation | 6 | Manual, schedule, due |
| Advanced Delegation | 10 | Cascade, env revoke, policy |
| Security | 22 | Tamper, 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:
| Helper | Usage |
|---|---|
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:
| Category | Attacks | Description |
|---|---|---|
| Unauthorized access | 4 | Rogue identity, wrong passphrase, env boundaries |
| File tampering | 7 | Bit-flip on all file types, zero-fill, truncate, trailing junk |
| Privilege escalation | 5 | Contributor invite/revoke/cross-env, non-member write/list |
| Stolen files | 2 | Copied vault, copied identity + wrong passphrase |
| Replay attacks | 3 | Replay used invitation, tamper invitation, forge invitation |
| Audit attacks | 2 | Delete audit log, corrupt entries |
| Path traversal | 3 | Symlink, vault name traversal, env name traversal |
| Crypto exposure | 3 | Plaintext in files, plaintext keys, passphrase leak to child |
| Cross-vault | 3 | Swap headers, policies, env files between vaults |
| Post-revocation | 2 | Read/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-exportssigyn-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
- 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). - Implement the logic in the command file.
- 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!