A CLI tool that uses an LLM to check your source code against a curated set of security rules and reports pass/fail per rule with details for any failures.
codeguard check— check files you've changed in your git repo before committing.codeguard scan PATH— scan every source file in a folder. Useful for auditing an existing codebase or checking files outside of git.codeguard recheck RESULTS_JSON— re-run a previous analysis from its saved JSON output, skipping files that haven't changed. Useful for iterating on fixes without re-checking the whole codebase.
codeguard rules is a configuration command for managing which version of the security rules is in use.
The security rules come from Project CodeGuard, a separate open-source project whose primary goal is to guide AI coding tools to write secure code in the first place. This CLI takes a different angle — it checks code that already exists, running it through those same rules after the fact.
- Works on any codebase, not just AI-generated code
- Deterministic pass/fail per rule, with details on what failed and why
- Low token cost — only checks files you explicitly target, and can skip unchanged files on recheck
- Optionally outputs JSON record file for auditing / escalation
This project is 99% written by AI, and currently fails its own checks. In other words, its not secure and should never be deployed. Use entirely at your own risk.
New to Python virtual environments? See the Python packaging guide.
python3 -m venv venv && source venv/bin/activate
pip install -e .To run codeguard from anywhere, add an alias to your ~/.zshrc:
alias codeguard='/path/to/venv/bin/codeguard'Create a .codeguard.yaml in your project root (or copy the included example):
llm:
provider: anthropic # anthropic | openai | google
model: claude-haiku-4-5
api_key: sk-ant-xxx # or use api_key_env: ANTHROPIC_API_KEY
# Optional: override the upstream repository used by `codeguard rules`
# Defaults to the public project-codeguard repo if omitted.
rules:
upstream_owner: cosai-oasis
upstream_repo: project-codeguardCodeguard searches for .codeguard.yaml from the current directory upward, so one file at the project root covers all subdirectories. The file is in .gitignore by default — use api_key_env for CI/CD.
All commands support --help to see available options. The four commands are:
check— analyze changed files in the current git reposcan PATH— analyze all source files in a folderrecheck RESULTS_JSON— re-run a previous analysis, skipping unchanged filesrules— manage which version of the security rules is in use
Checks files changed in the current git repo (staged, unstaged, and untracked by default).
codeguard check # all changed files
codeguard check --staged # staged only
codeguard check --unstaged # unstaged + untracked only
codeguard check --format json # output as JSON
codeguard check --silent --format json # no prompts or progress (CI/CD)--staged and --unstaged are mutually exclusive. In check mode the LLM sees both the full file and the diff — failures caused by a recent change are flagged as [introduced by recent change].
Scans every source file in a folder, regardless of git state. Recursive by default. Common build and dependency directories are skipped automatically (.git, node_modules, __pycache__, venv, dist, etc.).
codeguard scan . # scan current directory recursively
codeguard scan ./src --no-recursive # top-level files only
codeguard scan . --format json # output as JSON
codeguard scan . --silent --format json # no prompts or progress (CI/CD)Re-runs analysis from a JSON file previously produced by check, scan, or recheck. Files whose content hash (SHA256) matches the stored hash are skipped — only changed files are sent to the LLM. Output is in the same JSON format, so results can be chained (recheck → recheck).
codeguard recheck results.json # skip unchanged files
codeguard recheck results.json --force # re-check all files regardless of hash
codeguard recheck results.json --silent # no progress output
codeguard recheck results.json --format json > out.json # output as JSONUse --force when you want fresh LLM analysis on all files even though the source hasn't changed — for example, after switching to a different model or LLM provider, or after updating the rules version.
File paths are resolved using the base_path recorded in the JSON, then the JSON file's directory, then the current working directory. The currently active rules version is used, not the version from the original run.
Downloads and manages versions of the security rules from the upstream project-codeguard repository. The CLI ships with bundled rules; run download to get the latest.
Rules are cached at:
- macOS:
~/Library/Caches/codeguard/rules/ - Linux:
~/.cache/codeguard/rules/ - Windows:
%LOCALAPPDATA%/codeguard/rules/
codeguard rules list # show available versions and which is active
codeguard rules download # download latest (main branch)
codeguard rules download v1.3.1 # download a specific tagged release
codeguard rules use v1.3.1 # switch to an already-downloaded version
codeguard rules clear # remove all cached rules and metadatalist output shows each version's rules date, whether it's cached, and whether main has changed since you last downloaded it:
Available versions:
main (latest — updated 2026-04-30) ← current | no changes since last download
v1.3.1 (2026-01-29) cached
v1.3.0 (2026-01-29)
download main skips the download if no rules have changed since the last fetch. use switches versions without re-downloading.
All commands support two output formats via --format text (default) or --format json. Progress and prompts always go to stderr regardless of format; the final result goes to stdout.
Before any LLM calls, codeguard prints a pre-check summary to stderr showing the active rules version, config file path, every file to be checked with its detected language, and the full list of rules to be applied (tier-1 always-apply and tier-0 language-specific). A confirmation prompt follows. Use --silent to skip all of this for CI/CD.
Rules: main (rules date: 2026-04-30)
============================================================
PRE-CHECK SUMMARY
============================================================
Config file: .codeguard.yaml
Files to check (14):
✓ codeguard/cli.py [python]
✓ codeguard/config.py [python]
...
Rules to apply (23):
Always-apply (tier-1): 3
- codeguard-1-crypto-algorithms
- codeguard-1-digital-certificates
- codeguard-1-hardcoded-credentials
Language-specific (tier-0):
python: 10 rule(s)
- codeguard-0-input-validation-injection
...
============================================================
Proceed with analysis? [Y/n]:
Each file is checked inline as results come in:
[1/14] Checking codeguard/cli.py [python]... 13/13 passed ✓
[2/14] Checking codeguard/config.py [python]... 12/13 passed ❌
❌ codeguard-1-hardcoded-credentials
Hardcoded credentials detected. Line 41: ...
In check mode, failures caused by a recent diff hunk are flagged as [introduced by recent change].
The final summary is written to stdout:
SUMMARY
============================================================
Rules version: main rules date: 2026-04-30
Total: 177/182 checks passed
Files with failures: 4/14
A single JSON object written to stdout, suitable for piping to a file, parsing by scripts, or feeding into codeguard recheck.
- Python 3.8+
- Git repository (only for
codeguard check;codeguard scanworks on any folder) .codeguard.yamlconfig file with LLM provider settings- API key for at least one of: Anthropic Claude, OpenAI GPT, or Google Gemini
Source code changes are reflected immediately (editable install). Re-run pip install -e . only if pyproject.toml or requirements.txt have changed.
{ "schema_version": "1.0", // format version; recheck validates this before processing "timestamp": "2026-05-06T03:02:44.660971+00:00", // UTC ISO-8601 run time "base_path": "/abs/path/to/root", // absolute path used to resolve filenames; stored for recheck "parameters": { // exact CLI flags used for this run "command": "check", // "check" | "scan" | "recheck" "staged": false, "unstaged": false, "silent": false, "format": "json" }, "llm": { "provider": "anthropic", // LLM provider used for this run "model": "claude-haiku-4-5" // model used for this run }, "rules": { "version": "main", // active rules version: "main", "v1.3.1", or "bundled" "rules_date": "2026-04-30", // date of most recent change to the active rules "always_apply": [ // tier-1 rule IDs — applied to every file "codeguard-1-crypto-algorithms", "codeguard-1-digital-certificates", "codeguard-1-hardcoded-credentials" ], "language_specific": { // tier-0 rule IDs — keyed by detected language "python": [ "codeguard-0-input-validation-injection", "..." ] } }, "files": { "checked": [ // files that were sent to the LLM { "path": "codeguard/cli.py", // relative to base_path "language": "python", // detected language (null if unknown) "file_hash": "72726c4d..." // SHA256 hex of file content at analysis time } ], "ignored": [ // files skipped (non-source-code) "LICENSE" ] }, "results": [ // one entry per checked file { "file": "codeguard/cli.py", "language": "python", "file_hash": "72726c4d...", // SHA256 hex; used by recheck to detect changes "passed": 13, // number of rules that passed "failed": 0, // number of rules that failed "rules_checked": [ // all rule IDs evaluated for this file "codeguard-0-input-validation-injection", "codeguard-1-hardcoded-credentials", "..." ], "failures": [ // one entry per failed rule (empty array if all passed) { "rule_id": "codeguard-0-input-validation-injection", "details": "...", // LLM explanation of the failure "related_to_change": true // true if the failure is in a diff hunk (check mode only) } ] } ], // "recheck" block is only present in output produced by `codeguard recheck` "recheck": { "source_file": "/abs/path/to/results.json", // input file used "source_timestamp": "2026-05-06T03:09:51+00:00", // timestamp from the source file "source_rules_version": "main", // rules version from the source file "files_rechecked": 1, // files whose hash changed (or --force) — LLM was called "files_skipped_unchanged": 3, // files whose hash matched — LLM was NOT called "files_missing": 0, // files in source JSON that could not be found on disk "force": false // true if --force was passed } }