@vibeguard-dev/local
v1.8.0
Published
Static SQL safety analysis for AI agents — catch dangerous queries before they reach your database.
Downloads
64
Maintainers
Readme
@vibeguard-dev/local
Static SQL safety analysis for AI agents. Catch the dangerous queries before they reach your database.
🆕 v1.8 — works with every major coding agent
One command auto-installs the SQL safety skill into every agent on your machine:
npm install @vibeguard-dev/local libpg-query
npx vg-local install-skill # interactive — prompts before writing anywhereSupported agents (auto-detected):
| Agent | Detection signal | What gets installed |
|---|---|---|
| Claude Code (user + project) | ~/.claude/ or ./.claude/ | skills/vibeguard-sql-safety/SKILL.md |
| Cursor (legacy + modern) | .cursorrules or .cursor/rules/ | rule file with the SQL safety instruction |
| aider | CONVENTIONS.md or .aider.conf.yml | block appended to CONVENTIONS.md |
| GitHub Copilot CLI | .github/instructions/ etc. | .github/instructions/vibeguard-sql-safety.instructions.md |
| Google Gemini CLI | gemini.md or .gemini/ | block appended to gemini.md |
| Codeium Windsurf (legacy + modern) | .windsurfrules or .windsurf/rules/ | rule file with the SQL safety instruction |
| AGENTS.md family — Codex CLI, OpenCode, OpenClaw, Hermes, Pi | AGENTS.md, opencode.json, .pi/, .openclaw-system.md | block appended to AGENTS.md |
All instruction-file installs are idempotent (re-running replaces between marker comments, never duplicates). All file-write decisions are filesystem-based and conservative — install-skill only writes to harnesses it can auto-detect, and prompts before touching anything unless --yes is passed.
After the install, every supported agent reads its instruction file on every prompt and pre-flights SQL through:
echo "$SQL" | vg-local analyze --stdin --format=jsonlExit code 1 if any block-severity catch fires; one JSON object per catch on stdout. The agent gates on that.
→ Detailed install / flag reference: see Agent skill install below.
What it does
Your AI agent generates a SQL query. Before you run it, @vibeguard-dev/local
checks the query's structure for known footguns: missing WHERE clauses,
cartesian explosions, type-coercion bugs, recursive CTEs that don't
terminate, irreversible DROP / TRUNCATE, over-fetching projections.
36 senior-DBA-level checks, all static, sub-millisecond, zero network
calls.
Known limitations
VibeGuard is a static, schema-blind analyzer. It checks the shape of a query, not its run-time effect. A few boundaries you should know about before you ship it into CI:
Literal tautologies in
WHEREare caught (since v1.6.0). SQL-034 fires atblock/ 95 onUPDATE/DELETEwhoseWHEREreduces to a literal tautology —WHERE 1=1,WHERE true,WHERE id = id,WHERE NOT false,WHERE 'a' = 'a'. The cross-join variants (UPDATE … FROM/DELETE … USINGwithout a join predicate) are caught by SQL-035 and SQL-036 atblock/ 90.What is still NOT caught: semantic tautologies that depend on schema knowledge, such as
DELETE FROM users WHERE id IS NOT NULLon aNOT NULLPK column, orDELETE FROM users WHERE id IN (SELECT id FROM users). These require column-nullability or correlated- reference tracking and are out of scope for the local SDK.Schema-aware checks are out of scope. The SDK does not know which columns are
NOT NULL, which columns are foreign-key targets, or which tables hold sensitive data. Rules that would require that context (e.g. "thisWHERE col IS NOT NULLis a no-op becausecolis theNOT NULLPK") are deliberately not inlocal.Run-time effects are not modeled. The analyzer does not execute the query, plan it, or evaluate constants.
WHERE 1=1andWHERE current_timestamp > '1970-01-01'are static-tautological in the same way; the SDK treats both as "has aWHEREclause".
The trade-off is intentional: every check runs offline, sub-millisecond, with zero network calls and zero schema dependencies. If you need schema-aware analysis, that's the cloud product.
Quickstart
npm install @vibeguard-dev/local libpg-querylibpg-query is a peer dependency — install it alongside the SDK.
Server-side Node only for the initial release; browser support is
out of scope for now.
One-shot CLI
The fastest way to see what the SDK does:
npx @vibeguard-dev/local initThat scaffolds an example SQL file, runs the analyzer on it, prints
the catch with severity and fix, and adds an npm run lint:sql
script to your package.json you can wire into CI. For ongoing use:
vg-local analyze 'src/**/*.sql'
# Exits 0 if no block-severity catches; 1 if any. CI-friendly.
vg-local analyze 'src/**/*.sql' --fix-dry-run
# Print a unified diff of what --fix would change. Read-only.
vg-local analyze 'src/**/*.sql' --fix
# Apply autofixes in place. SQL-005 / SQL-006 / SQL-001 / SQL-011
# have fixers; other rules surface their catches unchanged.Agent skill install (vg-local install-skill)
One command auto-detects every agent harness on the current machine
- project and installs the
vibeguard-sql-safetyskill into each:
npx vg-local install-skill # interactive
npx vg-local install-skill --yes # non-interactive (CI / scripts)Harnesses supported in v1.8+: Claude Code (user + project),
Cursor (.cursorrules and .cursor/rules/), aider (CONVENTIONS.md),
GitHub Copilot CLI (.github/instructions/), Google Gemini CLI
(gemini.md), Codeium Windsurf (.windsurfrules and
.windsurf/rules/), and any harness that reads AGENTS.md — Codex
CLI, OpenCode, OpenClaw, Hermes (Nous Research), Pi (Inflection).
10 distinct target ids; detection is filesystem-based (the subcommand
looks for each harness's marker file or directory). Run
vg-local install-skill --help for the full detection signal list.
For deterministic activation in Claude Code (the only harness where
description-based routing is best-effort — every other supported
harness reads its instruction file on every prompt by construction),
opt in to a CLAUDE.md memory directive:
npx vg-local install-skill --yes --with-memory=user
# or --with-memory=project for project-scoped activationRestrict to one specific harness:
npx vg-local install-skill --target=copilot-cli
# valid: claude-user | claude-project | cursor-rules-file |
# cursor-rules-dir | aider | agents-md | copilot-cli |
# gemini | windsurf-rules-file | windsurf-rules-dirIdempotent — all file-append targets use marker comments
(<!-- vibeguard-skill-begin --> / <!-- vibeguard-skill-end -->);
re-running replaces between markers, never duplicates.
The SKILL.md file also ships in the npm tarball at
node_modules/@vibeguard-dev/local/examples/agent-skill/SKILL.md for
users who prefer to copy it manually.
Machine-readable output (--format=jsonl)
For agent harnesses, CI pipelines, and jq users, analyze has a
stable JSONL output mode:
vg-local analyze 'src/**/*.sql' --format=jsonl
# One JSON object per catch on stdout. Parse errors stay on stderr.
vg-local analyze 'src/**/*.sql' --format=jsonl \
| jq -c 'select(.severity == "block")'
# Filter to blocking catches only.--format=ndjson is accepted as an alias and emits the same bytes.
The per-line schema is stable post-1.7.0 and documented in
STABILITY.md.
Pipe from stdin (--stdin)
For agents and shell pipelines that have SQL in-memory and don't want to write a temp file:
echo "$SQL" | vg-local analyze --stdin --format=jsonl
# Reads SQL from stdin. The "file" field in JSONL output is "<stdin>".--stdin is mutually exclusive with positional globs, --fix, and
--fix-dry-run (no on-disk file to write back to). The future
--stdin --fix "stream-rewrite" mode is deliberately out of scope
for v1.7.
Reflect mode (experimental)
vg-local analyze --reflect (or --format=reflect) emits one
reflection JSON object per catch — designed for agent
episodic-memory ingestion. Each line includes pain_score,
importance, reflection, and suggested_lesson alongside the
standard catch metadata. The schema is vg-reflect/0 and is
explicitly NOT under semver commitments yet — see
STABILITY.md → Reflection output schema (EXPERIMENTAL)
for the graduation criteria, and docs/reflect-mode.md
for consumption recipes.
vg-local analyze 'src/**/*.sql' --reflect \
| jq -r '"- \(.suggested_lesson)"' >> LESSONS.mdESM
import { analyze, init } from "@vibeguard-dev/local";
await init(); // one-time WASM-parser bootstrap
const result = analyze(`UPDATE users SET email = '[email protected]'`);
if (result.catches.length > 0) {
console.error(result.catches[0]);
// {
// code: 'SQL-003',
// title: 'Unbounded UPDATE statement',
// severity: 'block',
// confidence: 99,
// detail: 'UPDATE on `users` has no WHERE clause. Every row in the table will be modified...',
// fix: 'Add a WHERE clause that scopes the update to specific rows...',
// threatCategories: ['destruction'],
// }
}CommonJS
const { analyze, init } = require("@vibeguard-dev/local");
(async () => {
await init();
const result = analyze("DELETE FROM users");
console.log(result.catches[0]?.code); // 'SQL-003'
})();That's the whole API. After init(), every analyze() call is
synchronous and sub-millisecond on typical queries.
What we deliberately do NOT do
This SDK does static analysis only. It checks the shape of your SQL. It does not:
- Compare your agent's stated intent against what the SQL would actually do
- Estimate real blast radius from the upstream Postgres planner
- Provide tamper-evident audit logging
- Offer human-in-the-loop escalation for grey-zone queries
- Track per-agent behavioral baselines over time
For those, you want VibeGuard Cloud — the wire-protocol proxy and MCP server this SDK is the static-analysis layer of. Use the SDK locally; use the cloud in production. The two are designed to work together.
The 36 catches
Each catch has a stable code (e.g. SQL-001), a severity, a confidence
range, and links to a docs page with examples and references. Catch IDs
are forever-stable — once published, an ID always means the same thing
(see STABILITY.md).
| Code | Title | Severity | Confidence | Default | Auto-fix | Status |
|---|---|---|---|---|---|---|
| SQL-001 | Cartesian explosion | block | 90–95 | ON | placeholder | ✅ shipped |
| SQL-002 | Self-join footgun | warn | 70–85 | ON | — | ✅ shipped |
| SQL-003 | Unbounded UPDATE / DELETE | block | 95–99 | ON | — | ✅ shipped |
| SQL-004 | Implicit type coercion in WHERE | warn | 75–85 | ON | — | ✅ shipped |
| SQL-005 | NULL comparison footgun | warn | 90–95 | ON | yes | ✅ shipped |
| SQL-006 | OFFSET without ORDER BY | warn | 85–95 | ON | placeholder | ✅ shipped |
| SQL-007 | NOT IN with nullable subquery | warn | 75–85 | ON | — | ✅ shipped |
| SQL-008 | String-concat injection patterns | block | 80–95 | ON | — | ✅ shipped |
| SQL-009 | DISTINCT without obvious reduction | info | 60–75 | ON | — | ✅ shipped |
| SQL-010 | Correlated subquery in SELECT | warn | 70–85 | ON | — | ✅ shipped |
| SQL-011 | Aggregate without GROUP BY | warn | 85–95 | ON | yes | ✅ shipped |
| SQL-012 | Recursive CTE without termination | block | 80–95 | ON | — | ✅ shipped |
| SQL-013 | DROP / TRUNCATE / DDL destruction | block / warn | 85–99 | ON | — | ✅ shipped (1.1.0) |
| SQL-014 | INSERT/UPDATE/DELETE without RETURNING | info | 50 | OFF (opt-in) | — | ✅ shipped (1.1.0) |
| SQL-015 | SELECT * over-fetch | info | 60 | ON | — | ✅ shipped (1.1.0) |
| SQL-016 | COPY … FROM/TO PROGRAM (server-side RCE) | block | 99 | ON | — | ✅ shipped (1.6.0) |
| SQL-017 | CREATE EXTENSION of untrusted procedural language | block | 95 | ON | — | ✅ shipped (1.6.0) |
| SQL-018 | ALTER TABLE … DROP COLUMN | warn | 90 | ON | — | ✅ shipped (1.6.0) |
| SQL-019 | CREATE TRIGGER (hidden side effects) | info | 75 | ON | — | ✅ shipped (1.6.0) |
| SQL-020 | CREATE OR REPLACE FUNCTION (silent overwrite) | info | 70 | ON | — | ✅ shipped (1.6.0) |
| SQL-021 | GRANT … TO PUBLIC | warn | 90 | ON | — | ✅ shipped (1.6.0) |
| SQL-022 | CREATE/ALTER ROLE … SUPERUSER | block | 95 | ON | — | ✅ shipped (1.6.0) |
| SQL-023 | pg_terminate_backend / pg_cancel_backend | warn | 85 | ON | — | ✅ shipped (1.6.0) |
| SQL-024 | VACUUM FULL (ACCESS EXCLUSIVE outage) | warn | 80 | ON | — | ✅ shipped (1.6.0) |
| SQL-025 | REFRESH MATERIALIZED VIEW (blocking refresh) | warn | 75 | ON | — | ✅ shipped (1.6.0) |
| SQL-026 | MERGE with tautological ON | block | 90 | ON | — | ✅ shipped (1.6.0) |
| SQL-027 | SET search_path to attacker-controlled schema | warn | 85 | ON | — | ✅ shipped (1.6.0) |
| SQL-028 | pg_create_*_replication_slot | warn | 80 | ON | — | ✅ shipped (1.6.0) |
| SQL-029 | dblink / CREATE SERVER (outbound network) | warn | 80 | ON | — | ✅ shipped (1.6.0) |
| SQL-030 | pg_read_* / lo_export / pg_ls_dir (server FS) | warn | 90 | ON | — | ✅ shipped (1.6.0) |
| SQL-031 | INSERT … SELECT … ON CONFLICT DO UPDATE (unbounded upsert) | info | 75 | ON | — | ✅ shipped (1.6.0) |
| SQL-032 | EXPLAIN ANALYZE of a destructive statement | info | 80 | ON | — | ✅ shipped (1.6.0) |
| SQL-033 | DO $$ … $$ opaque procedural block | info | 70 | ON | — | ✅ shipped (1.6.0) |
| SQL-034 | WHERE 1=1 / literal tautology on UPDATE/DELETE | block | 95 | ON | — | ✅ shipped (1.6.0) |
| SQL-035 | UPDATE … FROM without join predicate | block | 90 | ON | — | ✅ shipped (1.6.0) |
| SQL-036 | DELETE … USING without join predicate | block | 90 | ON | — | ✅ shipped (1.6.0) |
See ROADMAP.md for what's in / out of scope.
Per-rule overrides
Opt in to default-OFF rules, or disable default-ON rules for a single
call, via the rules option. The key is the rule's catch code
(case-insensitive):
// Opt in to SQL-014 (missing RETURNING)
const result = analyze(sql, {
rules: { 'sql-014': { enabled: true } },
});
// Disable SQL-007 for one call
const result = analyze(sql, {
rules: { 'sql-007': { enabled: false } },
});Use with...
Each example is a short, runnable integration showing how to wire the SDK into a common AI tool's pre-execution flow:
- Claude Code — see
examples/claude-code/ - Cursor — see
examples/cursor/ - Replit Agent — see
examples/replit-agent/ - Agent skill (drop-in
SKILL.md) — seeexamples/agent-skill/. Single-file skill for Anthropic Skills-compatible harnesses (Claude Code today; portable to Cursor / aider per the README's adaptation notes). Pairs with--format=jsonlfor machine-readable analyzer output.
For in-editor feedback on sql`...` template literals (with
--fix autofix), see the sibling package
eslint-plugin-vibeguard.
Architecture, in one paragraph
The SDK parses your SQL with libpg-query, walks the resulting AST with
a small, pure-function traversal helper, and runs each query through a
registry of catch-functions. Each catch returns either null (didn't
fire) or a structured Catch with code, severity, confidence, detail,
and fix. No network calls. No state between calls. Sub-millisecond on
typical queries. See ARCHITECTURE.md for the full
design rationale.
Contributing
We welcome new catches that meet the SDK's scope: static-AST-detectable SQL anti-patterns with documented real-world incidents. The proposal process starts with an issue (template here); PRs come after maintainer feedback on whether the pattern fits.
See CONTRIBUTING.md for the full process, CODE_OF_CONDUCT.md for community expectations, and ARCHITECTURE.md for how the codebase is laid out.
Security
This SDK does static analysis. It does not execute SQL. It does not open network connections. It does not log to disk.
If you find a vulnerability — a false-negative that lets a real-world dangerous pattern through, a panic / crash on adversarial input, or a supply-chain concern — see SECURITY.md for the disclosure process. Do not file security issues as public GitHub issues.
License
Apache License 2.0 — see also NOTICE for
attribution requirements that travel with derivative works.
About
VibeGuard is a wire-protocol security layer for AI agents that write SQL. This SDK is the open-source static-analysis component of the broader product.
