Skip to main content

AI coding security co-pilot — blocks insecure code at generation time

Project description

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

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


Development

git clone http://your-gitea/fwss/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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vigilsec-0.1.1.tar.gz (40.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vigilsec-0.1.1-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

File details

Details for the file vigilsec-0.1.1.tar.gz.

File metadata

  • Download URL: vigilsec-0.1.1.tar.gz
  • Upload date:
  • Size: 40.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for vigilsec-0.1.1.tar.gz
Algorithm Hash digest
SHA256 24a7b5a8c57f157689dc2aed688b0e7e5046d347cf3504b9108bdd213a6b05c6
MD5 65cd84a42e59924ea5406c1cac243013
BLAKE2b-256 c431aa17e642a0595f4801376e47d647180f8b80049b8fdcd366f41fe126f37f

See more details on using hashes here.

File details

Details for the file vigilsec-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: vigilsec-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 34.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for vigilsec-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cb933309358b1f314329d417d735736881de7b4a19d9e26cfb84d93602bfcfd8
MD5 ca70c3148dd6ee14954360be38ca9d2c
BLAKE2b-256 25bf35375e40372dfeeea39331cba4112f1f690216befb5f7533aecd5bf7722f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page