Skip to content

sidsri14/driftguard

Repository files navigation

DriftGuard

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.

30-second quickstart

cargo install --git https://github.com/sidsri14/driftguard --locked && driftguard init && driftguard doctor && driftguard check

After the package is published to crates.io, the install command becomes:

cargo install driftguard-cli --locked

For development from this repository:

cargo run -- init
cargo run -- check

Commands

driftguard init
driftguard check
driftguard check --since origin/main
driftguard check --since origin/main --env-scope changed
driftguard check --since origin/main --markdown
driftguard check --json
driftguard doctor
driftguard doctor --json
driftguard install-hook

Configuration

driftguard init creates:

env_files = [".env.example"]
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.

False-positive controls

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.

Machine-readable reports

driftguard check --json writes a versioned JSON document to stdout and keeps the same exit codes as terminal output:

  • 0: contracts passed
  • 1: drift was detected
  • 2: 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.

What v0.3 checks

  • JS/TS: process.env.KEY and process.env["KEY"]
  • JS/TS destructuring: const { KEY } = process.env
  • Python: os.getenv("KEY") and os.environ["KEY"]
  • Rust: std::env::var("KEY") and env!("KEY")
  • 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 --since is used
  • Project-wide and line-level env scan suppressions
  • Configuration health through driftguard doctor

Golden fixtures

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"
  }
}

GitHub Actions

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.yml for normal CI validation
  • .github/workflows/driftguard-pr-comment.yml for posting/updating PR comments
  • .github/workflows/quality.yml for Linux, macOS, and Windows tests
  • .github/workflows/release.yml for tag-based release binaries

Examples

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.sh

Play the recorded 30-second terminal session with asciinema:

asciinema play docs/demo.cast

Benchmarks

Run the scanner benchmark locally:

cargo bench --bench env_scan

The 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.

Scope

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.

About

Local-first Rust CI guard for environment drift and AI prompt output contracts.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages