DriftGuard catches missing environment variables and broken AI output contracts before merge.
See the engineering case study for architecture, validation evidence, scope boundaries, and a resume-ready project summary.
cargo install --git https://github.com/sidsri14/driftguard --locked && driftguard init && driftguard doctor && driftguard checkAfter the package is published to crates.io, the install command becomes:
cargo install driftguard-cli --lockedFor development from this repository:
cargo run -- init
cargo run -- checkdriftguard init
driftguard check
driftguard check --since origin/main
driftguard check --since origin/main --env-scope changed
driftguard check --staged --env-scope changed
driftguard check --since origin/main --markdown
driftguard check --json
driftguard doctor
driftguard doctor --json
driftguard install-hookdriftguard init creates:
env_files = [".env.example"]
deployment_manifests = [".env.production", "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", "k8s/**/*.yaml", "k8s/**/*.yml", "kubernetes/**/*.yaml", "kubernetes/**/*.yml", "vercel.json", "netlify.toml"]
ignore_dirs = [".git", "node_modules", "target", "dist", "build", ".next", ".venv", "__pycache__"]
source_globs = ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.mjs", "**/*.cjs", "**/*.py", "**/*.rs"]
ignore_env_keys = []Add prompt contracts as needed:
[prompts.router]
files = ["src/prompts/router.md"]
schema = "schemas/router.schema.json"
golden = "tests/golden/router/*.json"driftguard init starts with no prompt mappings. Add a contract block like the
router example when the project has a prompt output schema. Prompt contracts
are active only when their mapped prompt files exist.
Use --env-scope changed with --since when you only want environment checks
against changed source files. The default --env-scope all scans the configured
source globs across the repository.
Use --staged --env-scope changed for pre-commit checks against files staged in
the Git index. driftguard install-hook installs this staged-file check by
default.
deployment_manifests adds optional declaration sources from deployment files
when they exist. DriftGuard recognizes common Docker Compose and Kubernetes YAML
environment declarations, Vercel env objects, Netlify environment tables, and
.env.production style files.
Ignore a known non-deployment key across the project:
ignore_env_keys = ["LOCAL_DEV_ONLY"]Suppress a single source line or the next line when generated or dynamic code cannot be represented in an env manifest:
const local = process.env.LOCAL_DEV_ONLY; // driftguard-ignore
// driftguard-ignore-next-line
const generated = process.env.GENERATED_KEY;Keep suppressions narrow and reviewable. Commented-out code is ignored automatically for supported JS/TS, Rust, and Python files.
driftguard check --json writes a versioned JSON document to stdout and keeps
the same exit codes as terminal output:
0: contracts passed1: drift was detected2: DriftGuard could not execute the check
The top-level format_version is currently 1. CI integrations should check
that value before consuming environment or prompts failures.
Execution failures use verdict: "error" and include an error string, so
stdout remains valid JSON for exit code 2.
- JS/TS:
process.env.KEY,process.env?.KEY,process.env!.KEY, andprocess.env["KEY"] - JS/TS destructuring:
const { KEY } = process.env - Vite/Deno/Bun:
import.meta.env.KEY,Deno.env.get("KEY"), andBun.env.KEY - Python:
os.getenv("KEY"),os.environ["KEY"], andos.environ.get("KEY") - Rust:
std::env::var("KEY"),std::env::var_os("KEY"),env!("KEY"), andoption_env!("KEY") - Dynamic env access such as
process.env[key],os.environ.get(name), orstd::env::var(name)is flagged because DriftGuard cannot verify the declaration statically - JS/TS/Rust line and block comments are ignored during env scanning
- Python line comments are ignored during env scanning
- Missing keys in configured env manifests
- Prompt golden fixtures that violate configured JSON schemas
- Prompt template variables like
{{user_payload}}missing from golden fixture inputs - Changed prompt markdown files without mapped contracts when
--sinceis used - Deleted mapped prompt files when
--sinceor--stagedis used - Project-wide and line-level env scan suppressions
- Configuration health through
driftguard doctor
Simple golden fixtures can be the expected output JSON directly:
{
"destination": "support"
}For templated prompts, use an input object plus an output object. DriftGuard
checks that every {{variable}} in the prompt has a matching input key, then
validates output against the configured JSON Schema:
{
"input": {
"user_payload": "I need help with billing"
},
"output": {
"destination": "support"
}
}The included .github/workflows/driftguard.yml validates this repository by
installing the local crate with cargo install --path .. After DriftGuard is
published, consumer repositories can replace that install step with
cargo install driftguard-cli --locked.
When using driftguard check --since origin/main, keep fetch-depth: 0 on
actions/checkout@v4. DriftGuard needs the base branch ref available locally to
compute changed prompt files.
The repo also includes:
.github/workflows/driftguard.ymlfor normal CI validation.github/workflows/driftguard-pr-comment.ymlfor posting/updating PR comments.github/workflows/quality.ymlfor Linux, macOS, and Windows tests.github/workflows/release.ymlfor tag-based release binaries
See examples/broken-ai-app for a compact app that demonstrates:
- missing environment variable detection
- prompt template input coverage
- prompt output JSON Schema validation
Run the complete pass/fail demo from the repository root:
.\scripts\demo.ps1./scripts/demo.shPlay the recorded 30-second terminal session with asciinema:
asciinema play docs/demo.castRun the scanner benchmark locally:
cargo bench --bench env_scanThe benchmark generates 500 TypeScript files and measures the same scanner used by the CLI. Performance depends on the machine and filesystem, so DriftGuard does not claim a fixed scan time.
DriftGuard validates deterministic contracts. It does not predict LLM quality, execute prompts against model providers, inspect secret values, or replace a language-specific compiler or security scanner.