Vigil
AI coding security co-pilot — blocks insecure code at the moment of generation.
Vigil intercepts every file an AI coding assistant writes and blocks it if CRITICAL or HIGH security findings are detected — before the file hits disk. It's the only tool that operates at generation time rather than post-commit.
AI writes file → vigil scan → exit 2 → Claude Code blocks the write
The Problem
AI coding assistants reproduce the most common patterns in their training data. The most common patterns are insecure defaults.
The clearest example: every existing IaC scanner (Checkov, Trivy, Snyk, Semgrep) misses the docker-compose port binding that exposes your database to the internet:
ports:
- "5432:5432" # ← binds to 0.0.0.0, bypasses UFW, reachable from anywhere
The correct form is "127.0.0.1:5432:5432". Vigil catches it. Nothing else does.
Install
pip install vigilsec
Wire the Claude Code hook (one time):
vigil init --global
That's it. Every file Claude Code writes is now scanned before it saves. Reload Claude Code to activate.
Usage
# Scan a single file
vigil scan docker-compose.yml
# Scan a directory
vigil scan ./my-project/
# JSON output (for CI / dashboards)
vigil scan ./my-project/ --format json
# SARIF output (for GitHub Advanced Security)
vigil scan ./my-project/ --format sarif > results.sarif
# Only report HIGH and above
vigil scan ./my-project/ --severity HIGH
# Open feedback & waitlist form
vigil feedback
Exit codes:
| Code | Meaning |
|---|---|
0 |
No findings — write proceeds |
1 |
Advisory findings only (MEDIUM / LOW / INFO) |
2 |
CRITICAL or HIGH found — Claude Code blocks the write |
Rules
36 rules across 9 categories. All built-in, stdlib-only, zero runtime dependencies.
Secrets & Injection (10 rules)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-S001 | CRITICAL | Hardcoded AWS / cloud API keys |
| VGL-S002 | CRITICAL | Hardcoded passwords (password =, passwd =) |
| VGL-S003 | HIGH | Generic API key / token assignments |
| VGL-S004 | HIGH | Generic secret / credential assignments |
| VGL-S005 | CRITICAL | JWT signing secrets |
| VGL-S006 | CRITICAL | PEM private keys |
| VGL-S007 | CRITICAL | Credential-embedded database URLs (postgres://user:pass@host) |
| VGL-S008 | CRITICAL | Stripe live keys (sk_live_...) |
| VGL-S009 | CRITICAL | Slack tokens (xoxb-, xoxp-) |
| VGL-S010 | CRITICAL | OpenAI, GitHub, GitLab, Google provider keys |
| VGL-I001 | CRITICAL | eval() with variable input |
| VGL-I002 | HIGH | subprocess(shell=True) with variable input |
| VGL-I003 | HIGH | os.system() with variable input |
Docker IaC (2 rules)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-D001 | CRITICAL | "PORT:PORT" binding — bypasses UFW, exposes to internet |
| VGL-D002 | HIGH | Hardcoded secrets in environment: blocks |
Dockerfile Hardening (3 rules)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-DF001 | HIGH | Container running as root (no USER directive) |
| VGL-DF002 | MEDIUM | Unpinned :latest base image |
| VGL-DF003 | CRITICAL | Secrets baked into image layers via ENV/ARG |
nginx (1 rule)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-N001 | HIGH | Missing security headers, server_tokens on, deprecated TLS |
Kubernetes (1 rule)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-K001 | CRITICAL/HIGH | privileged: true, hostNetwork/hostPID/hostIPC: true |
IAM Policies (1 rule)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-IAM001 | CRITICAL/HIGH | "Action": "*" and "Resource": "*" wildcards |
AI Agent Patterns (7 rules)
New category — catches the security anti-patterns unique to AI-generated agentic code.
| Rule | Severity | What it catches |
|---|---|---|
| VGL-A001 | CRITICAL | LLM output piped to subprocess.run() / os.system() |
| VGL-A002 | HIGH | Hardcoded auto_approve = True / skip_confirmation = True |
| VGL-A003 | HIGH | Unbounded while True loop making LLM calls with no iteration cap |
| VGL-A004 | HIGH | LLM response content written directly to filesystem |
| VGL-PI001 | CRITICAL | User input embedded in system prompt |
| VGL-PI002 | HIGH | Raw request.body passed as LLM message content |
| VGL-PI003 | HIGH | str.format() on system_prompt variables with user-controlled data |
| VGL-PI004 | MEDIUM | Unsanitized tool output appended to conversation |
MCP Server Security (3 rules)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-MCP001 | CRITICAL | Injection strings in tool descriptions (ignore previous instructions) |
| VGL-MCP002 | HIGH | Dynamic tool descriptions built from user-controlled data |
| VGL-MCP003 | HIGH | Shell execution inside MCP handlers without a sandbox |
Shell Scripts (1 rule)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-S011 | HIGH | Secret variable passed inline to subprocess or SSH command — visible in ps aux on both machines |
Dependency CVEs (2 rules)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-DEP001 | HIGH | Python CVEs via pip-audit (runs on every requirements.txt change) |
| VGL-DEP002 | HIGH | npm CVEs via npm audit (runs on every package.json change) |
Trivy IaC Deep Scan (1 rule)
| Rule | Severity | What it catches |
|---|---|---|
| VGL-T001 | HIGH | Dockerfile and Terraform misconfigurations via Trivy |
Configuration
Place a .vigilrc file in your project root (or any ancestor directory):
# .vigilrc
disabled_rules = ["VGL-T001"] # skip trivy scan for this project
min_severity = "HIGH" # only report HIGH and above
exclude_paths = ["vendor", "legacy"]
telemetry = false # opt out of anonymous local telemetry
Vigil walks up the directory tree to find the nearest .vigilrc. Child config always wins over parent. Monorepos can have per-project overrides alongside a workspace default.
Inline suppression — for a specific line you've reviewed and accepted:
auto_approve = True # vigil: ignore
Same pattern as # noqa (flake8) and # nosec (bandit).
Opt-out
Vigil collects anonymous, local-only telemetry: rule ID, severity, and file extension. No file paths, no code, no identifiable data. Stored at ~/.vigil/events.jsonl — never sent anywhere.
Opt out permanently:
export VIGIL_NO_TELEMETRY=1
Or in .vigilrc:
telemetry = false
Adding a Rule
# src/vigil/rules/my_category.py
from pathlib import Path
from .base import Finding, Rule, Severity
class MyRule(Rule):
id = "VGL-X001"
name = "Descriptive rule name"
severity = Severity.HIGH
def applies_to(self, path: Path) -> bool:
return path.suffix == ".yml"
def check(self, path: Path) -> list[Finding]:
findings = []
for i, line in enumerate(path.read_text().splitlines(), 1):
if "bad_pattern" in line:
findings.append(Finding(
rule_id=self.id,
severity=self.severity,
message="Found bad pattern",
file_path=path,
line=i,
snippet=line.strip(),
fix="Do this instead.",
))
return findings
Then add it to DEFAULT_RULES in src/vigil/rules/__init__.py. Write tests. Done.
GitHub Actions
Add Vigil to any CI pipeline — copy vigil-action/workflow-template.yml into your project's .github/workflows/vigil.yml:
- name: Install Vigil
run: pip install vigilsec --quiet
- name: Scan with Vigil
run: vigil scan . --no-color
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: vigil-results.sarif
Findings appear as inline annotations on PR diffs in the GitHub Security tab.
Development
git clone https://github.com/vigilsec-io/vigil.git
cd vigil
python3 -m venv venv && source venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v
License
Business Source License 1.1 — free for non-commercial use. Commercial use requires a license agreement. Converts to MIT on 2030-06-26.
Feedback & Waitlist
Found a false positive? Want a rule that doesn't exist yet? Building with AI agents and hitting patterns Vigil should catch?
Join the waitlist → thefwss.com/vigil
Or: vigil feedback
Metadata
Release files for vigilsec 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vigilsec-0.2.1.tar.gz | 99.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vigilsec-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 180.5 kB
Release files / vigilsec-0.2.1.tar.gz
| Download URL | vigilsec-0.2.1.tar.gz |
|---|---|
| Size | 99.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
edcf662ed981ff486a234e329f90b173ae190623e8faa25ea37436b2cdc4103c
|
|
BLAKE2b-256 checksum How to use checksums |
2a92f126c4e7f172014a098fa7a2670f869ed85b1d79a836a3bab3aa985202b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|
Release files / vigilsec-0.2.1-py3-none-any.whl
| Download URL | vigilsec-0.2.1-py3-none-any.whl |
|---|---|
| Size | 81.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f5fe6263f58960054ffbe3fad4f3ac0ac2ac058c9b5991cfe60ddd75baf5c79a
|
|
BLAKE2b-256 checksum How to use checksums |
e522da33bd222e270b1404c1df9ce5e8a735f05e77a58c78d857cd63e1acb99e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|