Every GUARDIAN_* variable the MCP server, the hooks, the CLI and the developer tooling read, with its default. mcp/test/docs/docs.test.ts fails when the code reads one this page does not name.
Set them where the process that reads them starts: in the env block of the MCP server entry (.claude-plugin/plugin.json for the plugin, your host's MCP config otherwise), in the shell that launches Claude Code for the hooks, or in the CI job for the CLI.
| Variable | Read by | Default | Effect |
|---|---|---|---|
GUARDIAN_OFFLINE |
MCP server, CLI (scan, baseline update), hooks |
unset | 1 switches off the lookups dev-guardian makes on its own initiative: CISA KEV / FIRST EPSS (prioritize_findings, risk_score, create_fix_pr), the registries and OSV for vetting (vet_packages, the install hook), scan_skill's OSV lookup (check_deps), verify_live, Wordfence / wordpress.org (wp_vuln_check with mode: "source"), and cosign's signature and provenance check of an image (scan_containers: cosign skipped and in missing_tools). What could not be measured is reported as unknown / unavailable / partial coverage, never as clean. It does not stop requests to a target you named (scan_dast, wp_vuln_check with mode: "rest", perf_check, a scan_skill URL) or the scanners' own network use (Semgrep registry, Trivy database) — see SECURITY.md. |
GUARDIAN_RETENTION_SCANS |
MCP server | 50 |
Scans kept per (project, scan type) in .guardian/guardian.db; 0 keeps everything. Scoped scans (a diff, --staged) and scans that measured nothing (failed, cancelled, coverage none) are counted apart, N of each, so the newest scan that did measure is never pushed out. A baseline's scan, a running scan, the scans a baselined security_scan_full or audit_executive run stands on (its children, its sub-scans), and every scan the project's open findings are read from (including an older one whose findings newer, partial scans did not measure again) are never pruned. Pruning runs in the background after the server starts. |
GUARDIAN_SCAN_TIMEOUT_MS |
MCP server, CLI | 600000 (10 min) |
Timeout for a scanner process when the tool sets none of its own. On timeout the whole process tree is killed. scan_containers' cosign check gives each image this as ONE budget shared by all its cosign calls (each call also capped at 3 min); what the budget cuts short is reported as no verdict, naming it. |
GUARDIAN_ENGINE_LOAD_DEADLINE_MS |
CLI | 60000 (1 min) |
How long cli/dev-guardian.mjs waits for its engine (mcp/dist/engine.js) to load, at least 1000. An engine that does not finish loading in that time — like one that cannot be loaded, or one from another build that lacks what the CLI uses — is a damaged install: one line saying so, exit 3. |
GUARDIAN_MCP_AUDIT_BUDGET_MS |
MCP server | 600000 (10 min) |
The whole budget of one audit_mcp_tools call. Each server gets the smaller of its timeout_ms and what is left; the servers left when it runs out are skipped with that reason, never passed. |
GUARDIAN_KEEP_REPORTS |
MCP server, CLI | 3 |
Report directories of each kind (sast-…, deps-…, surface-…, …) kept under a project's .guardian/reports/; a scan removes the older ones when it starts its own. Never pruned: an SBOM, a VEX document, a perf_check report or DAST evidence (a stored finding points at it). At least 1. |
GUARDIAN_MAX_CONCURRENT_SCANS |
MCP server, CLI | 2 |
Scanner processes allowed to run at once; further calls queue. security_scan_full holds no slot itself. |
GUARDIAN_SEMGREP_IMAGE |
MCP server, CLI | semgrep/semgrep |
Docker image for the Semgrep fallback that scan_sast and map_attack_surface use when semgrep is not installed but Docker is. Pin a tag here for reproducible runs. |
GUARDIAN_CACHE_DIR |
MCP server | per-OS user cache | Where wp_vuln_check (mode: "source") keeps the Wordfence feed, used as given — and where a wp_vuln_check run against a site with no install on this machine (live mode, a URL only) writes its WPScan report: <cache>/wp-vuln-check/<host>-<hash>/wpvuln-…/, the newest GUARDIAN_RETENTION_SCANS of each site kept. Those reports are not under GUARDIAN_DATA_DIR, even though on Windows the default cache sits inside the default data directory: to keep a run out of your profile (a test, a sandbox), set both. Default: %LOCALAPPDATA%\dev-guardian\cache on Windows, $XDG_CACHE_HOME/dev-guardian or ~/Library/Caches/dev-guardian on macOS, $XDG_CACHE_HOME/dev-guardian or ~/.cache/dev-guardian elsewhere. |
GUARDIAN_DATA_DIR |
MCP server, CLI (status, dashboard, db adopt), hooks |
per-OS user data dir | dev-guardian's per-user data: the registry of the databases it created (registry/<db_id>.json — a project's .guardian/guardian.db is trusted only when its id is registered there for that very path: created by dev-guardian there, or registered by you with node "<plugin root>/cli/dev-guardian.mjs" db adopt --project <dir> --yes — one from 3.0.0 is not, until you do), and the database a project uses when its own cannot be used — foreign, not writable, tracked by git, or holding schema objects the migrations never create — as <dir>/<hash of the project path>/guardian.db. Default: %LOCALAPPDATA%\dev-guardian on Windows, $XDG_DATA_HOME/dev-guardian or ~/.local/share/dev-guardian elsewhere — one resolution, which the hooks share. Its directories are created 0700 and, on POSIX, must belong to you; a symbolic link or another user's directory there is refused. When it cannot be used at all (it cannot be created — a container user with no home, HOME=/ — or it is refused), the server does not exit: the session runs on an in-memory database, and health_status and every scan say history will not persist until this points at a writable directory. The hooks read it to find that registry/, which an assistant may not write — with the Write tool or from the shell (see hooks.md); set it in the shell that launches Claude Code as well as in the server's env, or the hooks guard the default directory. |
GUARDIAN_HOOKS |
hooks | unset | off disables every hook. The user-level ~/.config/dev-guardian/hooks.json with "enabled": false does the same; a project's .guardian/hooks.config.json cannot (its enabled: false is ignored). |
GUARDIAN_HOOKS_BASH_BLOCK |
hooks | unset | 0 / false: catastrophic commands are only warned about; 1 / true: always blocked. Wins over both config files. A project's .guardian/hooks.config.json cannot switch the block off — see hooks.md. |
GUARDIAN_HOOKS_DEBUG |
hooks | unset | 1 writes the hooks' diagnostics to stderr (they are silent and fail open otherwise). |
GUARDIAN_PKG_VET |
hooks | unset | 0 turns off install-time package vetting in the PreToolUse hook. No project file can do it — not even with "enabled": false; the user-level config's "enabled": false turns it off along with every other hook. The vet_packages tool is unaffected. |
GUARDIAN_PKG_VET_DEADLINE_MS |
hooks | 8000 |
The one deadline for install-time package vetting in a PreToolUse hook call, in ms from the start of the call (at least 3000, the network budget — a smaller value left no time to look a package up, and a malicious one went through with a "not verified" note; at most 12000: Claude Code kills a hook at 15 s, and the command then runs with no verdict — the answer and the hook's exit need the rest). The 3 s network budget is cut to it; what is left when it passes reads "vetting time budget exhausted" — not verified, never ok. |
| Variable | Where | Purpose |
|---|---|---|
GUARDIAN_SCAN_ID |
environment of the scripts a scan runs | the id of the scan row the run belongs to |
GUARDIAN_PROC_TREE_ID |
every child process on Windows | a per-run token the runner uses to find and kill the whole process tree, including MSYS descendants taskkill cannot see |
GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n>, GIT_CONFIG_VALUE_<n> |
every process dev-guardian starts — git, the scanners, the package managers, the scripts — except the test command create_fix_pr runs and the DAST application |
git configuration overrides that keep a scanned repository's own git configuration and hooks from running anything (SECURITY.md). Appended after the entries your own environment sets, which are kept. |
GIT_PAGER, GIT_EDITOR, GIT_SEQUENCE_EDITOR |
the same processes | cat, :, : — git's own "no pager" and "no editor". |
GIT_PROXY_COMMAND, GIT_NO_LAZY_FETCH |
the same processes, only in a repository that sets core.gitProxy or a remote.<name>.uploadpack |
empty (no proxy command — unless you set one, which is kept) and 1 (no lazy fetch). |
Read only by the test suite, npm run ablate and npm run eval:llm-scan; nothing at runtime looks at them.
| Variable | Read by | Effect |
|---|---|---|
GUARDIAN_REQUIRE_COSIGN |
test suite | 1 turns a missing (or pre-3.0) cosign in test/e2e/cosignRegistry.test.ts — the real cosign against a registry that fails on purpose — from a visible skip into a hard failure. With 1, the verify cases' need for Sigstore's TUF trust root (cosign initialize, network) is a hard requirement too: it fails rather than skips them. |
GUARDIAN_REQUIRE_SEMGREP |
test suite | 1 turns a missing Semgrep (or Trivy, for the Trivy e2e) from a visible skip into a hard failure — set it when you need to know a rule pack was actually exercised. A Semgrep that is on PATH but does not answer semgrep --version fails the rule-pack tests with or without it: only absence is a skip. |
GUARDIAN_REQUIRE_LINTERS |
test suite | 1 turns a missing actionlint or zizmor — the real linters test/e2e/ciInitCli.test.ts runs over the rendered GitHub workflow — from a visible skip into a hard failure. |
GUARDIAN_REQUIRE_ISOLATED_HOME |
test suite | 1 fails the run when ~/.semgrep/semgrep.log, ~/.semgrep/settings.yml or ~/.cache/semgrep_version changed during it. The suite points Semgrep's settings, log and version cache at its own temporary directory (test/setup/semgrepHome.ts); without the variable a change is printed as a warning, because a Semgrep run outside the suite (by you, or by an open session's MCP server) writes the same files. |
GUARDIAN_PERF_STRICT |
test suite, through test/helpers/timing.ts |
1 is for a quiet machine. It runs the absolute time bounds of test/unit/hooks/bashGuard.test.ts, secretScan.test.ts, test/unit/pkgvet/parseCommand.test.ts and test/unit/secrets/verify/rawReport.test.ts — each its own test, a visible skip without it — tightens the wait bounds of the tests whose defect is a wait (a FIFO, /dev/zero, a network path, a budget ignored: hangBound in test/helpers/timing.ts, 12 s without it), and tightens the bounds that always run in test/unit/runners/processRunner.test.ts, test/e2e/hooksDispatcher.test.ts, test/e2e/hooksPkgVet.test.ts and test/integration/auditMcpToolsBounds.test.ts, which without it sit under what the property allows (the hook's 15 s kill, for the hooks) and far under the defect each was written for. What the timing tests exist for is asserted without it: a ratio — eight times the input must cost under 22.6 times as much — and, where a 16 KB cap bounds the cost, a ceiling against a benign input of the same size. Both are measured in alternated chunks of equal duration, each side's cheapest chunk per call, and a reading over the bound is measured again, up to three comparisons: a loaded machine only adds time, and in bursts, while each defect reintroduced reads over its bound every time. That is what a busy runner and v8 coverage move least — not "not at all": every probe's distribution over 20 runs at 100% CPU is in the review 3.0 R7 round-2 commit. |
GUARDIAN_SEMGREP |
npm run ablate |
Semgrep binary to use (after --semgrep=, before PATH). |
GUARDIAN_RUST_SRC, GUARDIAN_CS_SRC, GUARDIAN_JAVA_SRC, GUARDIAN_PY_SRC, GUARDIAN_GO_SRC, GUARDIAN_PHP_SRC |
npm run ablate |
Real-code corpus for axis 3 of the bugfix-rs, -cs, -java, -py, -go and -php packs. Unset: axis 3 prints N/A. Set to a path that does not exist: the run throws. |
GUARDIAN_RGPD_SRC |
npm run ablate |
Axis-3 corpus for the rgpd pack; unset falls back to mcp/src. |
GUARDIAN_LLM_SRC |
npm run ablate |
Axis-3 corpus for the llm pack: a tree of real LLM-application code, Python and JS/TS (the pack header names the one it was measured on). Unset: axis 3 prints N/A. Set to a path that does not exist: the run throws. |
GUARDIAN_WEBJS_SRC |
npm run ablate |
Axis-3 corpus for the web-js pack: a tree of real Node application code, JS/TS, with raw SQL and Express handlers (the pack header names the one it was measured on). Unset: axis 3 prints N/A. Set to a path that does not exist: the run throws. |
GUARDIAN_LLMSCAN_SPIKE, GUARDIAN_VAMPI_SRC, GUARDIAN_BENCHMARK_PY_SRC, GUARDIAN_JUICESHOP_SRC, GUARDIAN_DVWA_SRC |
npm run eval:llm-scan |
Corpora of the LLM-scan evals: the spike folder (the synthetic app-s and its key), VAmPI at f16052d, OWASP BenchmarkPython at f129148, Juice Shop at 1618a61 and DVWA at 43b0f8b. None is copied into the repository; a run copies each one, blinded, into a temp directory. Unset: that corpus's items print N/A and the run reports itself incomplete. Set to a path that does not exist, or to a checkout at another commit: the run throws. |
GUARDIAN_CI_INIT_PIN_SHA |
cli/dev-guardian.mjs ci-init |
Test seam: a 40-hex commit used instead of resolving the release tag. Production never sets it. |
GUARDIAN_TEST_* |
individual tests | Test-internal switches (live network e2e, fetch mocks, raw temp paths, the run's own directory, GUARDIAN_TEST_TIMING_LOG=<file> to record every timing measurement as a JSON line). Not for users. |
The suite itself runs with GUARDIAN_OFFLINE=1 (mcp/vitest.config.ts); a test that exercises a network path clears it and supplies its own mocked fetch.
| Variable | Read by | Effect |
|---|---|---|
WORDFENCE_API_KEY |
wp_vuln_check (mode: "source") |
Wordfence Intelligence v3 token. Without it the Wordfence pass is skipped and coverage is partial; the wordpress.org checks still run. |
WPSCAN_API_TOKEN |
wp_vuln_check |
WPScan API token when api_token is not passed; without one the public rate limit applies. |
the variable named by auth_header_env |
scan_dast |
The Authorization header for authenticated probes, so the credential never enters the transcript. |
XDG_CACHE_HOME, LOCALAPPDATA |
wp_vuln_check (the Wordfence feed, and a live-mode run's report) |
Base of the default cache directory (see GUARDIAN_CACHE_DIR). |
CLAUDE_CONFIG_DIR |
audit_agent_config, audit_mcp_tools (with include_user_config), hooks |
Where Claude Code keeps its global .claude.json and settings.json, read from there instead of ~/.claude.json and ~/.claude/settings.json, as Claude Code does. The hooks guard settings.json / settings.local.json there like ~/.claude/settings*.json. |
CLAUDE_PROJECT_DIR |
hooks | Set by Claude Code for every hook: the project whose .guardian/ configuration applies, wherever the session has cd-ed to (without it, the nearest ancestor holding .guardian or .git, never the home directory, its ancestors or the temp directory). |
NO_COLOR |
dev-guardian status |
Disables colour in the terminal summary. |
SEMGREP_SEND_METRICS |
scan_sast, security_scan_full, the CLI's scan (Semgrep reads it) |
off makes Semgrep refuse its registry rules (--config=auto, used unless local-only): the Semgrep run fails, and its reason says this variable is why — unset it for the scan, or scan local-only. dev-guardian never turns metrics back on; a local-only scan passes --metrics=off itself. |
GIT_CONFIG_COUNT |
every git dev-guardian starts | Your own configuration entries: dev-guardian's overrides are appended after them. A value git would refuse ("bogus count") makes dev-guardian refuse to run git or any process in a repository, naming it. |
GIT_SSH |
create_fix_pr's push |
Used, quoted, as core.sshCommand when the repository sets its own core.sshCommand (which is never used) and your configuration sets none. |
https_proxy, HTTPS_PROXY, http_proxy, all_proxy, ALL_PROXY |
every git dev-guardian starts | When the repository sets its own http.proxy, http.<url>.proxy or remote.<name>.proxy (never used) and your configuration sets none: the proxy git uses instead — as git would have read these variables without the repository's setting — or none. |
GIT_CONFIG |
every git dev-guardian starts | Removed from the one git config read of the repository's configuration: it would make that read, and only that one, look at another file. |
npm_config_*registry*, NPM_CONFIG_REGISTRY, NPM_CONFIG_USERCONFIG, NPM_CONFIG_GLOBALCONFIG, NPM_CONFIG_PREFIX, YARN_NPM_REGISTRY_SERVER, YARN_REGISTRY, BUN_CONFIG_REGISTRY, PIP_*INDEX*, PIP_FIND_LINKS, PIP_NO_INDEX, PIP_CONFIG_FILE, VIRTUAL_ENV, UV_*INDEX*, UV_FIND_LINKS, UV_CONFIG_FILE, CONDA_PREFIX, COMPOSER_HOME, NUGET_*SOURCE*, NUGET_*FEED*, NUGET_*CONFIG*, NuGetPackageSourceCredentials_*, ProgramFiles(x86) |
vet_packages, the install hook |
Evidence of a private registry: a name missing from the public registry is then unknown, never denied. See hooks.md. |