vaultguard
by ashishrao-4
README.md
<p align="center">
<img src="https://raw.githubusercontent.com/ashishrao-4/vaultguard/main/assets/logo.png" alt="vaultguard" width="128"/><br/>
<strong><code>๐ vaultguard</code></strong><br/>
<em>Your Obsidian vault, guarded for your AI agents.</em><br/><br/>
<code>npx @ashishrao-4/vaultguard init</code> ยท zero dependencies ยท pure Node ยท cross-platform
</p>
---
## The problem
Your AI agents are powerful. They ship code, run commands, and one day they will ask: *"give me the database URL."*
You hand it over. Now that value lives in every transcript, log, checkpoint, and backup of your conversations. Rotate it a month later โ a year later โ and the old one is still out there.
**The vault itself** โ Obsidian โ is encrypted only if you make it so, and your agent reading your notes means your agent reading your secrets.
## What vaultguard does
Secrets live in **your** Obsidian vault as AES-256-GCM ciphertext. Your agents get them **by name** over MCP โ they run commands with the values injected into the environment and **never see them**, while every access is **audited**.
<p align="center">
<img src="assets/architecture.png" alt="vaultguard architecture" width="640"/><br/>
<em>The simple version: your agent asks by name, vaultguard decrypts on demand, the value stays out of the conversation.</em>
</p>
- **You own the data.** No cloud, no SaaS, no server. The encrypted blocks are plain markdown.
- **Readable in Obsidian.** Encrypted blocks look like notes; reveal them with one click.
- **Tell your agent to run things** using secrets โ values never surface in transcripts.
- **Zero npm dependencies.** Pure Node โฅ 18. Windows / macOS / Linux.
---
## Quickstart
### 1 ยท Install
```bash
npm install -g @ashishrao-4/vaultguard
```
### 2 ยท Point it at your vault
```bash
vaultguard init --vault "C:\Users\you\Documents\Obsidian Vault"
```
It asks for a **passphrase** โ the key that encrypts and decrypts *every* block in this vault.
Then it:
- creates `Secrets.md` in your vault,
- installs the **Inline Secret Block** plugin into the vault automatically,
- writes `~/.vaultguard/config.json` โ **without** the passphrase (you provide it via `VAULTGUARD_PASSPHRASE`).
> Restart Obsidian and enable the plugin: **Settings โ Community plugins โ Inline Secret Block โ Enable**.
>
> The passphrase is never written to disk by default. Prefer env vars:
> `VAULTGUARD_PASSPHRASE` (or `DOORMAN_PASSPHRASE`). If you want it conveniently stored anyway โ
> at the cost of weaker security โ run `vaultguard init --store-passphrase` instead (see Threat model).
### 3 ยท Add your first secret โ the Obsidian way
In **Obsidian**, open `Secrets.md` and add a plaintext block:
````markdown
```secret DATABASE-URL
Mydatabaseurl@postgres
```
````
Click **Show** โ the plugin instantly replaces it with an encrypted `secret-lock` block. Your raw value is gone; what remains:
````markdown
```secret-lock DATABASE-URL
Nx60U4Ph/+1CO+58Zr00HXhEW9GZ6voHlpS+bEXPpP69avbJaSfafZCC2dpPn6UgdMN+3PJUd+UPm39YAXhFTbHvLUpHDndzbODsL8fOm7IMWC16zjSQCW7CbRWklmUxOGl0lX2qpQ==
```
````
That's it. Same value, later, forever: click **Show** again.
> **No Obsidian? Use the CLI instead:**
> ```bash
> vaultguard add DB_URL # hidden prompt
> vaultguard set DB_URL # rotate in place
> ```
### 4 ยท Connect your agent
```bash
vaultguard mcp
```
prints ready-made config for your harness:
**opencode** โ in `opencode.json` (or globally via the app):
```json
{
"mcp": {
"vaultguard": {
"type": "local",
"command": ["node", "C:/path/to/vaultguard/src/server.mjs"],
"environment": { "VAULTGUARD_PASSPHRASE": "your-passphrase" }
}
}
}
```
**Claude Code:**
```bash
claude mcp add vaultguard -e VAULTGUARD_PASSPHRASE=your-passphrase -- node C:/path/to/vaultguard/src/server.mjs
```
**Cursor:** add the same server to your project's `.cursor/mcp.json` (or the *MCP* settings tab):
```json
{
"mcpServers": {
"vaultguard": {
"command": "node",
"args": ["C:/path/to/vaultguard/src/server.mjs"],
"env": { "VAULTGUARD_PASSPHRASE": "your-passphrase" }
}
}
}
```
### 5 ยท Use it
```text
you : "run a quick sanity check against DB_URL"
agent: run_with_secret(command: "psql $DB_URL -c 'SELECT 1'", secrets: ["DB_URL"])
you : โ exit 0 ยท audit entry written ยท no secret leaked
```
- `run_with_secret` โ secrets injected into the command's environment only.
- Output is scrubbed โ any accidental echo of a secret is replaced with `[REDACTED:NAME]`.
- `get_secret` โ **disabled by default** so values never reach the agent; opt in with `allowGetSecret: true` in the config if a tool insists on the raw value.
---
## Security model
| Layer | What stops it |
|---|---|
| **At rest** | AES-256-GCM, PBKDF2-SHA-256 (250,000 iterations, 16-byte salt, fresh 12-byte IV per value). Byte-compatible with the [Inline Secret Block](https://github.com/vnrtmnv/obsidian-inline-secret-block) plugin. |
| **Approval gate** | `run_with_secret` is **denied by default** unless you set `requireApproval: false` (or `VAULTGUARD_REQUIRE_APPROVAL=0`). |
| **Secret access gate** | `get_secret` is **disabled by default** โ values never reach the agent; enable only via `allowGetSecret: true`. The intended path is `run_with_secret` (env injection, values never seen). |
| **Host allowlist** | Only named clients (from MCP `clientInfo`) may call tools. Empty list = allow all. |
| **Command allowlist** | Only command prefixes you list may run (e.g. `["psql", "node", "git"]`). Empty = allow all. |
| **Audit log** | Every call โ who (host), what, which secrets, outcome โ appended to `~/.vaultguard/audit.jsonl`. View with `vaultguard audit`. |
| **Output scrubbing** | Secret values and their first 8 chars are redacted from command output. |
### Configuration
Edit `~/.vaultguard/config.json`:
```jsonc
{
"vaultPath": "C:/Users/you/Documents/Obsidian Vault",
// passphrase is NOT stored here by default โ provide VAULTGUARD_PASSPHRASE instead
"allowlist": { "hosts": [], "commands": ["psql", "node"] },
"requireApproval": false, // true (default) = gate run_with_secret
"audit": true,
"allowGetSecret": false // false (default) = values never reach the agent
}
```
The **only** way the passphrase lands in this file is `vaultguard init --store-passphrase`,
which sets `"storePassphraseOnDisk": true` and includes `"passphrase"`. Everything else reads
the passphrase from the `VAULTGUARD_PASSPHRASE` env var or the CLI prompt.
| Env var | Overrides |
|---|---|
| `VAULTGUARD_VAULT_PATH` / `VAULT_PATH` | vault path |
| `VAULTGUARD_PASSPHRASE` / `DOORMAN_PASSPHRASE` | passphrase |
| `VAULTGUARD_HOME` | config dir (default `~/.vaultguard`) |
| `VAULTGUARD_REQUIRE_APPROVAL=0` | auto-approve |
| `VAULTGUARD_AUDIT=0` | disable audit |
| `VAULTGUARD_ALLOW_GET_SECRET=1` | enable `get_secret` (default: off) |
> **Passphrase hygiene:** vaultguard never writes the passphrase to disk unless you opt in
> (`init --store-passphrase`). Supply `VAULTGUARD_PASSPHRASE` in each harness config (see step 4)
> and protect `~/.vaultguard` like an SSH key. Changed passphrase? `vaultguard rekey` re-encrypts
> every block, then update the env var wherever you set it.
---
## CLI reference
| Command | What it does |
|---|---|
| `vaultguard init` | Configure vault + passphrase, create `Secrets.md`, install plugin |
| `vaultguard add <NAME>` | Encrypt + store a new secret (interactive or `--value`) |
| `vaultguard set <NAME>` | Rotate a secret in place |
| `vaultguard rekey` | Re-encrypt every block with a new passphrase (interactive, or `--old-passphrase`/`--new-passphrase`) |
| `vaultguard list` | List secret names (no values) |
| `vaultguard audit [--lines n]` | Tail the audit log |
| `vaultguard mcp` | Print harness-specific MCP config |
| `vaultguard info` | Show config + security posture |
| `vaultguard test` | Crypto self-test |
---
## Threat model โ and when NOT to use it
vaultguard is a thin convenience layer, **not a secrets manager**. Its job is to keep secret *values*
out of your AI-agent transcripts, logs, and checkpoints.
**What it does NOT protect against:**
- **A compromised machine or harness.** The passphrase (or an opted-in stored config) lives on your
disk. Any process running as you โ a backup tool, ransomware, a compromised plugin, your IDE โ can
read your files and decrypt the vault.
- **A hostile agent.** The entire idea is that the agent runs commands **with** secrets in the
environment. Treat that as "the agent is you." Start with command allowlists and review
`vaultguard audit`; don't grant access you wouldn't grant yourself.
- **Weak passphrases.** AES-256-GCM + PBKDF2 is only as strong as the passphrase. Use a long random
one (your password manager can generate and store it).
- **Exfiltration through legitimate channels.** A determined agent can copy ciphertext or raw values
anywhere that's reachable. vaultguard is a barrier, not a boundary.
- **Plugin supply chain.** `vaultguard init` downloads the Inline Secret Block plugin from its GitHub
releases. A malicious plugin that knows your passphrase can decrypt everything โ pin/verify it if
you care.
**Use it when:** you want *"agents run things with secrets without me pasting values into the chat"*
and the residual risks above are acceptable to you.
**Don't use it when:** you need real secrets-management guarantees โ rotation policy, hardware-backed
keys, no procedure that makes plaintext reachable to a native plugin โ when your threat model
includes a hostile agent on a shared or CI machine, or when the vault itself needs encryption at rest
(Obsidian's own vault encryption, or an encrypted volume, is the answer there).
---
## FAQ
**Is my vault git-safe?** The encrypted blocks are plain markdown โ safe to commit, sync, or put anywhere Obsidian works. Since the passphrase no longer lives in `~/.vaultguard/config.json` by default, committing that file leaks your vault path and settings but not your key.
**What if I forget the passphrase?** The blocks are AES-256-GCM. It cannot be recovered โ that's the point.
**Which Obsidian plugin?** [Inline Secret Block](https://github.com/vnrtmnv/obsidian-inline-secret-block) โ `vaultguard init` installs it for you.
**Do I need a server?** No. It's a local stdio MCP server (`node src/server.mjs`). Nothing listens on a port.
---
## License
MIT ยฉ vaultguard contributors.
The bundle installs the [Inline Secret Block](https://github.com/vnrtmnv/obsidian-inline-secret-block) plugin (also MIT), downloaded at `init` time from the plugin's official releases โ it is **not** vendored into this package. This project uses Node.js built-ins only (`crypto`), so there are no dependency licenses to track.
---
*Guard your vault. Let your agents work.*This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues