Valca

io.github.pranlabsv0.6.1更新於 Oct 7, 2026

Scans code, IaC and AI-agent config for security problems. Read-only — nothing can be edited.

概覽

AI 產生的概覽

讓助理對程式碼、IaC 與 AI 代理設定執行唯讀安全掃描,回傳發現項目與規則目錄。

功能
Valca 提供兩個唯讀工具:scan(path) 回傳檔案或目錄的發現項目,包含規則 ID、嚴重性、訊息、檔案、行號與修正建議;list_rules() 回傳完整規則目錄,含每條規則的 ID、嚴重性與偵測內容。涵蓋密鑰、GitHub Actions、Docker、Docker Compose、Terraform、Kubernetes、提示注入、相依性完整性等。沒有任何工具會編輯、修正或寫入內容。
適用情境
適合在工作階段中讓助理檢查程式碼或基礎設施檔案的安全問題,例如審查 Docker Compose 檔案、Terraform 模組或 GitHub Actions 工作流程。它是獨立寫入時掛鉤的唯讀補充,掛鉤才是強制路徑;MCP 伺服器由代理自行選擇啟用。
執行需求
透過 uvx 從 PyPI 套件 valca 啟動的本機程序,需安裝 mcp 額外元件。未宣告帳號、API 金鑰或標頭。掃描範圍限於 valca-mcp 啟動時的目錄,或由 VALCA_MCP_ROOT 指定。僅相依性規則需要網路,會把套件名稱與版本字串傳送到 api.osv.dev、pypi.org 與 registry.npmjs.org。
安裝前請注意
唯讀:沒有工具會編輯、修正或寫入。比對到的原始碼行不會回傳,因此不會暴露密鑰值。路徑以掃描根目錄的相對形式回傳。相依性規則(VGL-PKG001 至 VGL-PKG004)會把套件名稱與版本字串傳送給第三方主機;在 .valcarc 中停用它們即可完全離線執行。此路徑下遙測已關閉。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Valca,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

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 → valca 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:

yaml
ports:  - "5432:5432"   # ← binds to 0.0.0.0, bypasses UFW, reachable from anywhere

The correct form is "127.0.0.1:5432:5432". Valca catches it. Nothing else does.


Install

bash
pip install valca

Wire the Claude Code hook (one time):

bash
valca init --global     # `valca 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:

HostUsed byWhat is sent
api.osv.devVGL-PKG001 (known CVE in a pinned version)package name + version
pypi.orgVGL-PKG002/003/004 (hallucinated, stale, or suspicious package)package name
registry.npmjs.orgVGL-PKG002/003/004package 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 .valcarc and Valca runs completely offline:

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

Usage

bash
# Scan a single filevalca scan docker-compose.yml
# Scan a directoryvalca scan ./my-project/
# JSON output (for CI / dashboards)valca scan ./my-project/ --format json
# SARIF output (for GitHub Advanced Security)valca scan ./my-project/ --format sarif > results.sarif
# Only report HIGH and abovevalca scan ./my-project/ --severity HIGH
# Open feedback & waitlist formvalca feedback

Review what has been caught over time. Both commands read the local scan history — nothing leaves your machine.

bash
# Findings log — what was caught, where, and whenvalca log                              # 20 most recent findingsvalca log --severity CRITICAL          # only criticalsvalca log --project api --since 2026-09-01valca log --limit 100 --format json    # for dashboards
# Aggregate stats — rule frequency, severity mix, precision per rulevalca statsvalca stats --format json

valca stats reports how often each rule fires and how often you suppressed it, so rules with poor precision in your codebase are visible rather than guessed at.

Exit codes:

CodeMeaning
0No findings — write proceeds
1Advisory findings only (MEDIUM / LOW / INFO)
2CRITICAL 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.

Valca 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

116 rules across 27 categories. All built-in, stdlib-only, zero runtime dependencies.

This catalogue is generated from the rule registry — it cannot drift from the shipped engine.

Secrets & Credential Exposure (14 rules)

RuleSeverityWhat it catches
VGL-I001CRITICALeval() or exec() — code injection risk
VGL-I002HIGHsubprocess with shell=True
VGL-I003HIGHos.system() call
VGL-S001CRITICALAWS access key hardcoded
VGL-S002CRITICALHardcoded password
VGL-S003CRITICALHardcoded API key
VGL-S004CRITICALHardcoded bearer token
VGL-S005CRITICALHardcoded JWT secret
VGL-S006CRITICALPEM private key in source
VGL-S007CRITICALDatabase URL with embedded credentials
VGL-S008CRITICALStripe live secret key
VGL-S009CRITICALSlack token hardcoded
VGL-S010CRITICALProvider API key hardcoded (OpenAI / GitHub / GitLab / Google)
VGL-S012HIGHInsecure placeholder default for security-critical config

GitHub Actions — AI Agent Surface (13 rules)

RuleSeverityWhat it catches
VGL-GHA001CRITICALPwn Request — pull_request_target with attacker-controlled checkout ref
VGL-GHA002CRITICALScript injection — user-controlled context expression in run: step
VGL-GHA004HIGHSecret directly interpolated in run: step (visible in process table)
VGL-GHA005MEDIUMWorkflow missing explicit permissions block (implicit GITHUB_TOKEN scope)
VGL-GHA006HIGHCache usage in pull_request workflow (cache poisoning attack vector)
VGL-GHA007HIGHSelf-hosted runner with pull_request trigger (persistent runner risk)
VGL-GHA008HIGHworkflow_run trigger without ref/repo validation
VGL-GHA009CRITICALAI agent wired to untrusted-input trigger (issues/pull_request_target) with API key in env
VGL-GHA010HIGHAI agent on pull_request_target without fork origin guard
VGL-GHA011CRITICALUntrusted GitHub event data interpolated into AI agent prompt
VGL-GHA012HIGHAI agent on untrusted trigger with no --allowedTools restriction
VGL-GHA013MEDIUMAI agent on untrusted trigger with no --max-turns limit
VGL-GHA014HIGHcontents: write permission with AI agent on untrusted trigger

AI Agent — Prompt Injection (9 rules)

RuleSeverityWhat it catches
VGL-PI001CRITICALUser input interpolated into the LLM system prompt
VGL-PI002HIGHRaw HTTP request body used as LLM message content
VGL-PI003HIGHString formatting used to build prompts with user data
VGL-PI004MEDIUMTool output appended to the conversation without sanitization
VGL-PI005CRITICALHTTP request data flows into an LLM prompt unsanitised
VGL-PI006CRITICALLLM response flows into subprocess, exec or eval
VGL-PI007HIGHWebhook payload flows directly into an AI agent
VGL-PI008HIGHDatabase content interpolated into a prompt — second-order injection
VGL-PI009HIGHUser input written into a vector store — memory poisoning

Dockerfile Hardening (8 rules)

RuleSeverityWhat it catches
VGL-DF001HIGHDockerfile runs as root — no USER directive
VGL-DF002MEDIUMDockerfile uses unpinned :latest base image
VGL-DF003HIGHDockerfile bakes secret into ENV or ARG layer
VGL-DF004HIGHcurl
VGL-DF005HIGHTLS verification disabled in Dockerfile RUN fetch
VGL-DF006MEDIUMADD used for local files — use COPY instead
VGL-DF007HIGHCOPY . without .dockerignore — risks leaking .git, .env, credentials
VGL-DF008MEDIUMWorld-writable permissions set in Dockerfile (chmod 777)

Docker Compose (7 rules)

RuleSeverityWhat it catches
VGL-D001CRITICALDocker public port binding — bypasses UFW, exposes service to internet
VGL-D002HIGHdocker-compose environment block contains hardcoded secret
VGL-D003CRITICALDocker container runs in privileged mode
VGL-D004HIGHDocker container uses host network mode
VGL-D005CRITICALDocker socket mounted into container — full container escape vector
VGL-D006HIGHSensitive host path mounted into container
VGL-D007HIGHAWS credentials directory (~/.aws) mounted into container — exposes all profiles

Terraform (7 rules)

RuleSeverityWhat it catches
VGL-TF001CRITICALTerraform hardcoded secret value
VGL-TF002HIGHTerraform resource with public access enabled
VGL-TF003HIGHTerraform encryption explicitly disabled
VGL-TF004CRITICALIMDSv1 enabled on EC2 instance — vulnerable to SSRF metadata theft
VGL-TF005HIGHTerraform S3 state backend stored without encryption
VGL-TF006MEDIUMDeletion protection disabled on managed resource
VGL-TF007MEDIUMAudit logging disabled on Terraform-managed resource

Deserialization & Path Traversal (5 rules)

RuleSeverityWhat it catches
VGL-DESER001CRITICALInsecure deserialization — pickle.loads / pickle.load
VGL-DESER002HIGHInsecure YAML deserialization — yaml.load() without SafeLoader
VGL-DESER003HIGHInsecure deserialization — marshal.loads with untrusted data
VGL-PATH001HIGHPath traversal — user input passed to file open or path join without validation
VGL-SSTI001CRITICALServer-Side Template Injection — user input rendered as Jinja2 template

MCP Server Security (5 rules)

RuleSeverityWhat it catches
VGL-MCP001CRITICALPrompt injection embedded in MCP tool description
VGL-MCP002HIGHMCP tool description built from user-controlled data
VGL-MCP003HIGHShell execution in an MCP tool handler without a sandbox
VGL-MCP004HIGHMCP server config uses an unpinned package or plaintext HTTP endpoint
VGL-MCP005HIGHMCP fetch tool takes an agent-controlled URL with no allowlist

Web Application Security (5 rules)

RuleSeverityWhat it catches
VGL-CORS001HIGHCORS wildcard — allow_origins=['*']
VGL-SQL001CRITICALSQL injection — query built with f-string or string concatenation
VGL-SQL002CRITICALSQL injection — ORM raw query with f-string
VGL-SSL001HIGHSSL verification disabled
VGL-SSRF001CRITICALSSRF — HTTP call with user-controlled URL

AI Agent — Excessive Agency (4 rules)

RuleSeverityWhat it catches
VGL-A001CRITICALLLM output piped into shell execution
VGL-A002HIGHHardcoded auto-approval disables human-in-the-loop
VGL-A003HIGHUnbounded agent loop with no iteration limit
VGL-A004HIGHLLM response written to disk without validation

Authentication & Session (4 rules)

RuleSeverityWhat it catches
VGL-AUTH001CRITICALJWT algorithm=none — signature verification bypassed
VGL-AUTH002CRITICALJWT verify_signature disabled — any token accepted
VGL-AUTH003HIGHWeak or hardcoded web framework secret key
VGL-AUTH004HIGHDebug mode enabled in framework code

Dependency Integrity (4 rules)

RuleSeverityWhat it catches
VGL-PKG001CRITICALPackage audit (CVE · hallucination · staleness · supply chain)
VGL-PKG002CRITICALPackage not found on registry — hallucinated or slopsquatting target
VGL-PKG003HIGHPackage version significantly behind latest — stale AI training data
VGL-PKG004HIGHPackage newly registered with few releases — supply-chain risk

Kubernetes (4 rules)

RuleSeverityWhat it catches
VGL-K001CRITICALPrivileged container or host namespace access
VGL-K002CRITICALallowPrivilegeEscalation enabled in Kubernetes securityContext
VGL-K003HIGHDangerous Linux capabilities added in Kubernetes securityContext
VGL-K004HIGHSensitive hostPath volume in Kubernetes manifest

Logging & Data Exposure (4 rules)

RuleSeverityWhat it catches
VGL-LOG001HIGHSensitive data written to logs (CWE-532)
VGL-LOG002HIGHError details leaked in HTTP response body (CWE-209)
VGL-LOG003MEDIUMSilent exception swallowing in authentication/security context
VGL-LOG004MEDIUMCRLF injection risk — user-controlled input logged without newline sanitization

Swift / iOS (4 rules)

RuleSeverityWhat it catches
VGL-SW001CRITICALHardcoded secret in Swift string literal
VGL-SW002HIGHPlain HTTP URL in Swift networking code
VGL-SW003HIGHSensitive value written to UserDefaults (unencrypted)
VGL-SW004CRITICALSSL certificate validation bypassed in URLSession delegate

Dependency CVE Scanners (3 rules)

RuleSeverityWhat it catches
VGL-DEP001HIGHVulnerable Python packages (pip-audit)
VGL-DEP002HIGHCritical npm vulnerability (npm audit)
VGL-DEP003HIGHVulnerable lockfile package (osv-scanner)

GitHub Actions — Workflow Hygiene (3 rules)

RuleSeverityWhat it catches
VGL-GH001HIGHGitHub Actions secret printed in run step (log exposure)
VGL-GH002HIGHGitHub Actions workflow with excessive permissions
VGL-GH003HIGHGitHub Actions uses mutable action ref (tag or branch)

AI Agent — Configuration Files (2 rules)

RuleSeverityWhat it catches
VGL-AGENT001CRITICALShell execution or exfiltration instructions in an AI agent config file
VGL-AGENT002HIGHCredentials exposed through an exception handler in agent code

JavaScript / TypeScript (2 rules)

RuleSeverityWhat it catches
VGL-JS001HIGHprocess.env secret with hardcoded string fallback
VGL-JS004HIGHeval() or new Function() with dynamic argument (CWE-95)

Row-Level Security (2 rules)

RuleSeverityWhat it catches
VGL-RLS001CRITICALPostgreSQL Row-Level Security explicitly disabled
VGL-RLS002HIGHMulti-tenant ORM query missing user/tenant filter

Cross-Site Scripting (1 rule)

RuleSeverityWhat it catches
VGL-XSS001HIGHCross-Site Scripting — unsafe HTML injection (CWE-79)

Cryptography (1 rule)

RuleSeverityWhat it catches
VGL-RAND001HIGHWeak randomness for security-sensitive value (CWE-330)

IAM Policies (1 rule)

RuleSeverityWhat it catches
VGL-IAM001CRITICALIAM policy wildcard in Action or Resource

Python (1 rule)

RuleSeverityWhat it catches
VGL-PY001HIGHDebug bypass without env guard — auth/security conditionally disabled

Shell Scripts (1 rule)

RuleSeverityWhat it catches
VGL-S011HIGHSecret variable passed inline to subprocess/SSH (ps aux leak)

Trivy IaC Deep Scan (1 rule)

RuleSeverityWhat it catches
VGL-T001HIGHTrivy IaC deep scan — Dockerfile / Terraform misconfigurations

nginx (1 rule)

RuleSeverityWhat it catches
VGL-N001HIGHnginx config missing security headers or weak TLS

Configuration

Place a .valcarc file in your project root (or any ancestor directory):

toml
# .valcarcdisabled_rules = ["VGL-T001"]        # skip trivy scan for this projectmin_severity   = "HIGH"              # only report HIGH and aboveexclude_paths  = ["vendor", "legacy"]telemetry      = false               # opt out of anonymous local telemetry

Valca walks up the directory tree to find the nearest .valcarc (the former .vigilrc name is still read). 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:

python
auto_approve = True  # vigil: ignore

Same pattern as # noqa (flake8) and # nosec (bandit).


Opt-out

Valca collects anonymous, local-only telemetry: rule ID, severity, and file extension. No file paths, no code, no identifiable data. Stored at ~/.valca/events.jsonl — never sent anywhere. History from the former ~/.vigil/ location is migrated automatically.

Opt out permanently:

bash
export VALCA_NO_TELEMETRY=1        # VIGIL_NO_TELEMETRY still works

Or in .valcarc:

toml
telemetry = false

Adding a Rule

python
# src/valca/rules/my_category.pyfrom pathlib import Pathfrom .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/valca/rules/__init__.py. Write tests. Done.


GitHub Actions

Use the action — PranLabs/valca-action:

yaml
permissions:  contents: read  security-events: write
steps:  - uses: actions/checkout@v7
  - uses: PranLabs/valca-action@v1    with:      severity: HIGH      version: "0.6.0"    # pin for reproducible CI
  - uses: github/codeql-action/upload-sarif@v4    if: always()    with:      sarif_file: valca-results.sarif      category: valca

Or call it directly, without the action:

yaml
- run: pip install valca --quiet- run: valca scan . --no-color- run: valca scan . --format sarif > valca-results.sarif

Findings appear as inline annotations on PR diffs, and in the repository's Security tab.


MCP server

The hook blocks an agent. The MCP server lets one ask.

bash
pip install "valca[mcp]"valca-mcp

Register it with any MCP client — for Claude Code, claude mcp add valca -- valca-mcp. Two tools, both read-only:

ToolReturns
scan(path)Findings for a file or directory: rule, severity, message, file, line, suggested fix
list_rules()The full catalogue — every rule id, severity, and what it catches

There is no tool that edits, fixes or writes anything. A scanner that can modify code is a new attack surface, and it is the one VGL-MCP003 and VGL-MCP005 exist to catch.

Three limits are built in rather than configurable:

  • Scanning cannot leave the root. That root is the directory valca-mcp started in, or VALCA_MCP_ROOT if set. The agent picks the argument to scan, so without a boundary it could walk to ~/.ssh and map a filesystem it was never given.
  • Paths come back relative to that root. An absolute path carries your username and directory layout.
  • Matched source lines are never returned. For the secret rules that line is the secret.

Telemetry is off on this path whatever your configuration says. Your .valcarc is still honoured — disabled_rules, exclude_paths and min_severity all apply.

mcp is an optional extra, so installing Valca normally still pulls no runtime dependencies at all.

The hook remains the enforcement path. It runs on every write whether the model wants it or not; MCP is opt-in by the agent, and a check an agent can decline is not enforcement.


Development

bash
git clone https://github.com/PranLabs/valca.gitcd vigilpython3 -m venv venv && source venv/bin/activatepip 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 Valca should catch?

Open an issue → github.com/PranLabs/valca/issues

Or: valca feedback

來源:README.md,提交 caad412

工具

0
工具後設資料尚未被收錄。

版本歷史

1
  1. v0.6.1最新Oct 7, 2026