Skip to main content

Valca

AI coding security co-pilot — blocks insecure code at the moment of generation.

Formerly published as vigilsec. The package is now valca. Both the valca and vigil commands work, so existing hooks and scripts keep running unchanged.

Valca 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 valca

Wire the Claude Code hook (one time):

valca init --global     # `vigil init --global` also works

That's it. Every file Claude Code writes is now scanned before it saves. Reload Claude Code to activate.


Network access

Valca's core scanning is fully offline — the package has zero runtime dependencies and the engine never sends your code, file paths, or findings anywhere.

Three rules do reach the network, because checking whether a dependency is vulnerable or fabricated is impossible offline. When a manifest (requirements.txt, package.json, lockfiles) is scanned, these rules send package names and version strings only to:

Host Used by What is sent
api.osv.dev VGL-PKG001 (known CVE in a pinned version) package name + version
pypi.org VGL-PKG002/003/004 (hallucinated, stale, or suspicious package) package name
registry.npmjs.org VGL-PKG002/003/004 package name

Your source code, file contents, file paths, and scan results are never transmitted. If your dependency inventory is itself sensitive, turn these rules off in .vigilrc and Valca runs completely offline:

disabled_rules = ["VGL-PKG001", "VGL-PKG002", "VGL-PKG003", "VGL-PKG004"]

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

See It in Action

Blocking a vulnerable GitHub Actions workflow at write time:

Valca blocking a Comment-and-Control attack

In April 2026, researchers found that all three major AI coding agents (Claude Code, Gemini CLI, Copilot) could be hijacked to exfiltrate ANTHROPIC_API_KEY and GITHUB_TOKEN via a hidden HTML comment in a GitHub issue. CVSS 9.4. No special access required.

Vigil catches the vulnerable workflow (issues: trigger + AI agent + API key in env) before it reaches git — the only tool that does.

Full writeup: The Attack That Steals Your API Keys Through a GitHub Issue Comment


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 valca --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/cordon.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

Found a false positive? Want a rule that doesn't exist yet? Building with AI agents and hitting patterns Vigil should catch?

Open an issue → github.com/vigilsec-io/cordon/issues

Or: vigil feedback

Download files

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

Source Distribution

valca-0.3.1.tar.gz (106.4 kB view details)

Uploaded Source

Built Distribution

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

valca-0.3.1-py3-none-any.whl (88.5 kB view details)

Uploaded Python 3

File details

Details for the file valca-0.3.1.tar.gz.

File metadata

  • Download URL: valca-0.3.1.tar.gz
  • Upload date:
  • Size: 106.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for valca-0.3.1.tar.gz
Algorithm Hash digest
SHA256 cc7afb2f7f37bdf2dacc9ff5812f6900c2ca7afc1a47efe3f2a322f29ecfb6a3
MD5 8fdb25d078d3947dc76ad50a8d00d383
BLAKE2b-256 0d4216bc7d9239a13e6477816b4eec4add608fb34610f9ffe95829f9a9b70103

See more details on using hashes here.

File details

Details for the file valca-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: valca-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 88.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for valca-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 86f01b7623148902444548b64248e5b47b8bf88f4cc469d6480fd5ec2b54d6de
MD5 33f00d7cdb39f5d83a74cf79cd0ed8f5
BLAKE2b-256 9a406f7a1a71300a2716191987068d03ceacc53b5813c5531010004fce33965e

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.3

2 files

0.3.2

2 files

This release

0.3.1 This release

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page