A machine-level secrets vault for personal/small-team dev work: a thin PowerShell module over
Microsoft.PowerShell.SecretManagement + Microsoft.PowerShell.SecretStore, plus a CLI and a
local web UI on top of it. No cloud, no account, no subscription - everything lives in your own
Windows user profile.
Windows only. The setup/UI scripts use the Windows certificate store
(Cert:\CurrentUser\...), the Windows hosts file path, and Windows-only cmdlets
(Get-NetTCPConnection). pwsh itself and the underlying SecretManagement/SecretStore modules
are cross-platform, but this repo's tooling is not.
- Features
- Quick start
- PowerShell module
- CLI
- Web UI
- Naming convention & manifest.json
- Backup / restore
- Uninstalling
- Tests
- Known issues
- Get/set/remove secrets from any PowerShell session, on any repo on the machine
- A
strongboxCLI (list,get,set,reveal,backup,serve, ...) for people who don't want to remember cmdlet names - A local, bearer-token-gated web UI (optional HTTPS with a real "Secure" indicator) for browsing, editing, and revealing secrets
- Drift checking (
Test-Strongbox) - catches a manifest and vault that have quietly gone out of sync - Rotation-policy tracking with a staleness check, in the CLI, the module, and the UI
- Encrypted, passphrase-protected backup/restore - the one thing SecretStore doesn't provide on its own
- A safeguard specifically against scripts/AI agents grabbing a real secret by accident while testing a command (see CLI)
pwsh -NoProfile -File Install-StrongboxPrerequisites.ps1One command, idempotent (safe to re-run if it fails partway). It installs every module this tool
depends on (Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore, Pode),
bootstraps the NuGet provider and bypasses the PSGallery untrusted-repository prompt on a
genuinely fresh machine (via -Force on each Install-Module call, not by permanently trusting
PSGallery machine-wide), registers the vault, installs the Strongbox module, and registers the
strongbox CLI command.
Open a new shell, then:
strongbox helpImport-Module Strongbox
Get-Command -Module Strongbox| Function | What it does |
|---|---|
Get-StrongboxSecret -Name X [-Optional] |
Read a plaintext value |
Set-StrongboxSecret -Name X -Value Y [-Owner ...] [-RotationDays N] |
Write a value; stamps LastRotated automatically |
Remove-StrongboxSecret -Name X |
Delete a value |
Get-StrongboxSecretList |
Every tracked secret's name/owner/purpose/rotation/staleness - never values |
Get-StrongboxStaleSecrets |
Just the entries past their RotationDays policy |
Test-Strongbox |
Drift check: confirms manifest.json and the vault agree on what secrets exist |
Export-StrongboxBackup / Import-StrongboxBackup |
See Backup / restore |
Import-StrongboxSecretEnv -Map @{ENV_VAR='secret.name'} |
Set process-scoped env vars from the vault |
Export-StrongboxSecretUserSecrets -Project <csproj> -Map @{ConfigKey='secret.name'} |
Fan vault secrets into dotnet user-secrets |
Get-StrongboxSecretHeaders -Name X -Header Y [-Prefix 'Bearer '] |
JSON header object, for an MCP headersHelper |
A subcommand wrapper over the module functions above - pure presentation layer, no new logic.
strongbox list [--json] List every tracked secret
strongbox stale [--json] List only secrets past their rotation policy
strongbox check Drift check: manifest vs. vault
strongbox get <name> [--real] Print a secret's value to stdout (for scripting)
strongbox set <name> <value> [--rotation-days N] [--owner X]
strongbox remove <name> [--force] Prompts for confirmation unless --force
strongbox reveal <name> [--stdout] [--real] Copies to clipboard (auto-clears in 30s) by default;
--stdout prints it instead
strongbox backup export <path> Prompts for a passphrase - never pass one as an
strongbox backup import <path> [--force] argument, it would land in shell history
strongbox serve start|stop|status Manage the local web UI server
reveal defaults to clipboard-only (never printing the value), matching the web UI's reveal
modal's threat model - get is the deliberate exception, since its whole purpose is scripting/
piping the value somewhere.
Safeguard against scripts/agents grabbing the wrong secret. get/reveal refuse to touch
anything except the permanent sandbox secret tools.StrongboxSelfTest (auto-provisioned by
Register-StrongboxVault.ps1 with a harmless placeholder value) when run non-interactively (no
real terminal attached - a script, or an AI coding agent's tool calls), unless you pass --real.
A human typing at a real interactive prompt never sees this gate. It exists to prevent a script
or automated agent from grabbing a real secret when it's only trying to verify a command works -
tools.StrongboxSelfTest removes any reason to reach for a real name just to test with.
Registered as a bare strongbox command by Install-StrongboxPrerequisites.ps1 (or run
Install-StrongboxCli.ps1 on its own) - this adds a small forwarding function to $PROFILE
rather than adding .ps1 to $env:PATHEXT, since the latter would make every .ps1 file
anywhere on $env:Path silently executable by bare name.
pwsh -NoProfile -File ui\Start-StrongboxUi.ps1Or strongbox serve start / /strongbox from Claude Code - either way it's idempotent (safe to
run repeatedly) and won't print the bearer token unless one is freshly generated.
Binds 127.0.0.1 only - no external network exposure either way. The auth token is generated
once and persisted to .token (gitignored); the browser saves it to localStorage after first
use, so you shouldn't need to paste it again. Delete .token to force a fresh one.
Every reveal, add/update, and delete is appended to ui\audit.log (gitignored, local-only) -
who/when/what, never values.
By default the server serves plain HTTP on port 80 at http://127.0.0.1 (Chrome shows a "Not
secure" indicator for any hostname other than the literal localhost served over HTTP; this is
purely cosmetic, since nothing ever leaves the loopback interface either way. For a friendlier
hostname and a real "Secure" indicator, run this once per machine:
# 1. Requires an elevated PowerShell (hosts file is a protected system file):
Add-Content -Path "$env:WINDIR\System32\drivers\etc\hosts" -Value "127.0.0.1 strongbox.local"
# 2. Does NOT require elevation - generates a self-signed cert and trusts it for your user only:
pwsh -NoProfile -File ui\New-StrongboxCert.ps1Start (or restart) the server afterward - it auto-detects the trusted cert and switches to HTTPS
on https://strongbox.local (no port needed); without the cert it falls back to plain HTTP.
Certificate trust is inherently per-machine, so there's no certificate (or private key) this repo
could ship that would make your browser trust it too - everyone, including on a fresh clone,
runs New-StrongboxCert.ps1 once on their own machine. It uses PowerShell's built-in
New-SelfSignedCertificate rather than a third-party tool like mkcert, and trusts the cert only
in Cert:\CurrentUser\Root (no admin elevation needed, unlike Cert:\LocalMachine\Root) - scoped to
exactly one cert, one hostname, one user.
Secret names are prefixed by ownership: relay., yardi., tools., personal. (change the
regex in ui\Start-StrongboxUi.ps1's POST/reveal/delete routes for different prefixes).
manifest.json is your own local record of every secret's purpose, consumers, and rotation
policy. It never contains secret values, but it does describe your own systems (internal
hostnames, client names, etc.), so it's gitignored rather than committed. See
manifest.example.json for the schema shape with fake placeholder data.
SecretStore has no documented backup path of its own - if the underlying vault store is lost (OS reinstall, corrupted profile), every secret goes with it.
$pass = Read-Host -AsSecureString "Backup passphrase"
Export-StrongboxBackup -Path strongbox-backup.json -Passphrase $pass$pass = Read-Host -AsSecureString "Backup passphrase"
Import-StrongboxBackup -Path strongbox-backup.json -Passphrase $pass # add -Force to overwrite existing entriesThe backup file is AES-256-GCM encrypted with a key derived from your passphrase (PBKDF2, 210,000 iterations, random per-file salt) - deliberately not Windows DPAPI, since a DPAPI-"protected" file would only ever be restorable on the exact same Windows profile, defeating the point of a disaster-recovery backup. There is no recovery if you lose the passphrase.
pwsh -NoProfile -File Uninstall-Strongbox.ps1Stops any running UI server, removes the installed module copy and its PSModulePath entry, the
strongbox CLI function from $PROFILE, and unregisters the Strongbox vault name - every
actual secret value is untouched (SecretStore has one physical store per Windows user shared by
every vault name registered against it, so unregistering a name never touches the data;
re-running the install script re-registers the same name against the same store). Does not touch
the HTTPS cert trust, the strongbox.local hosts entry, this repo directory, or manifest.json -
remove those yourself if wanted.
Install-Module Pester -MinimumVersion 5.0 -Scope CurrentUser -Force -SkipPublisherCheck
Invoke-Pester -Path Tests\Strongbox.Tests.ps1Covers the module's public functions against mocked Get-Secret/Set-Secret/Get-SecretInfo/
Set-SecretInfo calls (never a real vault) and a temp-file manifest. No Pester coverage exists yet
for the web UI backend (ui\Start-StrongboxUi.ps1).
Set-SecretInfo reliably succeeds for a call or two per PowerShell process, then starts failing
with an opaque "cannot write metadata" error for the rest of that session - confirmed
empirically while bulk-fixing metadata across 47 secrets. Each call works fine in its own fresh
pwsh process. If you need to bulk-update metadata across many secrets, loop over separate pwsh -Command invocations rather than one loop inside a single session.