arcanum-seal
v1.0.1
Published
Encrypted secrets for teams that ship fast. No servers, no accounts, no cloud — just git.
Maintainers
Readme
ArcanumSeal 🔐
Encrypted secrets for teams that ship fast.
ArcanumSeal is an open-source CLI tool that solves one problem: developers share API keys, passwords, and tokens over Slack, email, and Notion because there is no simple encrypted alternative. ArcanumSeal fixes this.
seal push → encrypts .env → safe to commit to git
seal pull → decrypts blob → .env available locally
seal exec → decrypts blob → injects into process (never touches disk)No servers. No accounts. No cloud. Git is the sync mechanism.
Why ArcanumSeal?
| Problem | ArcanumSeal |
|---|---|
| Secrets shared over Slack/email | Encrypted blobs committed to git |
| .env files accidentally committed | .gitignore updated automatically |
| New dev needs secrets → DM the admin | seal grant re-encrypts for them |
| Dev leaves → rotate everything manually | seal revoke re-encrypts in one command |
| CI needs secrets → store in plaintext | seal pull --key /tmp/key in pipeline |
| "Was this secret leaked?" | seal scan --git checks entire history |
Install
npm install -g arcanumSealRequirements: Node.js 22+. That's it.
Verify:
seal --version # 1.0.0
seal help # all commandsNode.js 22+ required. Check with
node --version.
Quick Start (Solo Developer)
1. Generate your keypair
seal keygen✓ Keypair generated and saved.
Your public key:
CUlyjQI/M3FeBQlzWTBzdK76WDkwtczywmDfSQIbyDQ=
Private key → ~/.arcanumSeal/master.key (never share this)
Public key → ~/.arcanumSeal/master.pub (safe to share)Your private key never leaves your machine. The public key is safe to share with anyone.
2. Initialize your project
cd my-project
seal init Project name detected: my-project
✓ ArcanumSeal initialized for project: my-project
Created: .arcanumSeal/config.json
Created: .arcanumSeal/team.json
Updated: .gitignore (ArcanumSeal entries appended)
Next steps:
1. seal keygen — generate your personal keypair
2. seal push --env development — encrypt your .env.development
3. git add .arcanumSeal/ && git push — commit encrypted secretsYour .gitignore is automatically updated:
# ArcanumSeal
.env
.env.*
!.env.example
.arcanumSeal/master.key ← private key blocked
# .arcanumSeal/*.enc.json ← encrypted blobs are NOT blocked (safe to commit)3. Encrypt your secrets
# Given .env.production contains:
# DB_HOST=prod-db.example.com
# DB_PASS=hunter2
# API_KEY=sk-prod-abc123
seal push --env production✓ Secrets pushed to .arcanumSeal/production.enc.json
3 secrets added
Next: commit the encrypted store to git:
git add .arcanumSeal/ && git commit -m "chore: update secrets"The encrypted file is safe to commit. It contains no plaintext:
{
"version": 1,
"environment": "production",
"secrets": {
"DB_HOST": {
"encrypted": {
"ciphertext": "base64...",
"nonce": "base64...",
"ephemeralPublicKey": "base64..."
},
"recipients": {
"<riya-pubkey>": { "ciphertext": "...", "nonce": "...", "ephemeralPublicKey": "..." },
"<arjun-pubkey>": { "ciphertext": "...", "nonce": "...", "ephemeralPublicKey": "..." }
},
"version": 1
}
}
}Each team member gets their own independently encrypted copy of every secret value. No two ciphertexts are the same.
4. Decrypt secrets back
seal pull --env production✓ 3 secrets synced to .env.productionOr write to a custom path:
seal pull --env production --out .env5. Run your app with secrets (never writes to disk)
seal exec --env production -- node server.jsSecrets are decrypted in memory only and injected as process.env. No .env file is written. Works with any runtime:
seal exec --env production -- python manage.py runserver
seal exec --env production -- ruby app.rb
seal exec --env production -- docker-compose upExit codes are mirrored exactly:
seal exec --env production -- node failing-script.js
echo $? # → 1 (same as the child process)Team Workflow
Adding a new developer
New developer (Arjun):
# Step 1: Generate keypair
seal keygen
# Step 2: Print public key to share with admin
seal request-access --to [email protected]Your public key:
bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg=
Send this key to [email protected] and ask them to run:
seal grant <your-email> --key bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg=Admin (Riya):
seal grant [email protected] --key bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg= Re-encrypting secrets for [email protected]...
✓ Access granted to [email protected]
Added to: .arcanumSeal/team.json
Environments: all
Re-encrypted: 2 environments
Next: commit and push so the new member can pull:
git add .arcanumSeal/ && git commit -m "chore: grant access to [email protected]" && git pushseal grant re-encrypts every secret for all current team members in a single pass — existing members retain access and the new member is added atomically.
Arjun (after git pull):
git pull
seal pull --env production # ✓ works immediatelyNo Slack DM. No plaintext secrets shared. No infrastructure required.
Revoking access when someone leaves
seal revoke [email protected] Re-encrypting secrets without [email protected]'s key...
✓ Access revoked for [email protected]
Removed from: .arcanumSeal/team.json
Re-encrypted: 2 environments
⚠ Commit and push IMMEDIATELY to prevent further access:
git add .arcanumSeal/ && git commit -m "chore: revoke access for [email protected]" && git pushseal revoke re-encrypts every secret for all remaining team members in a single pass — their access is preserved while the revoked member's key is permanently excluded.
After you push, Arjun's local copies are permanently undecryptable.
View team members
seal team listTeam members (2)
EMAIL ROLE ENVIRONMENTS ADDED ADDED BY
------------------- -------- ------------ ---------- -------------------
[email protected] admin ★ * 2026-01-01 [email protected]
[email protected] member * 2026-02-15 [email protected]Promote or demote a member
# Promote a member to admin (e.g. after key recovery or role change)
seal team promote [email protected]
# Demote an admin back to member
seal team demote [email protected]seal team promoteandseal team demoteare the only way to change roles —team.jsonshould never be edited manually.- Demoting the last admin is blocked — there must always be at least one admin.
- Both actions are recorded in the audit log (
PROMOTE/DEMOTE).
Developer Day-to-Day Workflow
This section covers the exact steps a developer takes during normal feature work — adding new secrets, changing existing values, and keeping the team in sync.
The Golden Rule
Never edit
.enc.jsonfiles directly.
Always edit your.env.<environment>file, then runseal push. ArcanumSeal is the only thing that touches the encrypted store.
Workflow 1 — Adding a Brand New Secret Variable
You're building a new feature that needs a Twilio API key. Here's the complete flow:
Step 1 — Add the variable to your local .env
# Open your local .env.development (not committed — gitignored)
echo 'TWILIO_AUTH_TOKEN=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' >> .env.developmentOr edit it in your editor. Your .env.development now looks like:
DB_HOST=localhost
DB_PASS=devpassword
API_KEY=sk-dev-test123
TWILIO_AUTH_TOKEN=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ← newStep 2 — Push the updated secrets
seal push --env development✓ Secrets pushed to .arcanumSeal/development.enc.json
1 secret added
3 secrets updated
Encrypted for 3 recipientsArcanumSeal re-encrypts all secrets (not just the new one) for every team member in a single pass.
Step 3 — Also add it to staging and production
# Add the real values for each environment
echo 'TWILIO_AUTH_TOKEN=ACyyyy_staging_token' >> .env.staging
echo 'TWILIO_AUTH_TOKEN=ACzzzz_prod_token' >> .env.production
seal push --env staging
seal push --env productionStep 4 — Update .env.example so teammates know the variable exists
echo 'TWILIO_AUTH_TOKEN=your-twilio-auth-token' >> .env.exampleStep 5 — Commit and push
git add .arcanumSeal/ .env.example
git commit -m "feat: add TWILIO_AUTH_TOKEN secret for SMS feature"
git pushStep 6 — Teammates pull and get the new secret automatically
# On any teammate's machine
git pull
seal pull --env development # .env.development now contains TWILIO_AUTH_TOKENOr if they use seal exec, it's available immediately with no extra steps:
seal exec --env development -- npm run dev
# process.env.TWILIO_AUTH_TOKEN is available inside the processWorkflow 2 — Changing an Existing Secret Value
You need to update the database password because it was rotated by your DBA.
Option A — Update via .env file + seal push (recommended for bulk changes)
Use this when you're changing multiple secrets at once, or when you already have the new values in your .env file.
# Edit .env.production — change DB_PASS to the new value
nano .env.production
# DB_PASS=old-password → DB_PASS=new-dba-rotated-password
seal push --env production✓ Secrets pushed to .arcanumSeal/production.enc.json
4 secrets updated
Encrypted for 3 recipientsgit add .arcanumSeal/
git commit -m "chore: update DB_PASS to new DBA-rotated value"
git pushOption B — seal rotate (recommended for a single secret, with hidden input)
Use this when you're changing one secret and want the value hidden from your terminal history and screen.
seal rotate DB_PASS --env productionNew value for DB_PASS: ********
✓ DB_PASS rotated successfully
Environment: production
Version: 2 → 3
Updated by: [email protected]seal rotate re-encrypts the new value for all team members in one step — no need to run seal push afterward.
git add .arcanumSeal/
git commit -m "chore: rotate DB_PASS"
git pushWhen to use which:
seal push— you have a new.envfile from your DBA / DevOps, or you're changing many secrets at onceseal rotate— you're changing one secret interactively and want the value hidden from your screen and shell history
Workflow 3 — Removing a Secret Variable
A feature was deprecated and LEGACY_PAYMENT_KEY is no longer needed.
Step 1 — Remove it from your .env file
# Edit .env.production and delete the LEGACY_PAYMENT_KEY line
nano .env.production# Before
DB_HOST=prod-db.example.com
DB_PASS=secret
LEGACY_PAYMENT_KEY=pk_live_old123 ← delete this line
API_KEY=sk-prod-abc
# After
DB_HOST=prod-db.example.com
DB_PASS=secret
API_KEY=sk-prod-abcStep 2 — Push — ArcanumSeal detects the removal
seal push --env production✓ Secrets pushed to .arcanumSeal/production.enc.json
3 secrets updated
1 secret removedThe key is removed from the encrypted store. Teammates will no longer receive it on their next seal pull.
Step 3 — Also remove from .env.example
# Remove the LEGACY_PAYMENT_KEY line from .env.example
nano .env.example
git add .arcanumSeal/ .env.example
git commit -m "chore: remove deprecated LEGACY_PAYMENT_KEY"
git pushWorkflow 4 — Pulling the Latest Secrets (After a Teammate Pushed Changes)
A teammate added a new secret or rotated a value. You need to sync.
git pull # get the updated .enc.json
seal pull --env development # decrypt to your local .env.development✓ 5 secrets synced to .env.developmentIf you use seal exec instead of seal pull, you don't need to do anything — the latest secrets are always decrypted fresh on every run:
seal exec --env development -- npm run dev # always uses the latest encrypted storeWorkflow 5 — Working Across Multiple Environments
A typical day might involve switching between environments:
# Morning: local development
seal exec --env development -- npm run dev
# Afternoon: test against staging
seal pull --env staging
npm run test:integration
# Deploy to production
seal validate --env production --keys DB_HOST,DB_PASS,API_KEY
seal exec --env production -- npm run deployOr check what's in each environment without decrypting:
seal history --env production # see recent pushes and rotations
seal rotation-report # check if any secrets are overdue for rotationWorkflow 6 — Checking What Changed (Before Pulling)
Before pulling and decrypting, you can inspect what changed between two pushes:
seal history --env productionHistory for production (4 events)
TIMESTAMP ACTION USER SECRET DETAILS
------------------- ------ ---------------- -------- -------
2026-03-12 10:15:00 ROTATE [email protected] DB_PASS —
2026-03-11 14:22:31 PUSH [email protected] — —
2026-03-10 09:00:00 PUSH [email protected] — —Diff two specific pushes to see exactly what changed:
seal diff p3 p4 --env productionDiff: push p3 → p4 (production)
↻ Changed (1):
SECRET VERSION FINGERPRINT VALUE
------- ------- ----------- ---------------------------
DB_PASS v2 → v3 63ef416a (value changed — not shown)
— Unchanged: 4 secretsWorkflow 7 — Onboarding Yourself to an Existing Project
You just joined a team that already uses ArcanumSeal.
# 1. Clone the repo (encrypted stores are already in git)
git clone [email protected]:company/project.git
cd project
# 2. Install ArcanumSeal
npm install -g arcanumSeal
# 3. Generate your personal keypair (one-time setup)
seal keygen
# 4. Share your public key with an admin
seal request-access --to [email protected]Your public key:
bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg=
Send this key to [email protected] and ask them to run:
seal grant [email protected] --key bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg=# 5. After the admin grants access and pushes — pull the latest
git pull
seal pull --env development
# 6. Start working
seal exec --env development -- npm run devQuick Reference — Which Command to Use When
| Situation | Command |
|-----------|---------|
| Added a new variable to .env | seal push --env <env> |
| Changed multiple values in .env | seal push --env <env> |
| Changed one secret (hidden input) | seal rotate <KEY> --env <env> |
| Removed a variable from .env | seal push --env <env> (removal is automatic) |
| Teammate pushed new secrets | git pull then seal pull --env <env> |
| Run app with latest secrets | seal exec --env <env> -- <command> |
| Check what changed recently | seal history --env <env> |
| Compare two versions | seal diff p1 p2 --env <env> |
| Check rotation health | seal rotation-report |
| Verify all required secrets exist | seal validate --env <env> --keys A,B,C |
Common Mistakes to Avoid
| ❌ Don't | ✅ Do Instead |
|----------|--------------|
| Edit .arcanumSeal/*.enc.json directly | Edit .env.<env> then seal push |
| Commit .env.* files | They are gitignored — use seal push |
| Share secrets over Slack/email | Use seal grant to give access |
| Use --value flag for seal rotate | Use SEAL_ROTATE_VALUE=... seal rotate or interactive prompt |
| Forget to git push after seal push | Always commit .arcanumSeal/ after any push/rotate/grant/revoke |
| Assume revoke alone cuts off access | After seal revoke, rotate all secret values too |
Secret Rotation
Rotate a single secret
seal rotate DB_PASS --env productionNew value for DB_PASS: ********
✓ DB_PASS rotated successfully
Environment: production
Version: 3 → 4
Updated by: [email protected]Check rotation health
seal rotation-reportEnvironment: production
SECRET LAST ROTATED VERSION BY STATUS
---------- ------------ ------- ---------------- ----------
DB_HOST 0d ago v2 [email protected] ✓ HEALTHY
DB_PASS 0d ago v4 [email protected] ✓ HEALTHY
API_KEY 95d ago v1 [email protected] ⚠ DUE SOON
STRIPE_KEY 200d ago v1 [email protected] ✗ OVERDUE
Summary
✓ Healthy: 2
⚠ Due soon: 1 (rotate within 90 days)
✗ Overdue: 1 (rotate immediately!)
Suggested rotation commands:
seal rotate STRIPE_KEY --env productionAudit Log
View all activity
seal auditAudit log (21 entries)
TIMESTAMP USER ACTION ENVIRONMENT SECRET
------------------- ---------------- ------ ----------- -------
2026-03-11 15:43:07 [email protected] ROTATE production DB_PASS
2026-03-11 14:22:31 [email protected] PUSH production —
2026-03-11 13:10:05 [email protected] PULL production —Filter the log
seal audit --user arjun # only Arjun's entries
seal audit --env production # only production
seal audit --action ROTATE # only rotations
seal audit --since 7d # last 7 days
seal audit --from 2026-01-01 --to 2026-03-31 # date rangeExport for compliance (SOC2, etc.)
seal audit --since 90d --export audit-report.json✓ Audit log exported to audit-report.json
21 entries written
Format: JSON array, timestamps ISO 8601Leak Detection
Scan git history
seal scan --git Scanning git history for 5 secret values...
✓ Clean — no secrets found in git history (47 commits scanned)If a leak is found:
✗ Found 1 leaked secret in git history!
SECRET ENVIRONMENT LOCATION
------- ----------- ------------------------------------------
DB_PASS production commit a3f8b21c — config/database.js:12
Rotate these secrets immediately:
seal rotate DB_PASS --env productionScan working files (pre-commit hook)
seal scan --filesUse as a pre-commit hook to block accidental commits:
# .git/hooks/pre-commit
#!/bin/sh
seal scan --filesVersion History & Diffing
View history for an environment
seal history --env productionHistory for production (3 events)
TIMESTAMP ACTION USER SECRET DETAILS
------------------- ------ ---------------- ------- -------
2026-03-11 15:43:07 ROTATE [email protected] DB_PASS —
2026-03-11 10:24:12 PUSH [email protected] — —
2026-03-10 09:00:00 PUSH [email protected] — —
Current state
SECRET VERSION LAST UPDATED BY FINGERPRINT
---------- ------- ------------------- ---------------- -----------
DB_HOST v2 2026-03-11 10:24:12 [email protected] 4953d00c
DB_PASS v4 2026-03-11 15:43:07 [email protected] 63ef416a
API_KEY v2 2026-03-11 10:24:12 [email protected] 664e0f99Diff two versions
seal diff p1 p4 --env productionDiff: push p1 → p4 (production)
↻ Rotated (1):
SECRET VERSION FINGERPRINT VALUE
------ ------- ----------- ---------------------------
DB_PASS v1 → v4 63ef416a (value changed — not shown)
— Unchanged: 2 secrets
Note: plaintext values are never shown in diff output.Integrating ArcanumSeal Into Your Project
This section walks through complete, real-world integration scenarios from scratch — from a solo developer to a full team with CI/CD.
Scenario A — Solo Developer (Node.js / Express)
Goal: Encrypt your .env.production so it's safe to commit, and run your app without ever writing secrets to disk.
Step 1 — Install and generate your keypair
npm install -g arcanumSeal
seal keygen✓ Keypair generated and saved.
Your public key:
CUlyjQI/M3FeBQlzWTBzdK76WDkwtczywmDfSQIbyDQ=
Private key → ~/.arcanumSeal/master.key (never share this)
Public key → ~/.arcanumSeal/master.pub (safe to share)Step 2 — Initialize ArcanumSeal in your project
cd my-express-app
seal init✓ ArcanumSeal initialized for project: my-express-app
Created: .arcanumSeal/config.json
Created: .arcanumSeal/team.json
Updated: .gitignoreYour .gitignore now contains:
# ArcanumSeal
.env
.env.*
!.env.example
.arcanumSeal/master.key
.arcanumSeal/audit.log
.arcanumSeal/*.tmpStep 3 — Create your .env.production
cat > .env.production << 'EOF'
DB_HOST=prod-db.example.com
DB_PORT=5432
DB_PASS=super-secret-password
API_KEY=sk-prod-abc123xyz
STRIPE_SECRET=sk_live_abc123
EOFStep 4 — Encrypt and commit
seal push --env production✓ Secrets pushed to .arcanumSeal/production.enc.json
5 secrets added
Encrypted for 1 recipientgit add .arcanumSeal/
git commit -m "chore: add encrypted production secrets"
git pushYour .env.production is never committed (blocked by .gitignore). Only the encrypted blob is in git.
Step 5 — Run your app with secrets injected (no disk write)
# Instead of: node server.js (which requires a .env file)
seal exec --env production -- node server.jsSecrets are decrypted in memory and injected as process.env. Your server.js reads them normally:
// server.js — no changes needed
const db = new Pool({
host: process.env.DB_HOST,
password: process.env.DB_PASS,
})Step 6 — Add an .env.example for documentation
cat > .env.example << 'EOF'
DB_HOST=your-db-host
DB_PORT=5432
DB_PASS=your-db-password
API_KEY=your-api-key
STRIPE_SECRET=sk_live_...
EOF
git add .env.example
git commit -m "docs: add .env.example"Scenario B — Team of Developers (Full Workflow)
Goal: Admin Riya sets up secrets, grants access to new developer Arjun, and later revokes access when Arjun leaves.
Step 1 — Admin (Riya) sets up the project
# Riya already has a keypair from seal keygen
cd team-project
seal init
seal push --env production
seal push --env staging
git add .arcanumSeal/
git commit -m "chore: initialize ArcanumSeal with encrypted secrets"
git pushStep 2 — New developer (Arjun) joins
On Arjun's machine:
git clone [email protected]:company/team-project.git
cd team-project
npm install -g arcanumSeal
seal keygen
seal request-access --to [email protected]Your public key:
bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg=
Send this key to [email protected] and ask them to run:
seal grant [email protected] --key bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg=Arjun sends this public key to Riya (Slack, email — it's safe to share).
Step 3 — Admin (Riya) grants access
seal grant [email protected] --key bY2W660AN/FqsQlycQbtKMltG3hW0F/IsM4FUTxN5zg= Re-encrypting secrets for [email protected]...
✓ Access granted to [email protected]
Added to: .arcanumSeal/team.json
Environments: all
Re-encrypted: 2 environmentsgit add .arcanumSeal/
git commit -m "chore: grant access to [email protected]"
git pushStep 4 — Arjun pulls and starts working
git pull
seal pull --env staging # writes .env.staging locally
seal exec --env production -- npm start # injects production secrets in memoryStep 5 — Rotate a secret (e.g., after a suspected leak)
seal rotate DB_PASS --env productionNew value for DB_PASS: ********
✓ DB_PASS rotated successfully
Environment: production
Version: 1 → 2
Updated by: [email protected]For CI rotation, use the env var form to avoid exposing the value in process arguments:
SEAL_ROTATE_VALUE=new-super-secret seal rotate DB_PASS --env production
git add .arcanumSeal/
git commit -m "chore: rotate DB_PASS"
git pushAll team members automatically get the new value on their next seal pull or seal exec.
Step 6 — Arjun leaves the company
seal revoke [email protected]✓ Access revoked for [email protected]
Re-encrypted: 2 environments
⚠ IMPORTANT: Re-encryption alone does NOT invalidate existing local copies.
You MUST rotate all secret values to fully cut off access:
seal rotate <KEY> --env <env> (repeat for every secret in every environment)# Rotate every secret to fully cut off access
seal rotate DB_PASS --env production
seal rotate API_KEY --env production
seal rotate STRIPE_SECRET --env production
# ... repeat for all secrets
git add .arcanumSeal/
git commit -m "chore: revoke access for [email protected] and rotate all secrets"
git pushScenario C — Python / Django Project
ArcanumSeal is language-agnostic. Here's a Django integration:
Setup
pip install --user arcanumSeal # or: npm install -g arcanumSeal
seal keygen
seal initCreate .env.production
cat > .env.production << 'EOF'
DJANGO_SECRET_KEY=your-50-char-secret-key-here
DATABASE_URL=postgres://user:pass@prod-db:5432/mydb
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
REDIS_URL=redis://prod-redis:6379/0
ALLOWED_HOSTS=myapp.com,www.myapp.com
EOF
seal push --env productionRun Django with secrets injected
# Development
seal exec --env development -- python manage.py runserver
# Run migrations
seal exec --env production -- python manage.py migrate
# Collect static files
seal exec --env production -- python manage.py collectstatic --noinput
# Django shell with production secrets
seal exec --env production -- python manage.py shellsettings.py — no changes needed
# settings.py
import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DATABASES = {'default': dj_database_url.parse(os.environ['DATABASE_URL'])}
AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']Scenario D — Docker / Docker Compose
Option 1 — Inject secrets into a container at runtime
# Pull secrets to a temp file, run container, then delete
seal pull --env production --out /tmp/.env.prod
docker run --env-file /tmp/.env.prod myapp:latest
rm /tmp/.env.prodOption 2 — Use seal exec to wrap docker-compose
# Secrets are injected into docker-compose's environment
seal exec --env production -- docker-compose upYour docker-compose.yml reads from the environment:
# docker-compose.yml
services:
app:
image: myapp:latest
environment:
- DB_HOST=${DB_HOST}
- DB_PASS=${DB_PASS}
- API_KEY=${API_KEY}Option 3 — Build-time secrets (multi-stage Dockerfile)
# Dockerfile
FROM node:22-alpine AS builder
# Build stage — no secrets needed
FROM node:22-alpine AS runtime
# Secrets are injected at runtime via seal exec, not baked into the image
COPY --from=builder /app /app
CMD ["node", "server.js"]# Run with secrets injected
seal exec --env production -- docker run myapp:latestScenario E — Monorepo (Multiple Services)
Each service has its own ArcanumSeal store. Use --env to namespace by service:
# Initialize once at the monorepo root
seal init
# Push secrets per service
seal push --env api-production
seal push --env worker-production
seal push --env frontend-production
# Run each service with its own secrets
seal exec --env api-production -- node services/api/server.js
seal exec --env worker-production -- node services/worker/index.jsFile structure:
monorepo/
├── .arcanumSeal/
│ ├── config.json
│ ├── team.json
│ ├── api-production.enc.json
│ ├── worker-production.enc.json
│ └── frontend-production.enc.json
├── services/
│ ├── api/
│ ├── worker/
│ └── frontend/
└── .gitignoreScenario F — Pre-commit Hook (Leak Prevention)
Automatically scan for leaked secrets before every commit:
# .git/hooks/pre-commit
#!/bin/sh
set -e
echo "ArcanumSeal: scanning for leaked secrets..."
seal scan --files
if [ $? -ne 0 ]; then
echo ""
echo "✗ Commit blocked — secret values found in working files."
echo " Remove the secrets and run: seal push --env <env>"
exit 1
fichmod +x .git/hooks/pre-commitOr with Husky:
npm install --save-dev husky
npx husky init
echo "seal scan --files" > .husky/pre-commitScenario G — Environment-Specific Configs (dev / staging / prod)
# Create separate .env files per environment
cat > .env.development << 'EOF'
DB_HOST=localhost
DB_PASS=devpassword
API_KEY=sk-dev-test123
EOF
cat > .env.staging << 'EOF'
DB_HOST=staging-db.example.com
DB_PASS=staging-secret
API_KEY=sk-staging-abc456
EOF
cat > .env.production << 'EOF'
DB_HOST=prod-db.example.com
DB_PASS=ultra-secret-prod-pass
API_KEY=sk-prod-xyz789
EOF
# Encrypt all three
seal push --env development
seal push --env staging
seal push --env production
# Commit all encrypted stores
git add .arcanumSeal/
git commit -m "chore: encrypt all environment secrets"Switch environments:
# Local development
seal exec --env development -- npm run dev
# Integration tests against staging
seal exec --env staging -- npm test
# Production deployment
seal exec --env production -- npm run deployScenario H — Validate Secrets Before Deployment
Use seal validate as a deployment gate — it exits with code 1 if any required secrets are missing, blocking the deployment:
# package.json
{
"scripts": {
"predeploy": "seal validate --env production --keys DB_HOST,DB_PASS,API_KEY,STRIPE_SECRET",
"deploy": "node scripts/deploy.js"
}
}npm run deploy
# predeploy runs first:
# ✓ All 4 required secrets present for environment: production
# Then deploy runsIf a secret is missing:
✗ Validation failed for environment: production
Missing secrets (1):
✗ STRIPE_SECRET
Present secrets (3):
Run: seal push --env production to add missing secretsThe deploy is blocked with exit code 1.
CI/CD Integration
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ArcanumSeal
run: npm install -g arcanumSeal
- name: Write CI private key
run: |
mkdir -p ~/.arcanumSeal
echo "${{ secrets.ARCANUMSEAL_KEY }}" > ~/.arcanumSeal/master.key
chmod 600 ~/.arcanumSeal/master.key
- name: Validate secrets present
run: seal validate --env production
- name: Pull secrets and deploy
run: seal exec --env production -- npm run deploy
# OR: seal pull --env production && npm run deploySetup:
- Run
seal keygenlocally to generate a CI keypair - Run
seal grant [email protected] --key <ci-public-key>to grant CI access (The person runningseal initis automatically registered as admin inteam.json) - Store the CI private key as a GitHub Actions secret named
ARCANUMSEAL_KEY - Commit and push the re-encrypted blobs
Validate secrets in CI
# Fails with exit code 1 if any secrets are missing — blocks deployment
seal validate --env production
# Validate specific required keys
seal validate --env production --keys DB_HOST,DB_PASS,API_KEY,STRIPE_KEYHow It Works
ArcanumSeal uses NaCl box encryption (X25519 key exchange + XSalsa20-Poly1305 AEAD) via the audited tweetnacl library.
Encrypting a secret:
- Generate a one-time ephemeral X25519 keypair
- Generate a random 24-byte nonce
- Encrypt with
nacl.box(plaintext, nonce, recipientPublicKey, ephemeralPrivateKey) - Store
{ ciphertext, nonce, ephemeralPublicKey }— discard ephemeral private key - Each secret has its own unique ephemeral keypair
Decrypting a secret:
- Load your private key from
~/.arcanumSeal/master.key - Call
nacl.box.open(ciphertext, nonce, ephemeralPublicKey, yourPrivateKey) - Returns plaintext bytes → UTF-8 string
Multi-recipient encryption:
When you seal push, every secret is encrypted independently for each team member using their own ephemeral keypair. The store holds a recipients map (publicKey → EncryptedSecret) alongside the primary encrypted field. When you seal pull, ArcanumSeal looks up your public key in the recipients map first, then falls back to the primary field.
This means:
seal grantre-encrypts once for all members simultaneously — no second pass neededseal revokerebuilds the recipients map without the revoked key — one atomic operation- Each member's ciphertext is independent — compromising one does not affect others
Why this design:
- Compromising one secret does not compromise others (unique ephemeral keys per recipient)
- The git repository never sees plaintext
- Standard, audited cryptography — not custom
- Backward-compatible: stores without a
recipientsmap still decrypt via the primary field
File Structure
your-project/
├── .arcanumSeal/
│ ├── config.json ← project config (commit ✓)
│ ├── team.json ← team public keys (commit ✓)
│ ├── production.enc.json ← encrypted secrets (commit ✓)
│ ├── staging.enc.json ← encrypted secrets (commit ✓)
│ └── audit.log ← local audit log (do NOT commit — gitignored)
└── .gitignore ← updated automatically
~/.arcanumSeal/
├── master.key ← YOUR private key (NEVER commit — chmod 600)
└── master.pub ← YOUR public key (safe to share)Command Reference
| Command | Description |
|---|---|
| seal init | Initialize ArcanumSeal in the current project |
| seal keygen | Generate your personal X25519 keypair |
| seal push --env <env> | Encrypt .env.<env> and store it |
| seal pull --env <env> | Decrypt secrets to .env.<env> |
| seal exec --env <env> -- <cmd> | Run command with secrets injected (no disk write) |
| seal grant <email> --key <key> | Grant a team member access |
| seal revoke <email> | Revoke a team member's access |
| seal request-access --to <email> | Print your public key for an admin |
| seal team list | List all team members and their access |
| seal team promote <email> | Promote a member to admin role |
| seal team demote <email> | Demote an admin to member role |
| seal rotate <KEY> --env <env> | Rotate a single secret to a new value |
| seal rotation-report | Show rotation health for all secrets |
| seal audit | View the full audit log |
| seal audit --export <file> | Export audit log as JSON |
| seal history --env <env> | Show push/rotation history |
| seal diff <p1> <p2> --env <env> | Compare two push snapshots (no plaintext shown) |
| seal scan --git | Scan git history for leaked secrets |
| seal scan --files | Scan working files for secret values |
| seal validate --env <env> | Validate all expected secrets are present |
Security
- Algorithm: NaCl box — X25519 key exchange + XSalsa20-Poly1305 AEAD
- Library:
tweetnacl— audited, zero dependencies, 3.2kb - Private key: Never leaves your machine. Stored at
~/.arcanumSeal/master.keywithchmod 600 - Ephemeral keys: Each secret encrypted with a unique one-time keypair
- No plaintext: Only encrypted blobs are ever written to disk or committed to git
- No network: Fully offline. Zero HTTP requests at runtime
- Audit trail: Every push, pull, grant, revoke, and rotation is logged locally
See docs/SECURITY.md for the full security model.
CI secret rotation: Use the
SEAL_ROTATE_VALUEenvironment variable instead of--valueto avoid exposing secrets in process argument lists (ps aux):SEAL_ROTATE_VALUE=newvalue seal rotate DB_PASS --env production
Tech Stack
| | |
|---|---|
| Language | TypeScript (strict mode) |
| Runtime | Node.js 22+ |
| Module | ESM (import/export) |
| Encryption | tweetnacl ^1.0.3 |
| Encoding | tweetnacl-util ^0.15.1 |
| CLI parser | Built from scratch (no commander/yargs) |
| Terminal output | Built from scratch (no chalk) |
| Prompts | Built from scratch (no inquirer) |
| .env parser | Built from scratch (no dotenv) |
| Tests | Vitest — 100+ tests, 9 test files |
Total production dependencies: 2 (tweetnacl + tweetnacl-util)
Development
git clone https://github.com/your-org/arcanumSeal
cd arcanumSeal
npm install
# Run in dev mode
npm run dev -- keygen
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Type check
npm run lint
# Build
npm run buildLicense
MIT — see LICENSE
