vaultshell
vaultshell is an MCP server for storing secrets locally and injecting them into shell commands, sessions, or proxies by rule, with mandatory redaction so secrets never reach the model context.
Store, list, delete, and probe secrets across backends (encrypted file, keychain, env, file, 1Password, Vault, Infisical, Doppler); values are never returned.
Run one-shot shell commands (
shell_exec) with per-rule secret injection, optional cwd/profile/extraEnv allowlist, redacted output, dry-run, timeout, truncation, and dangerous-command blocking.Open, send to, list, close, and revoke persistent shell sessions; secrets are injected at creation, TTL-recycled, and revoke rebuilds without secrets.
Start, stop, and list credential-injecting reverse proxies on 127.0.0.1; secrets are injected in memory into headers, auto-expire, and are never returned.
List and statically validate injection rules, including warnings for unknown refs, unreachable rules, inject-everywhere, and merge-strategy risks.
Query audit logs containing only secret names and redacted commands.
Optionally run a loopback-only web UI for managing secrets, rules, config, and auditing (via
vaultshell web).
Resolves secrets from 1Password via the authenticated op CLI using op://vault/item/field references; read-only access for injecting secrets into shell child processes at exec time.
Provides a credential-injecting reverse proxy for Stripe API calls, letting commands call http://127.0.0.1:<port>/v1/charges instead of https://api.stripe.com/v1/charges; injects the Authorization header in memory only and auto-stops after a TTL.
Resolves secrets from HashiCorp Vault via the authenticated vault CLI using vault://path#field references; read-only access for injecting secrets into shell child processes at exec time.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@vaultshell execute the deploy script using the production secret profile"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
vaultshell
English | 简体中文
Available on the official MCP Registry: io.github.SoWhatI/vaultshell
An MCP server that stores secrets in local secure storage, injects them into shell child processes at exec time based on rules, and guarantees secret plaintext never reaches the model context — not in tool responses, not in logs, not in audit records.
The closed loop: reference-based storage + conditional injection + mandatory output redaction.
Status: M3 — one-shot commands + persistent sessions + credential proxy + external CLI backends. See CHANGELOG and docs/design-spec.md.
The iron rule
No MCP tool returns secret plaintext — ever. Responses contain only names, masks (
[REDACTED:NAME]), and redacted output.Resolvers are only invoked inside the launcher; values go straight into the child process
envand never pass through the tool layer.shell_exechas no free-formenvparameter. Secrets enter by rule reference only.Redactor failures are fail-closed: output is discarded, never returned raw.
Related MCP server: host-shell-mcp
Quick start
Requires Node.js 20+.
Install from GitHub (no npm registry account needed)
# Run on demand directly from the repo (npm installs devDependencies and
# builds dist/ via the `prepare` script):
npx -y github:SoWhatI/vaultshell
# Or install globally:
npm i -g github:SoWhatI/vaultshellThis is the same code as the npm registry package, installed from git —
npm clones the repo, runs prepare (→ npm run build) and links the
vaultshell bin. Pin a tag for reproducibility:
npx -y github:SoWhatI/vaultshell#v0.1.1.
Docker (ghcr.io)
docker run -i --rm \
-e MASTER_KEY=<64 hex chars> \
-v ~/.vaultshell:/home/node/.vaultshell \
ghcr.io/sowhati/vaultshell:latestMCP client config using Docker:
{
"mcpServers": {
"vaultshell": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MASTER_KEY=<64 hex chars>",
"-v", "~/.vaultshell:/home/node/.vaultshell",
"ghcr.io/sowhati/vaultshell:latest"
]
}
}
}The image runs as the non-root node user (HOME=/home/node), so the data
volume mounts at /home/node/.vaultshell. node-pty is excluded from the
image — sessions automatically fall back to pipe mode (pty: false) in
containers; injection and redaction are unchanged. The web subcommand
passes through (docker run … ghcr.io/sowhati/vaultshell web — loopback
inside the container, of limited use).
No Smithery distribution: Smithery has gone hosted-only (it only accepts servers deployed to its own infrastructure). vaultshell's secrets must stay on the user's machine by design — third-party hosting is incompatible with its threat model, so we deliberately do not publish there.
From source
# Run via npx (after publish) or from source:
npm install && npm run build
# 1. Create a master key for the encrypted-file backend
export MASTER_KEY=$(openssl rand -hex 32)
# 2. Create ~/.vaultshell/config.yaml and ~/.vaultshell/rules.yaml
# (full annotated examples: docs/user-guide.md)
# 3. Store a secret (never echoed back) and run with injection
# via your MCP client's tools: secret_set, then shell_execClaude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"vaultshell": {
"command": "npx",
"args": ["-y", "vaultshell"],
"env": { "MASTER_KEY": "<64 hex chars>" }
}
}
}Full setup, configuration reference, rule-writing guide, client integration, and FAQ: docs/user-guide.md.
MCP tools
Tool | Purpose | Returns |
| Run a one-shot command with per-rule injection. Params: |
|
| Open a persistent session; secrets injected once at creation |
|
| Send a command to a session |
|
| Kill a session; secrets die with the process |
|
| List live sessions | metadata only |
| Kill and rebuild the session without secrets |
|
| Start a credential-injecting reverse proxy on 127.0.0.1 |
|
| Stop a proxy |
|
| List running proxies | metadata only (incl. secret name, never value) |
| List names + metadata (backend ref, resolvable) | never values |
| Write to backend; persisted, never echoed |
|
| Delete and unregister |
|
| Check a ref resolves |
|
| Rules + static warnings (e.g. inject-everywhere) | — |
| Static findings: unknown secrets, unreachable rules, inject-everywhere, union risk, requireConfirm capability |
|
| Read audit entries | names + redacted commands only |
Execution limits (M3)
dryRun: trueonshell_execreports the would-beruleId,injectednames, final env key list (never values) and the deny-list verdict — without executing anything.timeoutSeconds(defaultdefaults.execTimeoutSeconds= 120, capped at 3600) kills the command and returnstimedOut: true.defaults.maxOutputBytes(default 1 MiB) truncates each output stream and setstruncated: true. Truncation happens after the Redactor — it can never bypass redaction.defaults.maxConcurrentExecs(default 8) rejects excess concurrentshell_execcalls with a clear error (no queue).All of the above are recorded in the audit log (
dryRun/timedOut/truncatedflags).
Web config UI
vaultshell web # loopback-only HTTP UI on a random port
vaultshell web --port 5317Prints a one-time-token URL like http://127.0.0.1:5317/?token=… — open it
to manage secrets (write-only), edit rules with live static-validation
warnings and a matcher dry-run, edit config, and browse the redacted audit
log. The token dies with the process; every API call needs it, mutations
require same-origin application/json requests, and no endpoint can ever
return a secret value. Do not port-forward it. Design & threat model:
docs/web-config-ui-design.md.
Credential proxy (M3)
For high-sensitivity API tokens, prefer not landing the secret in env at
all. Declare a proxy in config.yaml:
proxies:
- id: stripe
upstreamHost: https://api.stripe.com
secretRef: STRIPE_KEY # registry name or full ref
headerTemplate: "Authorization: Bearer ${value}"Then shell_proxy_start { "id": "stripe" } → { port, expiresAt }, and
commands call http://127.0.0.1:<port>/v1/charges instead of
https://api.stripe.com/v1/charges. The proxy injects the header in
memory only and auto-stops after its TTL (default 300s). Audit records the
host and secret name, never the header value.
Boundaries: http:///https:// upstreams only (proxy→upstream TLS is
properly validated; no CONNECT tunneling, no TLS termination); the
client→proxy leg is plaintext on 127.0.0.1, which inherits the same-UID
local-process boundary from the threat model.
Persistent sessions (M2)
shell_session_open spawns a long-lived shell with the matched rule's
secrets injected at creation. The session is recycled after an idle TTL
(defaults.sessionTtlSeconds, default 900s, overridable per rule via
ttlSeconds or per call) — when the process dies, the secrets die with it.
shell_session_revoke kills a possibly-compromised session immediately and
rebuilds one without any secrets.
Sessions use a real PTY via node-pty
(an optional dependency — native build, needs Xcode CLT on macOS /
build-essential + python3 on Linux). If node-pty cannot be loaded or spawned,
vaultshell automatically falls back to a plain child_process shell and the
shell_session_open response says pty: false; injection and redaction
behave identically, only interactivity is degraded. Session output merges
stdout/stderr (PTY semantics) and passes through the same Redactor.
Backend support matrix
Backend | Ref scheme | Status | Platforms | Notes |
Encrypted file |
| ✅ Implemented | all | AES-256-GCM; master key from |
Local keychain |
| ⚠️ macOS only | macOS | spawns |
Process env |
| ✅ Implemented | all | read-only; good for CI |
File |
| ✅ Implemented | all | first line; refuses permissions > 0600 |
Inline |
| ⚠️ Restricted | all | plaintext in config; disabled by default, startup warning when enabled |
1Password |
| ✅ via CLI | all | needs authenticated |
Vault |
| ✅ via CLI | all | needs authenticated |
Infisical |
| ✅ via CLI | all | needs authenticated |
Doppler |
| ✅ via CLI | all | needs authenticated |
Details, per-backend setup, and how to register a plugin resolver: docs/backends.md.
Threat model
Threat | Mitigation |
Model-context leakage | Structural: values never cross the tool layer; only names and masks |
Command output echoing secrets | Mandatory redaction (exact + URL/Base64 variants + generic patterns), fail-closed |
Agent dumping env ( | Hard-blocked deny-list (configurable via |
Same-UID local process reading | OS-level limit, cannot be fully fixed; per-command injection shrinks the window. Explicit boundary. |
Long secret residency | Per-command injection by default; sessions have idle-TTL recycling + instant |
Misconfigured rule causing inject-everywhere |
|
Config files leaking | Config stores refs only, never values; |
Full discussion: docs/design-spec.md §7. Report vulnerabilities privately per SECURITY.md.
Data layout
~/.vaultshell/
config.yaml # main config
rules.yaml # injection rules + secret refs (refs only, never values)
secrets.enc # encrypted-file backend (0600)
audit/ # JSONL audit, redactedVAULTSHELL_HOME overrides the data directory (used by tests).
Development
npm run build # tsc
npm test # vitest — redactor property tests, backend round-trips,
# rule matching, shell_exec end-to-endSee CONTRIBUTING.md — including the security red lines every PR must respect.
License
MIT © 2026 vaultshell contributors
Available Tools
16 toolsaudit_queryB
Query recent audit entries (newest last). Entries contain names and redacted commands only, never values.
Inputs: limit.
rule_listA
List injection rules with static warnings (e.g. rules matching every directory → potential unintended full injection).
Inputs: no named parameters.
rule_validateA
Statically validate rules.yaml and return findings: unknown secret refs, unreachable rules (shadowed by earlier ones), inject-everywhere rules (high severity), requireConfirm capability notes, union mergeStrategy risk notes.
Inputs: no named parameters.
secret_deleteB
Delete a secret from the backend and unregister it from rules.yaml.
Inputs: name (required).
secret_listA
List registered secret names with metadata (backend ref, resolvable). Never returns values.
Inputs: no named parameters.
secret_probeB
Check whether a secret ref can be resolved. Returns only {ok, masked}; masked never contains value fragments unless defaults.maskTail > 0.
Inputs: name (required).
secret_setB
Store a secret value into the configured backend. The value is persisted, never echoed back, and the name is registered in rules.yaml (ref only).
Inputs: name (required), value (required), ref.
shell_execA
Execute a one-shot shell command with secrets injected per matching rule. Output is redacted: injected secret values never appear in the response. Dangerous commands (env, printenv, export -p, /proc/*/environ, ...) are hard-blocked. There is no free-form env parameter; secrets are only injected by reference via rules.
Inputs: command (required), cwd, profile, extraEnv.
shell_proxy_listA
List running credential proxies (metadata only: id, port, upstreamHost, secret NAME, expiry, request count). Never includes values.
Inputs: no named parameters.
shell_proxy_startA
Start a credential-injecting reverse proxy on 127.0.0.1 (random port) for a proxies entry in config.yaml. Point commands at http://127.0.0.1: instead of the real API host; the proxy injects the secret into the configured header in memory only. Auto-stops after its TTL (default defaults.proxyTtlSeconds=300s). The secret value is never returned anywhere.
Inputs: id (required), ttlSeconds.
shell_proxy_stopC
Stop a running credential proxy.
Inputs: proxyId (required).
shell_session_closeB
Close a session: kill the process; injected secrets die with it.
Inputs: sessionId (required).
shell_session_listA
List live sessions (metadata only: id, cwd, injected names, pty, ttl, timestamps).
Inputs: no named parameters.
shell_session_openA
Open a persistent shell session (PTY when node-pty is available, plain child_process otherwise — the response's pty field says which). Secrets are injected once at creation per matching rule; idle sessions are killed after ttlSeconds (rule ttlSeconds overrides defaults.sessionTtlSeconds).
Inputs: cwd, profile, ttlSeconds.
shell_session_revokeA
Immediately kill a session and rebuild a fresh one WITHOUT any injected secrets. Use when a session may have been compromised. Returns the new sessionId.
Inputs: sessionId (required).
shell_session_sendA
Send a command to a persistent session. Output (merged stdout/stderr) is redacted before returning. Dangerous commands are blocked per security.dangerousCommands. Returns {output, exitCode}.
Inputs: command (required), sessionId (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 16 tools
Most tools target distinct resources and operations, covering secret lifecycle, shell execution, persistent sessions, proxy management, rules, and audit. The only slight overlap is between secret_probe and secret_list, since both can report secret resolvability, but their descriptions clarify the intended use.
All tool names use snake_case with clear domain prefixes such as secret_, shell_, shell_session_, shell_proxy_, rule_, and audit_. The verb_noun structure is consistent throughout, with no mixed conventions or vague naming.
The server has 16 tools across five coherent subdomains, slightly above the typical 3-15 range but still well-scoped. Each tool appears to earn its place, with no obvious redundant operations.
Core secret, shell, session, proxy, and audit workflows are well covered, including session revocation and proxy lifecycle. Rule management is read-only via list and validate, with no create/update/delete tools, which is a minor gap likely handled through the underlying rules.yaml file.
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Secrets for developers and agents—secure context and workflows without exposing secret values.
Work on your own computers from Claude or ChatGPT: run commands, edit and search files.
I run shell commands on your private cloud environment (bash, sh, zsh)
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to securely use secrets by running commands with environment-injected credentials and sanitizing output to prevent leakage.1,011 npm1MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to execute shell commands on a host macOS computer with configurable file access permissions, granting access to credentials and tools while enforcing sandbox restrictions.MIT
- AlicenseNot gradedqualityNot gradedmaintenanceEnables autonomous agents to run shell and subprocess commands through a guardrail that blocks destructive system mutations, path traversals, and reverse shells. It also adds prompt-injection detection, spend circuit breakers, loop detection, and privacy sanitization so agent execution stays within safe, budgeted bounds.MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude Code to use locally encrypted, multi-tenant credentials by running commands with injected secrets, writing .env files, materializing files, and making HTTP requests, while scrubbing values from all output so they never enter the chat. Also supports sealed retrieval and saving through native dialogs or an auto-clearing clipboard, role-based credential selection, versioning, rollback, archiving, and a full audit log.9 npmMIT