VaultGuard Agent Bridge
VaultGuard's local at-rest encryption does its job when an external CLI or LLM is pointed at the vault folder: protected notes appear as VG1\0 ciphertext. Do not work around that by giving the model the real vault directory or any key material.
Use the Agent Bridge instead. It exposes a narrow, user-approved tool surface that runs through the same VaultGuard gates as normal Obsidian reads and writes.
Security Model
- Do not mount the real Obsidian vault as the agent workspace. Use an empty temporary workspace for Claude Code, Codex, or other agents.
- Mint a short-lived Agent Bridge lease from Obsidian with the command VaultGuard: Create Agent Bridge Lease.
- Give the agent only the copied connection JSON or client-specific snippets:
endpoint, bearertoken,leaseId, expiry, and tool names. For Codex, keep the token inVAULTGUARD_AGENT_TOKEN, not inconfig.toml. - Never give the agent the LAK, at-rest recovery code, cloud key lease, refresh token, Cognito tokens, or
.obsidianstate. - Hidden root folders such as
.obsidian,.trash,.git,.claudian, and every configured VaultGuard exclusion stay blocked even if a lease scope tries to include them. - Remote models necessarily receive any plaintext returned by
vaultguard_readorvaultguard_search. Use narrow scopes, short TTLs, redaction, and provider retention controls.
Vault Orientation
All agent paths share a metadata-only vault orientation layer. External MCP clients, Codex snippets, Claude skills, in-plugin Claude/GPT chat, and the ChatGPT connector developer preview can call get_vault_orientation before reading or writing.
The snapshot identifies the active vault, bounded known-vault metadata when requested, local/cloud/org classification, Local Project Memory Mode, protection state, connector readiness, write safety, and bounded Git status. It never grants access by itself. Reads, writes, graph queries, permission checks, write confirmations, and audits still run through VaultGuard tools.
Orientation must not expose absolute local paths, raw Git remote URLs, API keys, bridge tokens, connector access tokens, local access keys, recovery keys, session tokens, cloud key leases, or decrypted vault dumps. Git detection is limited to .git at the active vault root and redacts remotes to host kinds such as github or gitlab.
Agents should call orientation first when a task may involve multiple vaults, protected/encrypted content, Git state, connector readiness, or write safety. Treat the active vault as the default target unless the user explicitly names another vault, and confirm the target vault before any future cross-vault write flow.
In-plugin OpenAI/GPT chat is not a public bridge
The OpenAI/GPT provider in VaultGuard AI Chat does not expose this local bridge server to ChatGPT, does not create a public connector, and does not tunnel /mcp. GPT receives OpenAI function-tool schemas derived from the same VaultGuard tool definitions, but every call is executed in-process by VaultToolRuntime against AgentBridge.getToolSurface().
That distinction is intentional: VaultGuard remains the authority for vault-relative paths, permission checks, local at-rest decrypt/encrypt, write confirmations, and audit metadata. Use the HTTP/RPC/MCP bridge only for external clients such as Codex, Claude Code, Claudian, Cursor, or a local LLM you intentionally connect.
In-plugin ChatGPT subscription chat uses a private local MCP session
The ChatGPT subscription (Codex CLI) AI Chat provider is not the public
ChatGPT connector. VaultGuard launches the official codex app-server locally,
verifies that codex login status explicitly reports a ChatGPT login, creates an
ephemeral in-memory Codex thread in an empty temporary directory, and configures
VaultGuard as its only MCP server. Native shell, filesystem, web, browser, image,
plugin, app, skill, memory, goal, and multi-agent capabilities are disabled; if
Codex nevertheless emits a native tool item or a non-VaultGuard MCP call, the
turn is interrupted and the session is discarded.
The MCP bearer exists only in the Codex child environment, never in argv or a
Codex config file. The in-app lease is forced non-persistent and session-bound,
is rejected by /rpc, and is revoked on cancel, provider change, chat close, or
plugin shutdown. This narrow path remains available in Local Project Memory Mode
while generic Agent Bridge leases and /chatgpt/mcp remain disabled.
ChatGPT connector developer preview
VaultGuard also has a separate developer-preview ChatGPT connector/app surface at
/chatgpt/mcp. It is not the local Agent Bridge /mcp endpoint and it does not
accept agt_... bridge lease tokens.
The connector preview:
- Uses short-lived
cgt_...connector tokens, protected-resource metadata, MCP auth challenges, and per-tool OAuth-style scopes. - Exposes only read-only
get_vault_orientation,list,search,read, andgraphtools. - Applies stricter output bounds and secret/path redaction than local bridge leases.
- Is disabled in Local Project Memory Mode.
- Is intended for Secure MCP Tunnel or disposable local HTTPS tunnel testing, not production/public connector publishing.
The standalone ChatGPT connector guide is not shipped in this release. Use
src/plugin/agent-bridge-codex.ts, src/plugin/vault-orientation.ts,
tests/agent-bridge-codex.test.ts, and tests/vault-orientation.test.ts as
the current source-truth before touching this path.
Lease Shape
Leases are memory-only and expire automatically.
{
"leaseId": "agl_...",
"agentName": "Claude Code",
"scopes": ["/project-x/**"],
"allowRead": true,
"writeMode": "confirm",
"expiresAt": "2026-05-08T12:30:00.000Z"
}
writeMode can be:
deny: read-only.confirm: everycreate,apply_patch,delete, orrenameasks the user in Obsidian.allow: writes proceed without per-file prompts; use only for trusted local models and tight scopes.
Persistent leases (until logout)
The default lease lifecycle is wall-clock: TTL up to 120 minutes, gone on plugin reload. For long-running agent setups (Codex, Claudian, Claude Code, a local LLM you're going to use every day) you can opt into a persistent lease that:
- Has no fixed TTL — it lives until you log out of VaultGuard, the refresh token is invalidated, or you revoke it.
- Survives Obsidian restarts. The lease record is stored at
.obsidian/plugins/vaultguard/agent-leases.envelope, encrypted with the local at-rest key (LAK) — same threat model as the rest of the at-rest layer. - Is bound to a specific userId + vaultId. If you log in as a different user, or move the vault folder to a binding that points at a different server vault, the persisted lease is dropped on load (and an audit event is emitted).
- Requires re-auth at creation: minting a persistent lease prompts for your VaultGuard password, the same gate that protects "View recovery code" and "Decrypt vault at rest." An unattended, unlocked Obsidian shouldn't be enough to mint a long-lived agent capability. The confirmation message names the agent and the actual scope you're approving.
- Rejects
writeMode: "allow".confirmis the maximum — every write surfaces a per-file prompt, even from a forgotten persistent lease. Read-only (deny) is also fine. We don't restrict scope width:/**is already vault-scoped (the bridge only knows about one vault binding, and hidden paths like.obsidianare always blocked), so forcing a narrower glob would be UX friction without a real threat-model justification — an ephemeral/**lease today reads the same files. The longer lifetime does widen the leak window for a stolen token; mitigate via "Rotate token" / "Revoke" in Settings, plus auto-revoke on logout.
The bridge HTTP server tries to bind to 127.0.0.1:47711 first so the URL pasted into Claudian / .mcp.json is stable across reloads. If the port is taken, it falls back to a random port and you'll need to re-paste the URL.
Manage leases from Settings → VaultGuard → Agent bridge connections: create new bridge leases, revoke all leases, or manage each current lease individually. Each entry shows its scope, lifetime, access mode, limits, and has Rotate token (mints a fresh bearer; old token instantly stops working) and Revoke lease actions. The settings panel also shows the live bridge URL and reveals fresh MCP/Codex snippets after token rotation.
Claude Code skill (optional)
Even with the MCP server registered, Claude Code's default behavior on a vault directory is to reach for Read / Glob / Grep — which return ciphertext for VaultGuard files. To steer the model toward the bridge tools, you can install a Claude Code skill that explains the situation and points at the right tools.
The skill is a single SKILL.md written to ~/.claude/skills/vaultguard/SKILL.md. It's install-once, machine-wide, no per-user state — bearer tokens and lease ids belong in your MCP config, not in the skill file. Re-installing the skill never invalidates an existing lease.
What it does
When Claude Code loads, it scans ~/.claude/skills/ and <project>/.claude/skills/ for skill files. The vaultguard skill description triggers when the user asks about Obsidian vault notes or when Read returns content beginning with the bytes VG1\0. The body tells the model:
- The on-disk files are AES-256-GCM ciphertext.
- The external
mcp__vaultguard__*tools, includingget_vault_orientation, and what each is for. - Path semantics (vault-relative, no leading
/). - Workflow rules:
list/searchbeforeread; never mix MCP reads with built-inEditwrites (would write plaintext where ciphertext is expected and break at-rest encryption). - Recovery hints for common errors (lease expired, scope mismatch, refused-access).
Installing
Settings → VaultGuard → Agent bridge connections → Claude Code skill → Install skill.
The button refuses to create ~/.claude/skills/ from scratch — if Claude Code isn't installed, the button shows "Install anyway" with a warning, on the assumption that you know what you're doing. If a SKILL.md already exists at the target path that wasn't installed by VaultGuard, the button shows "Overwrite existing SKILL.md" instead — same gate, explicit.
The installer is desktop-only (it touches ~/.claude/ via Node FS). On mobile Obsidian the row shows "Not available on this device."
Codex skill (optional)
Codex can load reusable skills from $HOME/.agents/skills/. VaultGuard can install a managed Codex skill at:
~/.agents/skills/vaultguard-obsidian/SKILL.md
The skill tells Codex to use the configured vaultguard MCP server for protected Obsidian vault content and to avoid raw filesystem reads/writes for encrypted notes. It contains no endpoint, bearer token, lease id, vault path, or user-specific state.
Install it from Settings → VaultGuard → Agent bridge connections → Codex skill → Install skill. If $HOME/.agents/skills/ is missing, the UI shows Install anyway so the user explicitly chooses whether VaultGuard should create the Codex skills tree. If a SKILL.md already exists at the target path without VaultGuard's managed marker, the UI shows Overwrite existing SKILL.md instead of replacing it silently.
Updating
The skill body is versioned with a managed marker (<!-- vaultguard-skill: managed schema=N -->). When VaultGuard ships a newer schema, the settings panel changes the button to Update / re-install. Re-installing only writes when the bytes have changed — repeated clicks are safe.
Uninstalling
Settings → VaultGuard → Agent bridge connections → Claude Code skill → Uninstall. Removes the vaultguard directory under ~/.claude/skills/. Refuses to delete a SKILL.md that wasn't installed by VaultGuard.
Audit trail
Every persistent-lease lifecycle event and every tool call routed through the bridge fires a structured audit event:
| Action | Fired when | Key metadata |
|---|---|---|
bridge.lease_created |
Lease minted (ephemeral or persistent) | leaseId, agentName, scopes, writeMode, persistent, ttlMinutes |
bridge.lease_revoked |
Lease deleted by user / session end | leaseId, agentName, persistent |
bridge.lease_token_rotated |
Bearer rotated via Settings | leaseId, persistent |
bridge.tool_invoked |
Every HTTP-routed tool call | leaseId, tool, transport (rpc|mcp), path/scope/query shape, outcome (success|denied|error) |
bridge.session_bound |
Persistent leases restored on plugin load | restored, dropped, userId, vaultId |
bridge.session_unbound |
Logout / refresh failure / account disabled | revoked, reason |
bridge.skill_installed |
Managed Claude or Codex SKILL.md written | action (created|updated|overwrote-conflict|noop), client when applicable, overwriteUnmanaged |
bridge.skill_uninstalled |
Managed SKILL.md removed | client when applicable, force |
Tool-call audits never include the file content, the diff body, or the search query string — only their shape (path, scope, lengths) so audit logs don't accidentally retain plaintext.
Tool RPC
The local server listens on 127.0.0.1 only and is started only after a lease is created. Every request must include the copied bearer token.
curl -sS "$VAULTGUARD_AGENT_ENDPOINT" \
-H "Authorization: Bearer $VAULTGUARD_AGENT_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"tool": "vaultguard_read",
"leaseId": "'"$VAULTGUARD_AGENT_LEASE"'",
"args": { "path": "project-x/Plan.md" }
}'
Available tools:
vaultguard_get_vault_orientation(includeKnownVaults?, includeGit?, includeConnectorStatus?): returns safe active-vault, connector, safety, protection, and bounded Git metadata. It grants no file access.vaultguard_list(scope?, limit?): returns visible paths and effective permission labels.vaultguard_search(query, scope?, limit?): returns matching paths, line numbers, and short snippets.vaultguard_read(path, maxBytes?): returns plaintext through VaultGuard's permission and at-rest decrypt path.vaultguard_apply_patch(path, diff): applies a unified diff to a text file, then writes through VaultGuard.vaultguard_create(path, content): creates a new text file through VaultGuard and refuses to overwrite existing files.vaultguard_delete(path): deletes a note after permission, delete, and write-mode checks.vaultguard_rename(path, newPath): renames or moves a note after permission and write-mode checks; refuses overwrites.vaultguard_graph(op, path?, tag?, depth?, limit?): explores neighbors, related notes, tags, or vault graph summaries without reading every file.
Reads and writes go through the plugin's adapter interceptors, so they trigger the current login/session checks, vault-scoped permission checks, audit behavior, local at-rest decrypt/encrypt, and encrypted cloud sync logic.
MCP transport (for Claudian, Claude Code, Cursor, etc.)
The same bridge server also speaks the Model Context Protocol over Streamable HTTP at the /mcp endpoint that's surfaced alongside /rpc when you mint a lease. Use this when your agent already understands MCP — most AI clients do.
The lease modal copies a ready-to-paste MCP config:
{
"mcpServers": {
"vaultguard": {
"type": "http",
"url": "http://127.0.0.1:NNNNN/mcp",
"headers": {
"Authorization": "Bearer agt_...",
"X-VaultGuard-Lease": "agl_..."
}
}
}
}
Tools surface to the agent as mcp__vaultguard__get_vault_orientation, mcp__vaultguard__list, mcp__vaultguard__search, mcp__vaultguard__graph, mcp__vaultguard__read, mcp__vaultguard__apply_patch, mcp__vaultguard__create, mcp__vaultguard__delete, and mcp__vaultguard__rename. Argument shapes match the /rpc surface — get_vault_orientation takes { includeKnownVaults?, includeGit?, includeConnectorStatus? }, read takes { path, maxBytes? }, rename takes { path, newPath }, etc.
When the lease expires, mint a new one and update the X-VaultGuard-Lease header value (the URL and bearer token survive across leases as long as the bridge server stays up).
Using with Codex
Use the Codex snippets from the lease modal or the token-rotation reveal. Codex's config.toml should reference the bearer token through an environment variable, not inline the token:
[mcp_servers.vaultguard]
url = "http://127.0.0.1:47711/mcp"
bearer_token_env_var = "VAULTGUARD_AGENT_TOKEN"
enabled = true
enabled_tools = ["get_vault_orientation", "list", "search", "graph", "read", "apply_patch", "create", "delete", "rename"]
default_tools_approval_mode = "prompt"
startup_timeout_sec = 20
tool_timeout_sec = 120
Set the token in the PowerShell session that launches Codex:
$env:VAULTGUARD_AGENT_TOKEN = '<paste token from VaultGuard lease modal>'
For protected-vault work, launch Codex from an empty temporary working directory rather than the real vault folder:
$workspace = Join-Path $env:TEMP 'vaultguard-codex-empty'
New-Item -ItemType Directory -Force -Path $workspace | Out-Null
$env:VAULTGUARD_AGENT_TOKEN = '<paste token from VaultGuard lease modal>'
codex --cd $workspace
Codex may still have filesystem tools for its current workspace. The empty-workspace launch, Codex skill, and optional AGENTS.md guidance are the contract that protected vault content is accessed only through mcp__vaultguard__*.
Using with Claudian
Claudian spawns the Claude Code CLI with cwd pinned to your vault root, which means the CLI's built-in Read/Glob/Grep tools see VaultGuard's VG1\0 ciphertext. The integration path is to register VaultGuard as an MCP server and force Claudian into a slash command that whitelists only the MCP tools — so the built-in filesystem tools are gone for that turn.
Mint a lease in Obsidian (
VaultGuard: Create Agent Bridge Lease). Use a narrowscope(e.g./project-x/**) andwriteMode: "confirm"unless you're certain.Paste the "Claudian / Claude Code MCP server" snippet from the lease modal into Claudian's MCP servers settings.
Author a slash command in Claudian that whitelists only VaultGuard's tools. The exact location depends on Claudian's slash-command storage, but the front matter must contain:
--- allowed-tools: mcp__vaultguard__* --- You are an assistant working with the user's encrypted Obsidian vault. You have read/write access only via the vaultguard_* tools — the vault directory itself is encrypted at rest, so do not call Read, Glob, or Grep against it. To find files, call mcp__vaultguard__list or mcp__vaultguard__search first.Always invoke that slash command (e.g.
/vault-ask) when working on encrypted notes through Claudian. Don't use plain chat — plain chat retains the built-in filesystem tools and they'll see ciphertext.
Caveat (real, not theoretical): Claudian doesn't currently expose a global "disallow built-in tools" setting. Outside the slash command the built-ins are reachable, and the model may try them. The failure mode is benign — the agent sees ciphertext, complains, and asks for a path it can read — but it's an unnecessary failure. A ~10-line upstream PR to Claudian that merges a user-supplied disallowedTools list into ClaudeQueryOptionsBuilder.ts:141, 193 would close the gap globally; until that lands, the slash command is the contract.
Using with Claude Code directly
If you prefer Claude Code without Claudian, run it from an empty working directory (so its built-in Read has nothing to read) and add the MCP config above to .mcp.json or pass it via --mcp-config. Then start the session with a system prompt that pins the agent to mcp__vaultguard__* tools, and deny built-in filesystem/shell tools according to the installed Claude Code version.
Integration Notes
An Obsidian-side integration can also call:
const vaultguard = app.plugins.plugins.vaultguard;
const bridge = vaultguard.getAgentBridge();
const lease = await vaultguard.createAgentBridgeLease({
agentName: "Local model",
scope: "/project-x/**",
ttlMinutes: 30,
writeMode: "confirm",
});
const result = await bridge.read(lease.leaseId, { path: "project-x/Plan.md" });
Use the public bridge API instead of fs, shell reads, or direct adapter calls.