Installation
Requires Python 3.12 or newer. Runs on Windows, macOS and Linux.
$ pip install sealbox # or $ pipx install sealbox # isolated environment, available everywhere $ pip install -e . # from source (at the repo root)
Dependencies: cryptography, pydantic, shamir-mnemonic, keyring, bcrypt.
General usage
- -h, --help
- Show help.
sealboxwith no command shows the grouped list. - -V, --version
- Show the version.
- --lang {tr,en}
- Message language. Can also be set with the
SEALBOX_LANGenvironment variable. Default: your computer's language. - help <command>
- Details and examples for a command (same as
sealbox <command> --help).
Commands come in five groups:
| Group | Commands |
|---|---|
| Getting started | init, status |
| Everyday use | seal, exec, vault |
| Team | team |
| Recovery | recovery |
| Git | history, hook |
Mistyped commands get a suggestion: sealbox sael → "Did you mean: seal?". The commands are the same in any IDE's terminal (VS Code, PyCharm, Eclipse…).
sealbox init
changes no filesChecks the project and lists the next steps in order. It's the first command to run in a new project.
- -w, --workspace FOLDER
- Project root (default: the current folder).
- -c, --config FILE
- Path to
sealbox.toml.
$ sealbox init # check the project in the current folder $ sealbox init -w C:\my-project # another folder
sealbox status
changes no filesShows the state of the vault, the key, the recovery code and the team. Every problem line tells you what to do next. Exits with code 1 if something can't be opened.
- --json
- Machine-readable output for scripts and CI.
$ sealbox status ✓ Vault: 4 variables, 2 redacted values ✗ The vault key is not on this computer → sealbox recovery restore · No team (single user) → sealbox team init --github <name> ⚠ Exposed secrets in 2 file(s) → sealbox seal
sealbox seal
rewrites filesFinds passwords, tokens, API keys and personal data in your files:
- Moves secrets in code into the vault and writes an environment-variable lookup in their place (Python
os.environ, JS/TSprocess.env, JavaSystem.getenv). - Redacts data such as emails and national IDs with a
__SECURE_..__placeholder. - Takes
.envinto the vault and leaves.env.sealedin its place.
- paths
- Files or folders (default: the whole project).
- --check
- Don't write, only look. Exit code 1 if secrets are found.
- --json
- Print the report as JSON: lines, columns and categories, never values. The editor extension uses this.
- -w, --workspace
- Project root.
- -c, --config
- Path to
sealbox.toml.
$ sealbox seal --check # only look: any exposed secrets on disk? $ sealbox seal # move every secret in the project into the vault $ sealbox seal src/config.py # a single file
Run it from the project root. When run from a subfolder, it stops if there's a .env, .git or vault above, because that .env would be left exposed. If you chose that folder on purpose, pass -w ..
A file whose redaction can't be verified is left alone with a [!] warning, and the rest of the scan carries on. Redaction backups are encrypted with the vault key too.
sealbox exec
asks for approvalRuns your program with the secrets from the vault. An approval window first shows which values will be handed over. They're given only to this run, as environment variables. Secrets in the program's output are printed as [REDACTED:..].
- -- command
- The command to run, after
--. - -w, --workspace
- Project root (default: the nearest parent folder with a vault).
$ sealbox exec -- python app.py # Python $ sealbox exec -- npm start # Node.js $ sealbox exec -- java Main.java # Java $ sealbox exec -- mvn spring-boot:run # Maven / Gradle
By design: there's deliberately no flag or environment variable that skips approval. The child process's output goes through a pipe, so interactive or colourful terminal programs may behave differently.
sealbox vault
Manages the entries in the vault. Values are never printed to the terminal on their own. Showing and changing them asks for approval. Every subcommand accepts -w, --workspace.
| Subcommand | Usage | What it does |
|---|---|---|
list | vault list | Variable names and redaction placeholders. Shows no values. |
reveal | vault reveal NAME | Shows the value in the approval window. NAME can be a variable or a __SECURE_..__ placeholder. |
set | vault set NAME | Changes or adds a value (asked for in a window). |
expose | vault expose PLACEHOLDER [--as NAME] | Hands redacted data to the program at runtime. The placeholder becomes os.environ[...] in the code. --as sets the variable name (default: taken from the assignment). |
remove | vault remove [NAME...] [--unused] [--force] | Deletes entries (asks for approval). --unused: entries no file uses. --force: delete even if a file still uses it. |
reset | vault reset | If the key is lost and there's no recovery, creates a new empty vault. The old one is set aside. |
$ sealbox vault list $ sealbox vault reveal DB_PASSWORD $ sealbox vault reveal __SECURE_TCKN_103d…__ $ sealbox vault set DB_PASSWORD $ sealbox vault expose __SECURE_EMAIL_8f2c…__ --as ADMIN_EMAIL $ sealbox vault remove --unused
The same value always maps to the same entry, so sealing again creates no duplicates. An entry whose placeholder or variable name still appears in a file isn't deleted without --force.
sealbox team
Shares the vault with your teammates. The vault goes into the repo, and only the SSH keys of the GitHub accounts on the team can open the values. commit + push after every change.
| Subcommand | Usage | What it does |
|---|---|---|
init | team init --github NAME | Creates the team with you as owner. This computer's SSH key must be on that GitHub account. |
add | team add USER [--owner] | Fetches public keys from github.com/USER.keys and grants access. --owner: add as owner. |
remove | team remove USER | Removes them from the team. The vault key (DEK) is rotated and the values they may have seen are listed. |
promote | team promote USER | Makes a member an owner. |
list | team list | Members, roles and SSH fingerprints. |
$ sealbox team init --github my-name $ sealbox team add teammate $ sealbox team add teammate --owner $ sealbox team remove teammate $ sealbox team list
- Owner / member: owners can add and remove people. The rule is enforced in software: a member who can open the vault could technically hand a copy to someone else.
- Supported keys: ed25519 (converted to X25519) and RSA (OAEP).
- SSH keys without a passphrase are rejected. To add one:
ssh-keygen -p -f ~/.ssh/id_ed25519. The passphrase is asked for in a window, not in the terminal. - Integrity: every entry in
team.jsoncarries a MAC made with a key derived from the DEK. Someone who can write to the repo but doesn't know the DEK can't add a fake member or role.
sealbox recovery
A recovery code that reopens the vault if the vault key is lost (Windows reset, new computer). The code and shares are only shown in a window, never printed to the terminal.
| Subcommand | Usage | What it does |
|---|---|---|
setup | recovery setup [--new] | Creates a recovery code (one per user). --new: replace the existing code with a new one. |
shares | recovery shares | Splits the code into 2-of-3 Shamir shares (SLIP-39). Any 2 are enough. |
restore | recovery restore [--kit FILE] | Brings the key back with the code or 2 shares. --kit: from a kit, if the vault folder is gone too. |
export | recovery export FILE | Writes a recovery kit (locked DEK + encrypted vault). Useless without the code. |
status | recovery status [--json] | Is recovery set up? |
$ sealbox recovery setup $ sealbox recovery shares $ sealbox recovery export D:\project.sapkit $ sealbox recovery restore $ sealbox recovery restore --kit D:\project.sapkit
Recovery copies live under ~/.sealbox/recovery/, not in the project folder, so they never enter the repo. Because the public half is stored, new projects are locked to recovery without asking for the code.
sealbox history
changes no filesSearches the whole git history for vault values. A secret committed before it was sealed can still be read in old commits. Values are never printed: only the name, the commit and the file. Exit code 1 if anything is found.
- -w, --workspace
- Project root.
- --json
- Print the report as JSON (used by the editor extension). Contains no values.
$ sealbox history Git history: ⚠ DB_PASSWORD: exposed in 2 commit(s), pushed to a remote first: 3f9c2e1 2026-10-02, src/config.py → rotate it at the provider, then: sealbox vault set DB_PASSWORD
Pushed means shared: if a commit is already on a remote branch, the value may be in someone else's hands. Rotate it at the provider; rewriting history alone isn't enough.
sealbox hook
Installs a git pre-commit hook: if a vault value is about to be committed, the commit is stopped with file:line. It's a seat belt against accidents; git commit --no-verify skips it. The hook isn't committed, so each teammate installs it on their own machine.
| Subcommand | What it does |
|---|---|
install | Install the pre-commit hook |
uninstall | Remove the SealBox hook |
status [--json] | Is the hook installed? |
run | The check the hook runs (can be run by hand) |
$ sealbox hook install $ sealbox hook status
What's found, what's done
| What was found | What's done |
|---|---|
Password / token / API key as a whole string literal (.py, .js, .ts, .java…) | Moved to the vault. Becomes os.environ["DB_PASSWORD"] / process.env.DB_PASSWORD / System.getenv("DB_PASSWORD") |
| The same value repeated in a comment | Redacted with a placeholder |
| Email, national ID (TCKN), phone, IBAN, name | Redacted with a placeholder. If the program uses it, vault expose turns it into an environment variable |
.env | Secret lines move to the vault. .env is deleted and a .env.sealed with only placeholders stays |
| JSON / YAML / TOML config file | Redacted. The report says which environment variable the app should read the value from |
| Part of a larger string (e.g. inside a URL), or an unsupported language | Left alone with a [!] warning. Move it by hand |
| A file whose redaction can't be verified | Left alone with a [!] warning. The rest of the scan carries on |
Validators
Matches are validated to cut false alarms: the Turkish ID number checksum, email, phone, Luhn (cards), IBAN (mod-97) and entropy. Placeholders look like __SECURE_<CATEGORY>_<128 bits>__.
Key hierarchy
If the vault key (DEK) lived in a single place, losing that place would lock the vault forever. So the DEK has several locks (envelope encryption):
Recovery code (128 bits) ─Argon2id (64 MiB, t=3, p=4)─► Recovery KEK = X25519 ─┐ GitHub SSH key (ed25519→X25519 or RSA-OAEP) ────────────────────────────────────┼─► DEK ─► vault Windows credential store (cache) ───────────────────────────────────────────────┘
- Vault encryption: AES-256-GCM, random nonce, replay protection, AAD.
- Where the DEK lives: in the OS credential store (
keyring). On Windows in Credential Manager under the name sealbox, on macOS in the Keychain, on Linux in the Secret Service. Never on disk. - Unlock order: Windows store → (with a team) SSH key → an error with a recovery suggestion.
- If the key was rotated: when another owner rotated it (
dek_idmismatch), the new one is fetched via SSH.
What goes in the repo
| File | In the repo | Description |
|---|---|---|
.sealbox/project.vault | yes | The encrypted vault |
.sealbox/project.names | yes | Variable names and categories (no values). vault list reads this without opening the vault |
.sealbox/project.masked | yes | Placeholders of unnamed redacted entries |
.sealbox/team.json | yes | Members, roles, per-person locked copies of the DEK |
| Backups, lock files | no | Kept out by .sealbox/.gitignore |
~/.sealbox/recovery/ | no | In your user folder, outside the project |
Watch out: if the root .gitignore excludes the whole .sealbox/ folder, the team commands and status warn you. Secrets committed before the vault was added stay in git history and must be changed.
Configuration: sealbox.toml
Copy sealbox.example.toml to the project root as sealbox.toml, or pass it with --config. Never put real secrets in this file.
max_file_bytes = 524288 # To explicitly override the default excludes (.env, *.pem, .git/** ...): explicit_includes = [".env.example"] [scanner] # Names can't be found reliably with regex: list the names to redact explicitly. person_names = [] # Exact values, to suppress false positives: allow_values = [] disabled_rules = [] [[scanner.custom_rules]] id = "project-code" category = "CUSTOM" pattern = 'PRJ-\d{6}' file_globs = ["*.py", "*.md"]
| Field | Default | Description |
|---|---|---|
excludes | .env, *.pem, *.key, id_rsa*, .git/**, .venv/**, node_modules/**, build/**, .ssh/**… | Sensitive paths. Never scanned or written (.env is handled separately) |
explicit_includes | [] | To override an exclude on purpose |
max_file_bytes | 512 KiB | Larger files are skipped |
scanner.person_names | [] | Names to redact |
scanner.custom_rules | Add rules (id, category, pattern, file_globs) | |
scanner.disabled_rules | [] | Turn rules off |
scanner.allow_values | [] | Suppress false positives (exact value) |
Settings left over from older versions — [provider], [policy], share_dirs, audit_log_file and max_files_per_job — are ignored.
VS Code extension
The extension is the sealbox command's remote control in your editor; the command does all the work. Install it from the Marketplace or Open VSX or:
$ code --install-extension sealbox-0.3.0.vsix
Settings
| Setting | Default | Description |
|---|---|---|
sealbox.executable | "" | Path to the sealbox program. If empty, the project's .venv is searched first, then PATH. The old secureProxy.executable setting is also read. |
sealbox.checkOnSave | true | Check for exposed secrets when a file is opened or saved. |
Features
| Feature | What it does |
|---|---|
| Exposed-secret warnings | seal --check runs when a file is opened or saved, and secrets get a yellow squiggle |
| Quick fix | Lightbulb → "Move this file's secrets into the vault" |
| Hover | __SECURE_..__, os.environ["X"], process.env.X, System.getenv("X") → "Show value" (with approval) |
| Give at runtime | Lightbulb or hover on redacted data → vault expose |
| Status bar | Number of secrets in the vault and people on the team; a warning when there's no recovery backup |
| Run with the vault | Starts the command in the terminal with sealbox exec (approval every time) |
| Plain-run hint | If a program started with ▶ fails with KeyError for a variable in the vault, "Run with the vault" is suggested |
| Team and recovery | Set up a team and add or remove people from the menu. Recovery code, shares, kit, restore. These options are offered automatically when the key is missing |
A revealed value only appears in a window that closes. If you choose "Copy", the clipboard is cleared after 30 seconds. Menu titles are in English or Turkish, following VS Code's language.
What it shows and never shows
Shows
File paths, category names and counts, line/column, variable names, placeholders, error types.
Never shows
Secret values (only in the approval window, on request), the vault key, the recovery code and shares (only in a window), foreign exception messages (type name only).
Python logging records also pass through the leak filter.
Known limitations
- Regex can't find every secret. Unusually formatted passwords, and secrets that are split, concatenated, encoded (
\u0040, hex, ROT13) or written in natural language, may be missed. Review the report and addcustom_rulesif needed. - Name detection isn't reliable. Only the
person_nameslist and optionalDetectorplug-ins (e.g. an NER model) are used. - Short values (1–2 characters) can't be protected reliably.
- Wiping memory can't be guaranteed. The key is held as a
bytearrayand zeroed when done, but Python may make copies. Swap and crash dumps remain a risk. - Threat model: it closes the main scenario, "the agent reads a file and sends it to the cloud". A deliberately malicious program running under the same user account that goes after the credential store directly isn't fully blocked.
- The output filter doesn't catch secrets printed reversed or in pieces. The approval window is the real protection.
- A removed team member can open the old vault from git history with the old DEK.
team removelists those values; change them at their source.
Architecture
file ──► SafeFS ──► Scanner ──► Sealer ──────────► file (os.environ["X"] / __SECURE_..__)
path/symlink regex + move to vault / │
checks validators redact ▼
.sealbox/project.vault (AES-256-GCM)
key: credential store / SSH / recovery code
exec ──► approval window ──► secrets as environment variables ──► program (output filtered)
sealbox/
├── __main__.py CLI: commands, help texts and examples
├── cli_help.py Help plumbing, "did you mean"
├── i18n.py, i18n_en.py Turkish / English
├── status.py init / status checklist
├── config.py Pydantic configuration + safe defaults
├── vaultstore.py Project vault and key unlocking
├── runner.py exec, vault reveal/set/expose/remove
├── team.py Team sharing via GitHub SSH (team.json)
├── recovery.py Recovery code, 2-of-3 shares, kit
├── approval.py Approval and secret-value windows
├── sealing/
│ ├── sealer.py Move-to-vault / redaction plan
│ ├── rewriters.py Literal → environment variable (Python, JS/TS, Java)
│ └── file_operations.py SafeFS: path safety, locking, atomic writes
└── core/
├── scanner.py Regex rules + Detector plug-in interface
├── validators.py TCKN, email, phone, Luhn, IBAN, entropy
├── tokenization.py __SECURE_..__ placeholders
├── crypto.py AES-256-GCM
├── envelope.py DEK locks: Argon2id, X25519, SSH
├── key_sharing.py SLIP-39 Shamir sharing
└── leak_guard.py Hiding secrets in output and logs
Migrating from the old name (secure-ai-proxy)
You don't need to do anything:
- Old
.secure_proxy/vault folders and the~/.secure_proxy/recovery folder keep working as they are. New projects use.sealbox/. - The old
secure-ai-proxyentry in Credential Manager moves to thesealboxname the first time the vault is opened, and the old one is deleted. secure_proxy.tomlis still read (sealbox.tomltakes priority). Thesecure-ai-proxycommand keeps working for a while too.
Development
$ pip install -e ".[dev]" $ pytest -q $ ruff check sealbox tests $ mypy sealbox $ cd vscode-extension && npm install && npm test
When adding a new message, wrap the text in _("...") and add its English version to sealbox/i18n_en.py. tests/test_i18n.py catches missing translations. CI: Ubuntu + Windows, Python 3.12/3.13.