cred-vault
by daterius
README.md
# cred-vault
A local, encrypted credential vault (SSH / SFTP / FTP / FTPS) **plus an MCP server**, so
Claude can operate your servers without ever seeing your passwords.
**The idea:** passwords are stored encrypted. When a connection is needed, the vault
itself pulls the password out and injects it straight into the session. The assistant
runs the command but **never sees or types the password** — it never enters its context.
That's how it sidesteps the rule "an assistant must not type someone else's passwords":
the secret is injected by the vault program, not by the assistant.
Homepage & download: <https://vault.bro.tj>
---
## Security model
- `vault.enc` is encrypted with **AES-256-GCM**.
- The key is derived from your **master password** via `scrypt` (N=16384). The master
password itself is never stored.
- The file holds ciphertext only — no hostnames, logins or passwords in the clear.
- `list` returns metadata only (type, host, user, port) — **never passwords**. That is
the only thing the assistant can learn from the vault.
- A wrong master password fails the GCM auth tag, producing a clear error.
**Honest caveat:** while the vault is unlocked, any process running under your OS
account can decrypt it. The protection is that the tooling *uses* secrets without ever
printing them — it is not isolation from code running as you. Run `cred-vault lock`
when you're done.
---
## Install
```bash
npm i -g https://vault.bro.tj/cred-vault-1.2.4.tgz
```
Requires Node.js 18+. Everything runs locally on your machine.
Without a global install, every command also works through npx:
```bash
npx -y https://vault.bro.tj/cred-vault-1.2.4.tgz init
```
From a source checkout, use `node vault.js <command>` instead of `cred-vault <command>`.
---
## Quick start
```bash
cred-vault init # create an empty vault (set the master password)
cred-vault add myserver # add a credential (password input is hidden)
cred-vault unlock # start a session so tools work without the master password
```
`add` asks for: type (`ssh`/`sftp`/`ftp`/`ftps`), host, port, username, and a password
(or a private key path for ssh/sftp), plus an optional note. **You type the password
yourself** — ideally in your own terminal, not by asking an assistant.
---
## The manager UI
Prefer a window over prompts? Open the built-in manager:
```bash
cred-vault ui
```
It starts a small server bound to **127.0.0.1 only** and opens it in your browser: list
your credentials, add, edit and delete them, test a connection, and reveal a password when
you need to copy it. Nothing is uploaded anywhere — the page talks only to the process
running on your own machine.
You enter the master password on the page (it goes to localhost and nowhere else), so
writing to the vault is still protected exactly as it is in the CLI.
**On purpose, the access token is never printed to the console** — the process opens the
browser with it directly. That way an assistant driving the CLI cannot reach the manager
and cannot read your passwords. If a browser does not open, re-run with `--print-url` and
paste the address yourself. The manager exits after 30 minutes idle.
## Master password and unlocking
Decrypting the vault needs the master password. Handing that to an assistant would let
it decrypt everything — which defeats the purpose. So there is an **unlock** mode backed
by Windows DPAPI:
1. You run `cred-vault unlock` once in your own terminal and enter the master password.
2. The vault key is derived and stored in `vault.session`, encrypted by Windows for your
user account only (DPAPI, `CurrentUser`).
3. Connection commands (`list`, `test`, `exec`, `ls`, `get`, `put`) then work **without
the master password** — the OS unwraps the key. The master password never reaches the
assistant, and the server password is injected by the connector and never printed.
4. `cred-vault lock` ends the session (deletes `vault.session`).
Commands that **modify** the vault (`init`, `add`, `rm`) always require the master
password — only you run those.
The cross-platform alternative to DPAPI is the `VAULT_MASTER` environment variable set in
your own shell (on non-Windows this is the only option):
```powershell
$env:VAULT_MASTER = "your-master-password" # PowerShell
```
```bash
export VAULT_MASTER="your-master-password" # bash
```
---
## Use with Claude (MCP)
Register the MCP server so Claude gets native `vault_*` tools.
**Claude Code:**
```bash
claude mcp add cred-vault -s user -- cred-vault mcp
```
**Claude Desktop** — add to `claude_desktop_config.json`
(Settings → Developer → Edit Config), then restart the app:
```json
{
"mcpServers": {
"cred-vault": { "command": "cred-vault", "args": ["mcp"] }
}
}
```
From a source checkout, point at the file instead:
`claude mcp add cred-vault -s user -- node /path/to/vault.js mcp`
### MCP tools
| Tool | What it does |
|---|---|
| `vault_status` | vault and session state (is it unlocked?) |
| `vault_list` | credentials list — metadata only, no passwords |
| `vault_test` | verify a credential authenticates |
| `vault_exec` | run a command over SSH, returns stdout/stderr/exit code |
| `vault_ls` | list a remote directory (SFTP/FTP) |
| `vault_get` | download a file |
| `vault_put` | upload a file |
| `vault_read` | read a remote text file |
| `vault_write` | overwrite a remote file (previous version backed up) |
| `vault_edit` | replace an exact string inside a remote file |
| `vault_mkdir` | create a remote directory, with parents |
| `vault_mv` | rename or move |
| `vault_history` | list local backups taken before each write |
| `vault_restore` | put a backed-up version back on the server |
| `vault_trash_add` | queue a path for deletion later — opens nothing |
| `vault_trash_list` | show what is queued, with when and why |
| `vault_trash_remove` | take something back out of the queue |
| `vault_trash_clear` | empty the queue (deletes nothing anywhere) |
| `vault_trash_confirm` | turn the queue into ONE confirmation window |
| `vault_rm_request` | **ask** to delete right now — deletes nothing, opens the window |
| `vault_rm_status` | check what you decided |
Setup tools (`init`/`add`/`unlock`) are deliberately **not** exposed over MCP — they need
the master password and stay in the CLI.
**There is no delete tool.** Nothing an assistant can call over MCP removes a remote file;
see [Deleting](#deleting) below.
Then you just talk to Claude by credential name:
> "Upload `./dist` to **myserver** at `/public_html`" · "Show `df -h` on **myserver**"
---
## The file manager
Browse, open and edit files directly on the server — no download/re-upload round trip:
```bash
cred-vault ls myserver /public_html # list a directory
cred-vault read myserver /public_html/index.html
cred-vault write myserver /public_html/index.html ./local.html
cred-vault mkdir myserver /public_html/assets/img
cred-vault mv myserver /old/name.txt /new/name.txt
```
An assistant does the same through `vault_read`, `vault_edit`, `vault_mv` and friends,
still without ever seeing the password.
**Every overwrite is backed up first.** Before `write` or `edit` replaces a remote file,
the current contents are saved to a local history folder. Editing a live site is one
keystroke away from breaking it, so undo is built in:
```bash
cred-vault history myserver /public_html/index.html
cred-vault restore myserver 365a979c461d-2026-09-09T14-27-06-842Z
```
The last 20 versions per file are kept, in `history/` next to your vault. Backups never
leave your machine, and the folder is excluded from the published package.
---
## Deleting
Deletion is the one thing an assistant cannot do. There is **no delete tool over MCP** —
`vault_rm_request` only files a request:
1. The assistant calls `vault_rm_request` with the paths it wants gone.
2. A confirmation window opens on **your** screen. It walks the server live and shows
exactly what would be destroyed: every path, whether it is a file or a directory, how
many files and subdirectories are inside, and the total size.
3. You tick what may go — nothing is pre-selected — and press the button.
4. **That window performs the deletion itself**, and reports the result back.
Unanswered requests expire after 30 minutes. Approving is one-shot: a decided request
cannot be replayed. Paths that were not in the original request are refused, so an
approval cannot be widened. Deleting `/` is refused outright.
### The trash queue
Opening a window per file would be unbearable in a long task, so an assistant can put
paths aside as it works and raise **one** window at the end:
```bash
cred-vault trash add myserver /old/a.txt /old/cache # queue; opens nothing
cred-vault trash list # what is queued, and why
cred-vault trash remove /old/a.txt # changed your mind
cred-vault trash clear # forget it all (deletes nothing)
cred-vault trash confirm # ONE window for the whole batch
```
Nothing in the queue is touched until `confirm` turns it into a real request and you
approve it. Each item keeps the reason it was queued and the time it was added, and the
window shows both next to the path — so a batch is reviewed on its merits, not waved
through because it piled up. Queued items are forgotten after 24 hours.
You can open the window yourself at any time:
```bash
cred-vault confirm # see pending requests
cred-vault rm-status # what was decided recently
```
Selecting is deliberate: nothing is ticked for you, missing paths cannot be ticked at all,
and the delete button must be **held down**, not clicked — so a stray click destroys nothing.
Directories can be expanded into a tree to see exactly what is inside before you decide.
`cred-vault confirm --picker <url>` injects an element-picker script into the window, for
working on the page itself. It is off by default and never enabled automatically: this page
lists what is on your servers, and a third-party script running here would see those paths.
The manager UI has no such flag at all, because it can reveal passwords.
Like the manager, its access token is **never printed** — the process opens the browser
with it directly, so an assistant reading the console cannot approve its own request.
**Honest about the boundary:** this is a workflow guard, not an OS permission wall. A
process running as you can still call `cred-vault rmfile` directly — those two commands
exist for *your* hands and delete immediately, with no confirmation. What the design
guarantees is that deletion never happens as a side effect of an assistant's tool call:
the bytes are only removed inside the approval window, after a click.
---
## CLI commands
| Command | What it does |
|---|---|
| `cred-vault init` | create the vault |
| `cred-vault unlock` | start a DPAPI session (no master password afterwards) |
| `cred-vault lock` | end the session |
| `cred-vault status` | vault and session state |
| `cred-vault mcp` | run as an MCP server (stdio) — used by Claude |
| `cred-vault ui` | open the local manager in your browser (127.0.0.1) |
| `cred-vault license` | show the licence plan and exactly what is sent |
| `cred-vault add <name>` | add a credential |
| `cred-vault list` | list credentials (no passwords) |
| `cred-vault rm <name>` | delete a credential from the vault |
| `cred-vault test <name>` | check that a credential authenticates |
| `cred-vault exec <name> "<cmd>"` | run a command over SSH |
| `cred-vault ls <name> [path]` | list a remote directory (sftp/ftp) |
| `cred-vault get <name> <remote> <local>` | download a file |
| `cred-vault put <name> <local> <remote>` | upload a file (creates missing remote dirs) |
| `cred-vault read <name> <remote>` | print a remote text file |
| `cred-vault write <name> <remote> <local>` | overwrite a remote file (backs up the old one) |
| `cred-vault mkdir <name> <remote>` | create a directory, with parents |
| `cred-vault mv <name> <from> <to>` | rename / move |
| `cred-vault history <name> [remote]` | list local backups |
| `cred-vault restore <name> <backupId>` | restore a backed-up version |
| `cred-vault trash add <name> <remote...>` | queue for deletion later — opens nothing |
| `cred-vault trash list` | show the queue |
| `cred-vault trash remove <remote...>` | unqueue |
| `cred-vault trash clear` | empty the queue (deletes nothing anywhere) |
| `cred-vault trash confirm` | turn the queue into ONE confirmation window |
| `cred-vault rm-request <name> <remote...>` | ask for deletion now — opens the confirmation window |
| `cred-vault rm-status [id]` | show what was decided |
| `cred-vault confirm` | open the confirmation window |
| `cred-vault rmdir <name> <remotePath>` | remove a remote directory (recursive), **no confirmation** |
| `cred-vault rmfile <name> <remotePath>` | remove a remote file, **no confirmation** |
---
## Where the vault lives
By default: **`~/.cred-vault/vault.enc`** (with `vault.session` beside it).
If a `vault.enc` already sits next to the package files, that one is used instead —
convenient when running from a source checkout.
### Environment variables
- `VAULT_MASTER` — master password (otherwise prompted, or taken from a DPAPI session).
- `VAULT_FILE` — path to the vault file (overrides the default).
- `VAULT_SESSION` — path to the session file (defaults to beside the vault file).
- `VAULT_HISTORY_DIR` — where write backups are kept (defaults to `history/` beside the vault).
- `VAULT_PENDING_FILE` — where delete requests are queued.
- `VAULT_TRASH_FILE` — where the trash queue is kept.
- `VAULT_CONFIRM_NO_OPEN=1` — keep the confirmation window headless (no browser launch).
---
## Files
```
vault.js CLI entry point (also `vault.js mcp`)
mcp-server.js MCP server (stdio) exposing the vault_* tools
lib/crypto.js AES-256-GCM + scrypt
lib/store.js reads/writes vault.enc
lib/session.js DPAPI-backed unlock session
lib/prompt.js hidden password input
lib/fm.js file manager: read/write/edit/move + local version history
lib/pending.js delete-request queue (create only — it cannot delete)
lib/trash.js the trash queue: collect paths now, confirm them all at once
connectors/ssh.js SSH + SFTP (ssh2)
connectors/ftp.js FTP + FTPS (basic-ftp)
ui/server.js the local manager (127.0.0.1)
ui/confirm.js the delete confirmation window — the only code that deletes
```
## Licence
cred-vault is free to use. On first run it fetches a signed licence token from
<https://mcp.bro.tj> (the shared licence service for these tools) and caches it; `cred-vault license` shows the current plan.
What that does and does not mean:
- **It works offline.** The token is signed with Ed25519 and verified locally against an
embedded public key, so no network call is needed to use the product. If the licence
server is unreachable and there is no cached token, everything still runs on the free
plan. A credential tool must never stop working because a website is down.
- **Nothing sensitive is sent.** Only the product name, its version, the platform string
(`win32`, `darwin`, `linux`) and a random anonymous install id. No credentials,
hostnames, paths or file contents ever leave your machine — that is the whole point of
this tool and licensing does not get an exception.
- The token cannot be forged or edited: an invalid signature is simply ignored and the
product falls back to the free plan.
Set `VAULT_LICENSE_URL` to point at a different licence server, or
`VAULT_LICENSE_FILE` to move the cached token.
## Backup
Back it up by copying `vault.enc` somewhere safe. Without the master password the file is
useless to anyone. Lose the master password and the credentials are unrecoverable —
that is by design.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues