Venira MCP README

The full upstream README, mirrored for reference. Install config, tool schemas, adoption signals and our own overview live on the Venira MCP listing page.

venira-mcp

Fast, workspace-scoped MCP server for coding agents (OpenCode, Claude Desktop, any MCP client). Two tool modes — Default (32 coding tools + shell, no CI/CD) · Expert (43 tools: default + CI/CD). Setting VENIRA_JS_EXEC_MODE=ask, semi-auto or auto adds project_execute_js (33 / 44). Pure Node, 1 native dependency (better-sqlite3), no GUI, no external binary.

Security scope. Venira is a guard rail with an audit trail, not a sandbox. It runs with the privileges of the OS user that started it. The workspace lock limits the paths the file tools can touch (unless VENIRA_UNRESTRICTED=1), not what a shell command does: commands are classified by rules (allow / ask / deny) and every call is audited, but a malicious prompt can still steer the agent into running a harmful command that the active profile lets through (with off, every command runs). Anything running as the same OS user can read the keys in VENIRA_DATA_DIR. For untrusted workspaces, or whenever you need a real isolation boundary, run Venira in a container or VM. Details: SECURITY.md.

1. Setup

One-line install (run in your project, then restart the client):

Terminal
npx --yes venira-mcp@latest --install auto --workspace .

Prefer your agent? Paste this prompt into OpenCode/Claude/Cursor:

text
Set up Venira MCP for this workspace.
1. Detect my MCP client config path.
2. Run: npx --yes venira-mcp@latest --install auto --workspace .
3. Restart the client. Then call project_health to verify.

Manual config (pick your client):

ClientConfig pathFormat
VS Code<workspace>/.vscode/mcp.jsonJSONC
OpenCode~/.config/opencode/opencode.jsoncJSONC
Claude Desktopclaude_desktop_config.jsonJSON
Cursor~/.cursor/mcp.jsonJSON
Codex~/.codex/config.tomlTOML

All use the same npx command. See §2 for ready-to-copy JSON/TOML.

In this repo .vscode/mcp.json is git-ignored because it holds machine-specific settings (and may relax security flags). Copy .vscode/mcp.example.json to .vscode/mcp.json and adjust it locally.

2. Client configs (copy-paste)

The examples use four environment variables: workspace, tool set, output size, and security posture. For most users, change only VENIRA_WORKSPACE and keep VENIRA_SECURITY_PROFILE=balanced.

OpenCode / VS Code / Cursor / Claude / Kimi / DeepSeek / Antigravity — use the wrapper format supported by your client:

jsonc
// OpenCode: "mcp" > "venira" > "environment"
{
  "mcp": {
    "venira": {
      "type": "local",
      "command": ["npx", "--yes", "--package=venira-mcp@latest", "venira-mcp"],
      "enabled": true,
      "environment": {
        "VENIRA_WORKSPACE": "/absolute/path/to/project",
        "VENIRA_TOOL_MODE": "expert",
        "VENIRA_OUTPUT_MODE": "full",
        "VENIRA_SECURITY_PROFILE": "balanced"
      }
    }
  }
}

// VS Code: "servers" > "venira" > "env"; ${workspaceFolder} expands automatically.
{
  "servers": {
    "venira": {
      "type": "stdio",
      "command": "npx",
      "args": ["--yes", "--package=venira-mcp@latest", "venira-mcp"],
      "env": {
        "VENIRA_WORKSPACE": "${workspaceFolder}",
        "VENIRA_TOOL_MODE": "expert",
        "VENIRA_OUTPUT_MODE": "full",
        "VENIRA_SECURITY_PROFILE": "balanced"
      }
    }
  }
}

// Claude / Cursor and compatible clients: "mcpServers" > "venira" > "env"
{
  "mcpServers": {
    "venira": {
      "command": "npx",
      "args": ["--yes", "--package=venira-mcp@latest", "venira-mcp"],
      "env": {
        "VENIRA_WORKSPACE": "/absolute/path/to/project",
        "VENIRA_TOOL_MODE": "expert",
        "VENIRA_OUTPUT_MODE": "full",
        "VENIRA_SECURITY_PROFILE": "balanced"
      }
    }
  }
}

Generic MCP JSON example

For clients that accept the common mcpServers JSON shape, use the example for the user's operating system as a realistic starting point. Replace the example paths with paths on the user's machine; VENIRA_DATA_DIR is optional and can point to any writable directory. Keep this file private if it contains local paths or credentials.

Windows:

config.json
{
  "mcpServers": {
    "venira": {
      "command": "npx",
      "args": ["--yes", "--package=venira-mcp@latest", "venira-mcp"],
      "env": {
        "VENIRA_WORKSPACE": "C:\\Users\\YourName\\Projects\\my-app",
        "VENIRA_DATA_DIR": "C:\\Users\\YourName\\AppData\\Local\\venira",
        "VENIRA_TOOL_MODE": "expert",
        "VENIRA_OUTPUT_MODE": "full",
        "VENIRA_SECURITY_PROFILE": "balanced"
      }
    }
  }
}

Linux:

config.json
{
  "mcpServers": {
    "venira": {
      "command": "npx",
      "args": ["--yes", "--package=venira-mcp@latest", "venira-mcp"],
      "env": {
        "VENIRA_WORKSPACE": "/home/yourname/projects/my-app",
        "VENIRA_DATA_DIR": "/home/yourname/.local/share/venira-mcp",
        "VENIRA_TOOL_MODE": "expert",
        "VENIRA_OUTPUT_MODE": "full",
        "VENIRA_SECURITY_PROFILE": "balanced"
      }
    }
  }
}

mcpServers is a common client configuration shape, but some clients use a different outer key or file format. Preserve the Venira command, args, and environment values when adapting the example. The approval command returned at runtime uses the effective data directory from this configuration, so users and agents should copy data.approval.approve_command instead of constructing a path themselves.

Codex (~/.codex/config.toml):

toml
[mcp_servers.venira]
command = "npx"
args = ["--yes", "--package=venira-mcp@latest", "venira-mcp"]

[mcp_servers.venira.env]
VENIRA_WORKSPACE = "C:\\path\\to\\project"
VENIRA_TOOL_MODE = "expert"
VENIRA_OUTPUT_MODE = "full"
VENIRA_SECURITY_PROFILE = "balanced"

Security profiles

VENIRA_SECURITY_PROFILEShell policyUse
strictstrictRead-only and workspace-local allowlist runs without approval
balanced (default)balancedRecommended general-purpose setting
trustedpermissiveAsk-rule and unclassified commands run; hard-deny commands require approval
offoffTrusted environments only; prints a warning and audits shell commands

VENIRA_SHELL_MODE is an advanced override and takes precedence over the profile. VENIRA_JS_EXEC_MODE separately gates the JavaScript tool: off (default) hides it, ask registers it with per-call human approval, semi-auto registers it without approval but keeps the containment (network guard, workspace-only fs, scrubbed env), auto registers it without approval and without any containment (full network and fs access, inherited env; personal machines only).

Choose trusted when you want fewer prompts on a personal machine. Choose off only when you intentionally accept that shell checks are disabled; JS exec is still gated by VENIRA_JS_EXEC_MODE (a human gate for project_execute_js only counts when that mode is ask). The secret_env gate remains active in every profile.

Advanced configuration

VariableDefaultChange when
VENIRA_SHELL_MODEProfile mappingYou need to override only the shell policy (strict, balanced, permissive, or off)
VENIRA_JS_EXEC_MODEoffask registers project_execute_js with per-call approval; semi-auto registers it without approval but contained; auto registers it without approval and without containment
VENIRA_UNRESTRICTED0Keep workspace path restrictions enabled; do not set to 1 in production

See the full environment reference in §6 for caching, shell, provider, and diagnostic options.

3. Benchmark (per-op, p50)

Measured: Windows + Node 24, standalone build, 2026-11-01.

Opp50Notes
Cached read_file8.0 mswarm cache
read_batch (8 files)12.8 msone call, 8 items
get_file_info6.2 mscached
search (literal, *.ts)11.5 msp95 ≈ 1.3 s (file count dependent)
health (no DB)28.5 ms
shell_run + status~930 ms2 s wait budget dominates
First call113.7 mspost warm-up

Batch: 8 reads as one batch call (16.6 ms) vs 8× individual (~76 ms) — the round-trip saving that makes the agent loop fast.

Agent tool-path benchmark: Venira MCP vs Codex exec_command

Measured on 2026-11-07 on one Windows 11 build 26200 machine (Intel Core i5-1035G1, 8 logical processors, 11.8 GiB RAM, Node v24.20.0). Each workload used 20 paired samples per phase, alternating call order. Venira used the persistent MCP connection; each native exec_command call launched a fresh PowerShell process. Cache-miss samples cleared Venira's in-process cache; warm samples reused it. The OS filesystem cache stayed warm.

WorkloadPhaseVenira p50 / p95exec_command p50 / p95Ratio of p50s
Read first lines of 8 files in one batchCache miss53.5 / 72 ms962 / 1,121 ms17.98×
Read first lines of 8 files in one batchWarm39 / 60 ms1,103.5 / 1,549 ms28.29×
Search VENIRA_WORKSPACE across the workspace (49 hits)Cache miss118.5 / 188 ms1,034.5 / 1,507 ms8.73×
Search VENIRA_WORKSPACE across the workspace (49 hits)Warm34.5 / 152 ms1,018 / 1,383 ms29.51×

Both workloads matched exactly in all 20 pairs: normalized text for the 8-file batch and sorted file/line locations for search. These are practical agent tool-path timings, not raw filesystem timings. A separate 100-sample component benchmark shows native PowerShell can be faster for simple uncached reads; the largest Venira gains are on warm list/search and avoiding a fresh shell process. The p95 estimates are rough with 20 samples, and results from this one machine are not a general performance guarantee.

See the full methodology and results and raw samples. Reproduce the component benchmark from the repository root with npm run bench:tools -- --runs=100.

4. Two tool modes

Two tool modes set which tools the client sees. Set via VENIRA_TOOL_MODE env var or "toolMode" in venira.config.json (env wins). VENIRA_PROFILE=core|full still applies on top.

ModeToolsIncludes
default32 (33 with VENIRA_JS_EXEC_MODE=ask, semi-auto or auto)Read/write/edit/search, code intel, snapshots, shell, project_delete_path, optional JS exec — no CI/CD
expert43 (44 with VENIRA_JS_EXEC_MODE=ask, semi-auto or auto)Everything in default + project_pr_*, project_ci_*

project_delete_path is always available. In balanced / strict mode, shell rm -rf / / ~ and Windows drive-wide deletes are hard-denied, and recursive/force deletes (Remove-Item -Recurse -Force, rd /s /q, del /f /q) need approval. permissive mode lets ordinary ask-rule and unclassified commands run without a prompt, but hard-denies require approval and uninspectable command substitutions require approval in every policy-enabled mode.

5. Tools quick reference

All tools return the standard envelope (ok, kind, data, hint, elapsed_ms).

Read (batch-safe, cached)

ToolPurpose
project_list_filesList files/dirs, depth 1–3, limit ≤ 200
project_read_fileRead a file by line range; optional ranges[] (up to 16)
project_get_file_infoFile/dir metadata (kind, size, mtime)
project_searchSearch by file/dir name, content, regex, or symbol; hybrid: 1 tags hits with source+confidence
project_recent_filesRecently modified files (index-first, scan-fallback)
project_read_batchUp to 8 read-only ops in one call; each item takes the same arguments as its standalone tool (search items support regex, case_sensitive, hybrid, ...)
project_healthDiagnostics: capability, queue, cache hit-rates, p50/p95, security limits notice, and server.workspace_switching (enabled, allowed_roots)
project_read_auditRead recent audit log rows

Write (mutating, pass reason)

ToolPurpose
project_write_fileCreate/overwrite/append; expected_sha256 guards blind overwrites
project_edit_fileReplace old_text→new_text; edits[] (up to 64); fuzzy edit opt-in
project_edit_batchMultiple edits; stops at first failure and attempts checkpoint rollback. Check data.atomic and data.rollback_succeeded; snapshot failure/incompleteness or a partial restore means changes may remain.
project_createCreate a directory (missing parents are created; use project_write_file for file content)
project_delete_pathDelete file/dir; not undoable

Checkpoints (git-free rollback)

project_snapshot — create/list/restore/delete. Content-addressed, deduplicated, atomic per-file restore.

Code intelligence

ToolPurpose
project_outline_symbolParse a file's symbols (functions, classes, types) with line numbers
project_diff_filesUnified diff between two files
project_symbol_referencesFind all occurrences of a named symbol
project_selective_searchMulti-query search with AND/NOT + fuzzy scoring
project_dependency_graphTS/JS/Python/Rust/Go/Java/Kotlin import/symbol graph (JSON/DOT/Mermaid)
project_change_impact"What breaks if I edit this?" — bounded reverse/forward closure

Shell (async, policy-gated)

ToolPurpose
project_shell_runRun a command; background: 0 blocks for final output; 1 returns run_id. shell: powershell (Windows PowerShell 5.1), pwsh (PowerShell 7+), cmd or bash; omitted → VENIRA_DEFAULT_SHELL, else cmd on Windows and $SHELL/bash elsewhere
project_shell_statusPoll a running command (not queued, not audit-logged)
project_shell_cancelKill a running command by run_id
project_shell_cancel_sessionKill all shell runs in this session

Choosing a shell on Windows. powershell starts powershell.exe (5.1), which has no && / || pipeline-chain operators; pwsh starts PowerShell 7+ (winget install --id Microsoft.PowerShell), which has them. pwsh must be on the PATH of the server process, so restart the MCP client after installing it. Policy treats both as the same PowerShell dialect: every segment of an && / || chain is classified on its own, so a denied command after && is still denied (policy_adv_dialect_pwsh_chain_operators). pwsh starts slower than cmd; set VENIRA_DEFAULT_SHELL=pwsh only if you want it for every call. A shell binary that is not installed fails with [shell_unavailable] and an install hint instead of returning exit code -1. In PowerShell an unquoted (...), @(...) or {...} counts as a substitution the policy cannot inspect, so a command that carries one always asks for approval in policy-enabled modes, including permissive: PowerShell evaluates the group before the command runs, so git status (Remove-Item -Recurse -Force $HOME) would otherwise delete while looking like read-only git (policy_adv_dialect_powershell_grouping_expressions).

Git provider: PR/CI (GitHub, GitLab, Bitbucket)

project_pr_create/comment/merge/list/close/reopen, project_ci_status/trigger/logs/wait, project_ci_control (cancel / retry-failed; Bitbucket supports cancel only) — same tool args across providers; token from project_secret or env vars.

CI reads are bounded and never hide a cap:

  • Pagination — status/job/check-run/step lists follow the provider's next-page link (Link header on GitHub/GitLab, body next on Bitbucket) for at most 5 pages / 200 items, stay on the configured API origin, and never fetch the same page twice. When a cap or the provider total stops the walk, the result says so (truncated / partial) instead of reporting a partial list as complete.
  • Verdicts — a finished Bitbucket pipeline/step is read from state.result (not just COMPLETED), so a failed run is never shown green.
  • Logs — failing steps/jobs are read first; archives and entries are byte-capped (see VENIRA_GIT_LOG_*), and a log whose declared size is over the cap is refused before it is downloaded.
  • project_ci_wait — one bounded long-poll (no polling loop). Returns summary (one line), next_step (wait_again · merge_or_continue · fix_failing_step_then_push · read_logs · inspect_status), failing_jobs (max 5) and, on failure, a failure_tail of the first failing job (numeric ids on GitHub/GitLab, UUID step ids on Bitbucket).

Secrets (encrypted at rest)

project_secret — store/inspect/remove credentials by name; AES-256-GCM; rotation metadata; expired secrets refused. project_github_connect — GitHub Device Flow, tokens in vault.

Diagnostics & planning

project_explain_error — turn an error into a cause + next tool calls (rule table, no model). project_plan_read_batch — plan which files to read for a task, returns ready project_read_batch call.

JS execution

project_execute_js — run synchronous JS (opt-in via VENIRA_JS_EXEC_MODE). The mode decides both the approval step and how much the snippet is contained:

ModeApproval per callNetworkFilesystemEnvironment
off (default)tool not registeredn/an/an/a
askyesblockedworkspace onlyscrubbed
semi-autonoblockedworkspace onlyscrubbed
autonoopenunrestrictedinherited
  • Contained (ask, semi-auto): net, tls, http, https, http2, dgram and dns throw ERR_ACCESS_DENIED; fetch, WebSocket, EventSource and XMLHttpRequest are removed; Node's permission model limits reads and writes to the workspace and denies child processes and workers; only PATH, TERM and NODE_PATH are passed (HOME and USERPROFILE are empty). This is best-effort containment, not a sandbox.
  • auto drops all of the above: the snippet has the full network, the full filesystem, child processes and the server's environment (including any tokens in it). Use it only on a personal machine you trust. project_health reports js.exec_without_approval (a warning) for both semi-auto and auto; it does not distinguish the two.
  • semi-auto is the old behaviour of auto. If your config says VENIRA_JS_EXEC_MODE=auto and you want the snippet to stay contained, change it to semi-auto.
  • Output is still redacted for obvious secret assignments, and the timeout (default 30 s, max 10 min) applies in every mode.

6. Environment variables (key ones)

Full reference: the source reads 90+ distinct VENIRA_* names; this table lists the documented ones. See the source or npx venira-mcp --help for the rest.

VariableDefaultWhat it does
VENIRA_WORKSPACEprocess.cwd()Workspace root; all paths resolve against it
VENIRA_TOOL_MODEdefaultdefault (32 tools) or expert (43 tools); +1 with VENIRA_JS_EXEC_MODE=ask, semi-auto or auto
VENIRA_OUTPUT_MODEfullfull or compact (summarize/trim payloads > 8 KiB; generic payloads use preview_text)
VENIRA_PROFILEfullfull or core (disables dependency graph / change impact)
VENIRA_UNRESTRICTED"0""1" disables workspace lock (never in production)
VENIRA_SECURITY_PROFILEbalancedSelects the shell policy: strict, balanced, trusted, or off
VENIRA_SHELL_MODEProfile mappingAdvanced override for shell policy: strict, balanced, permissive, or off
VENIRA_SHELL_EXIT_EARLY"1""1" pollers return as soon as exit code is known
VENIRA_SHELL_TIMEOUT_POLICY"soft""soft" warns on timeout · "hard" kills process tree
VENIRA_SHELL_ENV_PASS_ALL"0""1" passes full host env to shell (build tools that need it)
VENIRA_DEFAULT_SHELLcmd on Windows, $SHELL / bash elsewhereShell used when a call does not name one: powershell (5.1), pwsh (7+), cmd, bash
VENIRA_JS_EXEC_MODE"off""off" hides project_execute_js · "ask" registers it with per-call approval · "semi-auto" registers it without approval, contained (network guard, workspace-only fs, scrubbed env) · "auto" registers it without approval and without containment
VENIRA_USE_RIPGREP"1""1" uses ripgrep if installed (faster on big repos)
VENIRA_CACHE_ALL"1""1" TTL caches on · "0" always-fresh (slower)
VENIRA_EDIT_FUZZY"0""1" enables fuzzy-match fallback for old_text, and adds fuzzy_distance, ignore_indentation and case_insensitive to the project_edit_file / project_edit_batch schemas. tools/list changes with this flag, so reconnect the client after changing it. With "0" those options are not advertised and a call that still sends them is refused with [unsupported]
VENIRA_DATA_DIRper-user OS pathWhere audit DB + encrypted secrets live
VENIRA_SNAPSHOT_ROOT<per-user data dir>/snapshotsShared checkpoint root for every project and client; each workspace gets its own sub-folder (absolute path)
VENIRA_SNAPSHOT_DIRunsetExact checkpoint store for a single project, no sub-folders; wins over VENIRA_SNAPSHOT_ROOT (absolute path)
VENIRA_SNAPSHOT_KEEP20Checkpoints kept per project (oldest pruned on create)
VENIRA_SNAPSHOT_MIRROR_DIRunsetOptional append-only copy of the checkpoint store in this existing folder (OneDrive/Dropbox, NAS share, USB drive, rclone mount); absolute path, must be outside the store and the workspace
VENIRA_SNAPSHOT_MIRROR_TIMEOUT_MS20000 after create, 100000 for action=mirrorTime budget of one mirror run; it stops between files (partial) and the next run resumes
VENIRA_DISABLE_TOOLSunsetComma-separated tool names to hide from tools/list
VENIRA_GIT_PROVIDERinferredgithub / gitlab / bitbucket
VENIRA_GIT_TOKENunsetFallback token when no provider-specific secret found
VENIRA_GIT_TIMEOUT_MS60000Per-request timeout for provider JSON calls (1 000–600 000)
VENIRA_GIT_RETRIES1Retries for safe GET reads only (0–3); POSTs are never replayed
VENIRA_GIT_LOG_TIMEOUT_MS90000Timeout for CI log downloads (1 000–900 000)
VENIRA_GIT_LOG_ZIP_BYTES33554432Max bytes of a CI log archive; larger declared sizes are refused before download
VENIRA_GIT_LOG_ENTRY_BYTES / VENIRA_GIT_LOG_TEXT_BYTES4194304 / 8388608Per-entry and total decoded log text caps
VENIRA_SECRET_ALLOW_REVEAL"0""1" allows reveal=1 to print secret plaintext
VENIRA_SHELL_OVERRIDESunsetOperator allowlist: ALLOW_GIT_PUSH, ALLOW_ENV_READ
VENIRA_TRACE"0""1" logs every tool call to stderr (debug only)
VENIRA_PERMISSION_MODEallow_allPermission mode passed when the session scope opens (manual / best_effort / allow_all); shown in project_health
VENIRA_SESSION_IDautoSet for test/verify runs to separate audit rows
VENIRA_SHELL_ELICIT"0""1" asks the person through an MCP confirmation dialog (client must support elicitation)
VENIRA_SESSION_GRANT_RULESbuilt-in: cd_outside_workspaceExtra ask-rule ids (comma-separated) that may be approved with --approve --session
VENIRA_SHELL_APPROVAL_TTL_MS600000 (10 min)Lifetime of an in-memory --session grant; pending approvals always expire after a fixed 5 minutes
VENIRA_ALLOW_SELF_INSTALL"0""1" lets project_install_self write other clients' config files
VENIRA_SHELL_POLICY_FILEunsetPath to a JSON shell policy file (allow/ask/deny rules); never read from the workspace automatically
VENIRA_TOOL_POLICY_JSON / VENIRA_TOOL_POLICY_FILEunsetPer-tool policy rules, inline JSON or a file (see lib/tool-policy.ts)

7. Security model

Conservative by default:

  • Workspace lock: all paths validated inside VENIRA_WORKSPACE; ../ escape rejected (path_escape).
  • Security profile (VENIRA_SECURITY_PROFILE, default balanced): sets the shell policy. VENIRA_SHELL_MODE can override it. Every project_shell_run command is classified allow, ask or deny.
    • balanced (default): hard-deny rules refuse the command; ask rules need approval; routine dev-workspace commands run; anything unclassified needs approval.
    • strict: like balanced, but only a read-only / workspace-local allowlist runs without approval.
    • permissive (the trusted profile): ordinary ask-rule and unclassified commands run without a prompt; hard-deny matches become approval requests (ask.deny.<rule>), and uninspectable command substitutions always require approval.
    • off: the shell policy is disabled — every command runs, nothing is asked or denied (only secret_env injection still asks). Prints a startup warning; use only in a trusted environment. Previous permissive behaviour (v1.14.4) = off.
    • Hard-deny (14 rules): disk destroy, filesystem mount/overwrite, shutdown/reboot, fork bomb, pipe download to shell, privilege escalation (sudo/doas/runas), filesystem root/drive-home delete (rm -rf /, rm -rf C:\, Remove-Item -Recurse -Force $HOME, …), chmod 777 /, Windows drive-wide and wildcard mass delete, encoded PowerShell commands, self-approval (touching the approve CLI or approval store), protected credential/key files.
    • Ask (20 rules): command substitution, reads/writes/cd outside the workspace, network access, package mutation, git history rewrite, container/VM control and state, process control and taskkill /f, secret dumps, dynamic execution and wrapper commands, find -delete/-exec, destructive recursive/force deletes (Remove-Item -Recurse -Force, rd /s /q, del /f /q; prefer project_delete_path), attribute reset, infinite writes to NUL, unresolved file arguments.
    • Deny always wins over ask.
  • Secrets: AES-256-GCM encrypted at rest; values never in tool metadata; secret_env requires human approval; expired secrets refused.
  • Provider DLP: Secretlint scans PR/CI request args and HTTP payloads; detected credentials blocked before sending, withheld from response.
  • Audit trail: all ops (including failures) written to SQLite WAL; project_read_audit for post-mortems.
  • Token transport: provider token in Authorization header only; dropped on cross-origin redirect.

Approvals — what needs a human, and how it is granted

GateTriggers whenSwitch (default)
Shell denyA hard-deny rule matches: refused outright, no approval possibleVENIRA_SECURITY_PROFILE (balanced; trusted asks; off disables shell checks)
Shell askAn ask rule matches, or the command is unclassified in balanced / strictVENIRA_SECURITY_PROFILE (balanced) · advanced override: VENIRA_SHELL_MODE
Secret injectionproject_shell_run with secret_envalways asks, in every mode
project_execute_jsEvery callVENIRA_JS_EXEC_MODE (off: tool not registered; ask: per-call approval; semi-auto: no approval, contained; auto: no approval, no containment)
Secret plaintextproject_secret with reveal=1VENIRA_SECRET_ALLOW_REVEAL (0)
Self-installproject_install_self writes another client's configVENIRA_ALLOW_SELF_INSTALL (0)
Named shell overrideALLOW_GIT_PUSH / ALLOW_ENV_READVENIRA_SHELL_OVERRIDES (unset), plus override and a reason on the call

ALLOW_GIT_PUSH covers one git push command with publish flags only (-u, --follow-tags, --tags, --atomic, --dry-run, ...) to a remote name and plain refs. It does not cover staging, committing, or chained commands; run those separately under normal policy so the operator can review repository changes before publishing. Force (-f, +ref, --force-with-lease), deletes (:ref, --delete), --mirror, --prune, --no-verify, URLs as remotes, pipes, redirects and substitutions fall back to the normal verdict (ask/deny).

Granting a shell or JS approval:

  1. The call fails with [approval_required] and returns data.approval_id (valid for 5 minutes) together with the command or code, the rule and the reason. Shell and JS share one payload, data.approval: { approval_id, gate, rule, reason, subject_preview, expires_at, approve_command, retry_with: { tool, arguments } }.
  2. A person copies data.approval.approve_command exactly into an interactive PowerShell terminal (the returned command includes the server data directory and works in Windows PowerShell and PowerShell 7), reviews the request and answers y. The CLI refuses without a TTY. --yes skips the prompt. --session extends the grant to the same command group until it expires, but only for rules in VENIRA_SESSION_GRANT_RULES (built-in: cd_outside_workspace).
  3. The agent retries the same call with approval_id. The grant is HMAC-signed, tied to the session, bound to the exact command / shell / cwd / secret_env / rule (or code / argv for JS), and single-use. Concurrent retries of the same id cannot both run: consumption is an atomic file claim, so exactly one wins.

TTL behavior: the pending request (approval_id) is valid for a fixed 5 minutes and cannot be revived by retrying. A --session grant lasts 10 minutes by default (VENIRA_SHELL_APPROVAL_TTL_MS) and lives only in this server process; restarting the server clears it.

Agent helpers:

  • project_shell_run with dry_run: 1 returns the tier, rule and would_run / requires_approval without running anything or creating an approval, so an agent knows beforehand which command will be asked.
  • approval_wait_ms (max 45000) on project_shell_run / project_execute_js keeps the call open while the human approves; a grant that arrives in time runs the command in the same call, otherwise you get the normal [approval_required] (the pending approval_id stays valid). Keep it below the client's request timeout.
  • project_health reports approvals (mode, JS gate, elicitation, session rules, TTL, max wait), the shared posture report (level, stable finding ids such as workspace.unrestricted, profile.off, combo.unrestricted_open_shell, js.exec_without_approval) and the flat warnings list. Posture findings are warnings only; invalid values already fail closed at startup. doctor surfaces the same report.

Alternative (off by default, project_shell_run only): VENIRA_SHELL_ELICIT=1 shows a confirmation dialog in MCP clients that support elicitation, and accepting it records the grant for you.

Removed in v1.15: approval_token and VENIRA_SHELL_AUTO_APPROVE_TOKEN. Passing approval_token now returns [approval_invalid] with a hint to use approval_id; setting the env var only logs a warning and is ignored.

Removed: VENIRA_ALLOW_JS_EXEC and VENIRA_JS_EXEC_APPROVAL — use VENIRA_JS_EXEC_MODE (off / ask / semi-auto / auto) instead. Setting the old vars only logs a warning and is ignored.

Approvals are a guard rail, not a sandbox: a process running as the same OS user can read the approval key.

Snapshot store (checkpoints)

Checkpoints live outside every workspace, in one shared per-user folder, so every MCP client that opens the same project sees the same history:

Code
%LOCALAPPDATA%\venira\snapshots\        (Linux: ~/.local/share/venira/snapshots, macOS: ~/Library/Application Support/venira/snapshots)
  my-app\              project.json  index\  blobs\
  my-app-6cb89aac\     a second workspace that is also called "my-app"
  • Folder name = workspace folder name. The first workspace to claim a name keeps it (recorded in project.json); a different workspace with the same name gets a -<hash8> suffix. The same workspace always maps to the same folder, whatever the path casing or 8.3/symlink spelling.
  • Override with VENIRA_SNAPSHOT_ROOT (shared root) or VENIRA_SNAPSHOT_DIR (exact folder, single project). Both must be absolute paths; a relative one fails with [config_invalid].
  • The file tools (read, write, edit, search, delete) refuse anything under the snapshot root or exact dir, the same way they refuse the data dir. Only project_snapshot touches the store.
  • Retention is per project (VENIRA_SNAPSHOT_KEEP), so one busy project cannot push out another's checkpoints.
  • Shell commands are guarded too: any read, write or delete operand inside the snapshot store is a hard deny in balanced / strict (deny.protected_file_access) and needs approval in permissive. off disables shell policy entirely, so keep checkpoints you cannot afford to lose in a separate backup as well.
  • Unreadable checkpoints are reported, never dropped silently. project_snapshot action=list adds unreadable[] (snapshot_version_unsupported = written by a newer venira-mcp, snapshot_corrupt = broken index file) and a hint. While any index is unreadable, blob cleanup is paused (gc_skipped: "unreadable_manifests" on delete), because that index still owns blobs this build cannot enumerate. For the first case upgrade venira-mcp; for the second, remove the broken index\<id>.json.gz yourself (agent shell access to the store is denied by design) and cleanup resumes.
  • Manifests carry a version. Older layouts are upgraded in memory when read (MIGRATIONS in lib/snapshot-store.ts); the file on disk is never rewritten.

Backing up the store

The store is plain files: immutable content-addressed blobs\<ab>\<sha256>, gzip manifests in index\ written by write-then-rename, and project.json naming the owning workspace. A backup is a folder copy; a restore is copying it back to the same location (a checkpoint only restores into the workspace recorded in its manifest).

  • Run the copy yourself, outside the agent: shell policy denies the store to agent commands.
  • Copy the shared root (VENIRA_SNAPSHOT_ROOT) to cover every project, or one project folder. Windows: robocopy "%LOCALAPPDATA%\venira\snapshots" "D:\backup\venira-snapshots" /E; Linux/macOS: rsync -a ~/.local/share/venira/snapshots/ /mnt/backup/venira-snapshots/ (macOS: the Application Support path above).
  • Do it while no agent is creating or deleting checkpoints. If you copy in two passes, take index\ first and blobs\ second: a blob is always written before the manifest that references it, so that order cannot capture a manifest without its blobs, while the reverse order can. Blobs without a manifest are harmless leftovers.
  • After restoring a backup, project_snapshot action=list should show the expected count and no unreadable entries.

Mirroring the store (optional)

Set VENIRA_SNAPSHOT_MIRROR_DIR to an existing absolute folder and venira keeps a second copy there, with no cloud SDKs, credentials or network code of its own (point it at a OneDrive/Dropbox folder, NAS share, USB drive or rclone mount and let that tool move the bytes).

  • Same layout as the store (<mirror>\<project>\project.json, index\, blobs\), so restoring is copying the folder back to the store location; there is no separate format.
  • After every project_snapshot action=create the result carries mirror (status, copied_blobs, copied_indexes, bytes, failed[], error). It is best-effort: a mirror problem never changes the checkpoint's own status. project_snapshot action=mirror runs the same sync on demand; add verify=1 to re-hash every blob already at the mirror and re-copy bad ones.
  • Order is blobs, then indexes, then project.json, so the mirror never holds a checkpoint whose blobs are missing. Every copy is verified (blob sha256 equals its file name; index bytes equal the source) before it gets its final name. A corrupt local blob blocks only the checkpoints that use it.
  • Append-only: venira never deletes anything at the mirror. A checkpoint pruned locally (VENIRA_SNAPSHOT_KEEP) or deleted stays at the mirror until you remove it by hand, so expect it to grow; the only overwrite is verify=1 replacing a file that no longer matches its source.
  • Bounded: a run stops between files once its time budget is spent and reports partial / timed_out; run it again to resume (only missing files are copied). A first sync of a large store may need a few runs.
  • Refused ([config_invalid]): a relative path, a folder that does not exist (venira never creates the mirror root, so an unmounted drive is not silently replaced by a local folder), and a folder that equals, contains or sits inside the local store or sits inside the workspace. A mirror folder already owned by a different workspace fails with [mirror_conflict].
  • The mirror folder is a protected root like the store: file tools refuse it and shell commands that read, write or delete inside it are denied.
  • To restore after losing the store: copy <mirror>\<project> back to the store location (for example %LOCALAPPDATA%\venira\snapshots\<project>) with venira stopped, then project_snapshot action=list. Unreadable local indexes are not mirrored (they are counted in skipped_unreadable).

Threats this does NOT protect against: a malicious prompt can still instruct the agent to run destructive commands. Use a container/VM for untrusted workspaces — see ISOLATION.md for Docker, WSL 2 and Windows Sandbox setups, including mounts, network controls and their limits.

8. Envelopes & error codes

Success:

JSON Config
{ "ok": true, "status": "completed", "kind": "file_read", "data": { … }, "hint": "Next chunk: …", "elapsed_ms": 2.57 }

Failure:

JSON Config
{ "ok": false, "status": "failed", "error": { "code": "not_found", "message": "…", "retryable": false }, "hint": "Check with project_list_files first", "next_actions": [{ "tool": "project_list_files", "arguments": { "relative_path": "." } }] }

ok describes the tool operation. For project_shell_run, a command that ran and exited non-zero still has ok: true; command_status: "failed" and hint report the command outcome. A running or cancelled command reports command_status: "running" or "cancelled". A read batch with failed items reports top-level ok: false, status: "partial", and error.code: "batch_partial_failure"; inspect data.responses for each result.

In compact mode, shortened payloads set trimmed: true and truncated: true; summary describes omitted content. For an otherwise unsupported large payload, data.preview_text contains a bounded JSON-text preview and summary reports the original size and shape. Large error messages or attached error data are also explicitly marked as trimmed. Use full when the complete payload is required.

Common codes: not_found, invalid_request, old_text_not_found, old_text_ambiguous, stale_content, path_escape, resource_limit, sensitive_result, unsupported, internal.

9. Agent instructions (paste into AGENTS.md / CLAUDE.md)

text
Venira MCP rules:
1. Unfamiliar task: call project_plan_read_batch first, then run steps[0].
2. Read with project_read_batch (up to 8 items). Never re-read a file a tool already returned.
3. Prefer project_edit_file / project_edit_batch over project_write_file; pass `reason` on every mutation.
4. Snapshot before multi-file or destructive changes.
5. Short commands: project_shell_run with background: 0. Long jobs: background: 1, then poll project_shell_status.
6. Do not call project_execute_js unless the user asked for JS; in `ask` mode it needs per-call approval, and in `semi-auto` the network is blocked (use project_shell_run for network access).
7. On failure, follow the envelope's hint. Treat ok:false + retryable:false as final.
8. Report only what the tool returned.
9. On [approval_required]: stop, tell the user the approval_id and approve_command, do not loop or approve it yourself; retry once with data.approval.retry_with after they confirm. Use dry_run: 1 to preview a command's tier.

10. Development

Terminal
npm run build       # tsc → dist/  (verify launches dist/server.js, so rebuild before verifying)
npm run smoke       # in-process sanity check
npm run doctor      # 15 preflight checks
npm run verify      # 518 behavioural + security checks
npm run coverage:shell  # V8 coverage for lib/shell-*.ts (report only, no thresholds)
npm run bench:mcp   # end-to-end numbers

Adding a tool:

  1. Register in tools/project.ts → registerProjectTools, use the config() helper.
  2. Read-only: route through executeCached; mutations: add to MUTATING set.
  3. Envelope: use buildEnvelope / errorEnvelope from lib/output.ts.
  4. Add a check in the matching section module under scripts/verify/ (scripts/verify.ts only orchestrates); update the decision tree in server.ts INSTRUCTIONS.
  5. npm run build && npm run verify && npm run bench:mcp — paste numbers in the PR.

11. Troubleshooting

SymptomFix
not_found on a path that looks fineRun npm run doctor; confirm VENIRA_WORKSPACE points where you think
path_escapePath resolves outside workspace; use a workspace-relative path
Shell command not finishingproject_shell_status with wait_seconds: 30; check project_health → shells.active
better-sqlite3 build failureUnusual platform; run npm rebuild better-sqlite3 with build tools, or use the Docker image
SQLite binding unavailableThe server still starts. Audit writes are skipped in the default best-effort mode; set VENIRA_AUDIT_STRICT=1 to fail operations that cannot be recorded. Install/rebuild better-sqlite3 to restore the audit trail.
venira.db won't openAnother process has the WAL locked; make sure only one server writes to the same data dir
Everything slowproject_health → queue.depth and per-op p95

License

Licensed under the GNU Affero General Public License v3.0 (AGPLv3) — see LICENSE.

Free for developers, individual use, and open-source projects. Embedding Venira into proprietary closed-source services or commercial agent platforms requires a commercial license from the author.