sealcode
v1.5.0
Published
Lock your source code in your own git repo. Stop AI agents, scrapers, and curious eyes from reading what's yours.
Maintainers
Readme
sealcode
Lock your source code inside your own git repo. Stop AI agents, scrapers, and curious eyes from reading what's yours.
npm install -g sealcodeRenamed from
vaultline. Existing vaults keep working unchanged — the on-disk format is unchanged,.vaultlinerc.jsonis still read, andVAULTLINE_PASSPHRASEis still accepted as a fallback.
What problem does this solve?
You push code to a private repo. You think it's safe. But anyone (or any AI) with read access to that repo — a contractor, a teammate's compromised laptop, a future Copilot indexing run, a leak — can read every line.
sealcode wraps your source files in AES-256-GCM ciphertext before they touch git. Your repo becomes a folder of opaque binary blobs with a stub package.json. To anyone without your passphrase, your codebase is uniformly random bytes.
To you, one command brings it all back:
sealcode unlock # readable code is restored
# ... edit ...
sealcode lock # back to ciphertext
git commit && git pushQuickstart (60 seconds)
cd your-project
sealcode init # interactive wizard
# 1. Auto-detects your stack (Node, Python, Go, Rust, Ruby, PHP, Java, ...)
# 2. Picks safe include/exclude defaults
# 3. Asks for a passphrase
# 4. Shows a recovery code — write it down ONCE
# 5. Locks every source file immediately
git add . && git commit -m "vault initialized" && git pushWhen you want to work on it:
sealcode unlock
npm install
npm run devWhen you're done:
sealcode lock
git commit && git pushWhy this beats git-crypt, git-secret, sops, etc.
| | sealcode | git-crypt | git-secret | sops | |---|---|---|---|---| | Encrypts whole files (not just diffs) | ✅ | partial | ✅ | ❌ (values only) | | Filenames hidden | ✅ | ❌ | ❌ | ❌ | | Repo metadata hidden (package.json, deps) | ✅ | ❌ | ❌ | ❌ | | Passphrase-based (no GPG keyring) | ✅ | ❌ | ❌ | partial | | Recovery code if you lose passphrase | ✅ | ❌ | ❌ | ❌ | | Works on any language/stack | ✅ | ✅ | ✅ | ✅ | | Auto-detects your ecosystem | ✅ | ❌ | ❌ | ❌ | | Built-in drift detection | ✅ | ❌ | ❌ | ❌ |
The big one: sealcode hides that your code exists at all. Other tools encrypt the files but leave the folder structure, the filenames, and the package.json (with every telltale dependency) in plain view.
Commands
sealcode init # one-time setup; interactive wizard
# --preset auto universal coverage via git ls-files
# (default since 1.4.0)
# --allow-monorepo override the multi-microservice
# guard (one vault for the whole tree;
# billed as one project)
sealcode scan # dry-run: show what would be locked, with coverage
# report, suspicious-exclusion hints, and a
# monorepo / multi-microservice notice if any
# sibling service directories are present
sealcode lock # encrypt source → vendor/ blobs (removes plaintext)
sealcode unlock # decrypt blobs → restore source (removes stubs)
sealcode verify # confirm every blob decrypts & matches its hash
sealcode status # show locked/unlocked + drift since last lock
sealcode status --check # exit 1 if unlocked (used by git hook). 1.4.1+: strict — passes only when locked.
sealcode status --check --allow-clean-unlock # 1.4.1+ escape hatch: only fail on drift
sealcode status --json # machine-readable state (editors / scripts)
sealcode rotate # change passphrase (blobs unchanged); env: SEALCODE_OLD_PASSPHRASE / SEALCODE_NEW_PASSPHRASE
sealcode backup <dir> # copy locked vault + config snapshot to a new folder
sealcode restore <dir> # restore from backup (use --force if locked dir exists)
sealcode install-hook # git pre-commit: block any commit while the project is unlocked (strict by default in 1.4.1+)
sealcode install-hook --lenient # pre-1.4.1 behavior: only block when the working tree has drifted vs the lock
sealcode uninstall-hook # remove hook block
sealcode panic # immediate re-lock + session wipe
sealcode logout # clear cached session, force passphrase next time
sealcode remove # permanently uninstall sealcode from this project (requires passphrase; emails project owner if linked)
sealcode presets # list supported ecosystems
sealcode share <opts> # mint a temporary access code (--email <addr> wraps the key for them)
# 1.1 controls (all optional):
# --paths src/api/,tests/ only decrypt these prefixes
# --mode ro read-only; watcher re-locks on edit
# --watermark "Licensed to {email} · grant {grantId}"
# --idle 30 auto-lock after N idle minutes
# --allow-ip 10.0.0.0/8 restrict redeem + heartbeat IPs
# --allow-country US GB ISO-2 country allowlist (Cloudflare-fed)
# --single-device hard-reject 2nd device fingerprint
# --nda "text" require typed "I agree" on redeem
sealcode redeem <code> # accept a code; auto-unlocks + spawns watcher when team-shared
sealcode watch <code> # poll the server with long-poll (~1s revoke); --daemon for background
sealcode lockdown # 1.1: panic — revoke ALL active grants for the linked project
sealcode install-service # macOS launchd / Linux systemd unit so the watcher survives reboots
sealcode uninstall-service
sealcode pro # Pro tier info
sealcode where # debug: print paths and state (no secrets)Aliases: sc, sealcode seal = lock, sealcode open = unlock.
Unlinking vs. removing
There are three different "undo" actions, and it's worth being precise about which one you want:
| Command | What it does | Touches the server? | Reversible? |
|---|---|---|---|
| sealcode link --remove | Forgets the dashboard association on THIS checkout. The vault stays. The server still has the project. | No. | Yes — just sealcode link <id> again. |
| Delete project (dashboard) | Releases the paid slot and the server-side repo binding. The local vault is untouched. | Yes (owner only). | No (the slot is gone). |
| sealcode remove | Permanently uninstalls sealcode from this local repo. Decrypts files back to plaintext, deletes vendor/, the config, and the cached session. | Yes if linked: emails the owner and audit-logs project.remove BEFORE local destruction. | No. |
sealcode remove requires the project passphrase (even if a session is unlocked) and a typed yes-remove confirmation. When the project is linked, it refuses to run if it can't reach sealcode.dev — that way no one can bypass the owner email by cutting the network. Use --offline to override explicitly.
Other useful flags:
sealcode remove --confirm yes-remove # skip the interactive prompt (for scripts)
sealcode remove --burn # discard ciphertext WITHOUT decrypting first (data loss)
sealcode remove --offline # don't notify the owner (we still try first)One vault per project
sealcode is licensed and operated per project, and the CLI plus the server enforce that.
Locally, at sealcode init — if your repo is a monorepo with multiple microservices (a root pnpm-workspace.yaml, a services/-style layout with several package.json files, etc.), sealcode init refuses at the root and tells you which subdirectories to initialize separately. Each service then gets its own keys, its own grants, its own audit trail, and — going forward — its own billing line item.
If you genuinely want one vault for the whole tree (small solo repo, intentional experiment monorepo) pass --allow-monorepo to override. Run sealcode scan first to see what was detected.
Server-side, at sealcode link — each project record on sealcode.dev is pinned 1:1 to a single local repository. The first sealcode link <id> records an opaque fingerprint of the repo's vault salt on the server; any later link attempt from a different local repo is rejected with HTTP 409 and SEALCODE_PROJECT_REPO_MISMATCH. Teammates cloning the same repo pass the check because they share the same salt.
If you really do need to migrate a paid project to a new repo, the project owner can re-pin with:
sealcode link --force <projectId>The force flag is owner-only and gets audit-logged as project.link.force.
Editor support
A VS Code companion lives in tools/vaultline-vscode/ — it shows the current lock state in the status bar when the CLI is on your PATH. (Rename in flight; the extension itself works against either sealcode or vaultline on PATH.)
CI (GitHub Actions)
- run: npm install -g sealcode
- env:
SEALCODE_PASSPHRASE: ${{ secrets.SEALCODE_PASSPHRASE }}
run: |
sealcode unlock
npm install --omit=dev
npm run buildSupported stacks (auto-detected)
| Marker file | Preset | Locked dir |
|---|---|---|
| package.json (no Next config) | Node.js / TypeScript | vendor/ |
| next.config.* | Next.js | vendor/ |
| requirements.txt / pyproject.toml / setup.py | Python | _site_packages/ |
| manage.py | Django | _site_packages/ |
| go.mod | Go | internal/sealed/ |
| Cargo.toml | Rust | target/.cache/ |
| Gemfile | Ruby on Rails | vendor/sealed/ |
| composer.json | PHP / Laravel | storage/sealed/ |
| pom.xml / build.gradle* | Java / JVM | .sealed/ |
| (none of the above) | generic | vendor/ |
Each preset uses a lockedDir name that looks native to its ecosystem — Go projects don't get a suspicious folder called locked/, they get internal/sealed/. Stealth by camouflage.
How the crypto works (the short version)
- Algorithm: AES-256-GCM with a random 12-byte IV per blob; gzip before encrypt.
- Key derivation: scrypt(passphrase, salt) → 32-byte wrapping key, N=2^17, r=8, p=1 (~1s, ~128MB).
- Master data key: randomly generated, stored on disk wrapped twice — once by your passphrase, once by a 128-bit recovery seed (the 30-char recovery code you wrote down at init).
- File-on-disk format:
[12B IV][16B GCM tag][ciphertext]with no header bytes. Indistinguishable from random data. - Filenames inside
vendor/: HMAC-SHA256 of the path, sharded by first 2 hex chars, truncated to 12. Stable across re-locks, indistinguishable across files. - No
# CV1or other text signatures that would letfile(1)/ scrapers / AI identify the format.
Where does the passphrase live?
Wherever you put it. The tool checks:
- Cached session (~8h after last unlock — stored at
~/.sealcode/sessions/<id>, encrypted with a host-binding key) SEALCODE_PASSPHRASEenv var (use in CI).VAULTLINE_PASSPHRASEis accepted as a fallback for projects upgrading from vaultline 1.x.- Interactive prompt
sealcode never writes your passphrase to disk in any recoverable form. Lose your passphrase AND your recovery code, and your code is gone. We can't help you. That's not a bug — it's the entire point.
Back up your recovery code in:
- a password manager (1Password / Bitwarden as a secure note)
- a printed sheet
- an encrypted USB / hardware key
CI / deploy
# GitHub Actions
- run: npm install -g sealcode
- env:
SEALCODE_PASSPHRASE: ${{ secrets.SEALCODE_PASSPHRASE }}
run: |
sealcode unlock
npm install --omit=dev
npm run build # produces dist/main.js
- run: rsync dist/ user@server:/app/dist/Your production server never sees the passphrase or the vault. It only runs the built dist/.
Pro features (coming soon)
The free CLI handles lock / unlock / verify forever. sealcode Pro adds:
- Team key sharing — invite teammates, no passphrase exchange needed
- Key rotation across N projects with one command
- Audit log — see who unlocked what, when
- Cloud key escrow — passphrase recovery via account auth
- Temporary access codes — time-boxed, revocable read access to a project
- CI/CD tokens — short-lived deploy keys, no long-lived secrets in Actions
Start a 14-day trial at sealcode.dev/pro.
FAQ
Does this affect git diffs?
Yes — vendor/ blobs change every lock (random IV). For human review, diff your local unlocked tree before locking. A sealcode diff <commit> command is on the Pro roadmap.
Can I encrypt only some files?
Yes — edit .sealcoderc.json's include array.
Does it work on Windows? Yes. Forward-slash normalization in opaque names; tested on macOS, Linux, Windows.
What's the perf cost? First unlock with passphrase: ~1s (scrypt). Subsequent commands within 8h: <100ms (cached session). Lock/unlock of a 1000-file repo: ~2s.
Is this audited? Not formally. The crypto is standard primitives (AES-256-GCM, scrypt) used in their textbook configurations. Audit yourself at the repo. We'll fund a formal audit once revenue supports it.
I had vaultline installed before — do I lose my vault?
No. The on-disk format is unchanged. sealcode reads any existing .vaultlinerc.json, your old ~/.vaultline/sessions/ cache, and VAULTLINE_PASSPHRASE env var. On the next sealcode lock the project will start writing the new config name (.sealcoderc.json), but the encrypted blobs themselves never need to be re-encrypted.
Contributing & tests
The CLI ships with a small fast test suite that runs in a tmpdir — no Postgres, no network. From tools/sealcode-cli/:
npm testThe seven targeted regression tests live under test/ and cover:
- envelope wrap/unwrap round-trip (the team-share crypto core)
- auto-preset coverage on a synthetic Next.js repo (the 1.4 motivating bug)
preserveUnseensemantics across scoped re-locks (1.3.6 safety guard)- read-only mode re-lock after EACCES (1.3.3 stub-write fix)
- watcher heartbeat classification of 5xx / network blips as transient
unlock-checkblocking unlock onrevoked/expiredserver responses- stale-pubkey warning when sharing to a recipient whose published key is >30 days old
Filter to a single test with FILTER=auto-preset npm test.
License
MIT. See LICENSE.
