@vibeguard-dev/local
v1.6.0
Published
Static SQL safety analysis for AI agents — catch dangerous queries before they reach your database.
Maintainers
Readme
@vibeguard-dev/local
Static SQL safety analysis for AI agents. Catch the dangerous queries before they reach your database.
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.ESM
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/
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.
