The SQL linter that catches production disasters at PR time.
DELETE FROM orders without a WHERE? SELECT * on a 50M-row table?
Valk Guard finds them in your code — before your pager does at 3am.
Most SQL linters use regex and only see raw .sql files. Valk Guard compiles your code and walks the AST.
It reads Goqu builder chains, SQLAlchemy ORM calls, and Go db.Query invocations — not by pattern-matching strings, but by parsing the actual abstract syntax tree. It synthesizes SQL from your ORM code, feeds it through a real PostgreSQL grammar, and runs every rule against it.
That means: if your ORM builds a DELETE without a WHERE, Valk Guard catches it — even though no raw SQL exists anywhere in your source.
| What it prevents | Example |
|---|---|
| Accidental mass updates | UPDATE users SET active = false (no WHERE) |
| Unbounded queries | SELECT id, email FROM users (no LIMIT) |
| Index-killing patterns | WHERE email LIKE '%@gmail.com' |
| Dangerous migrations | DROP TABLE, CREATE INDEX without CONCURRENTLY |
| Schema drift | ORM model says email exists, but migration dropped it |
| ORM footguns | session.query(User).delete() — no raw SQL, still caught |
Zero config. No database connection. Runs in CI in seconds.
PostgreSQL only. Valk Guard uses a PostgreSQL parser. MySQL, SQLite, and other dialects are not supported.
# Install
go install github.com/valkdb/valk-guard/cmd/valk-guard@latest
# If this is your first Go-installed CLI, add the Go bin dir to PATH.
# Default location: $(go env GOPATH)/bin
export PATH="$(go env GOPATH)/bin:$PATH"
# Scan your project
valk-guard scan .
# Scan only a specific folder
valk-guard scan ./migrations
# Scan only a specific file
valk-guard scan ./queries/report.sql
# JSON for CI pipelines
valk-guard scan . --format json
# Reviewdog PR comments
valk-guard scan . --format rdjsonl
# GitHub Code Scanning (SARIF)
valk-guard scan . --format sarif --output results.sarifThat's it. All 19 rules are enabled by default.
Pass one or more paths to scan only part of a repo:
valk-guard scan ./migrations
valk-guard scan ./queries/report.sql
valk-guard scan ./migrations ./internalValk Guard ships with 19 rules across three categories. Here are the highlights:
| Rule | What it catches | Severity |
|---|---|---|
| VG002 | UPDATE without WHERE — may wipe entire tables |
error |
| VG003 | DELETE without WHERE — same, but worse |
error |
| VG007 | DROP TABLE, TRUNCATE in application code |
error |
| VG001 | SELECT * — over-fetching columns |
warning |
| VG005 | LIKE '%...' — leading wildcard kills indexes |
warning |
| VG008 | CREATE INDEX without CONCURRENTLY — blocks writes |
warning |
| VG101 | ORM model references a column that migrations dropped | error |
| VG105 | Query SELECTs a column that doesn't exist in schema |
error |
See all 19 rules with full descriptions, examples, and severity levels.
Most SQL linters use regex. Valk Guard compiles and walks the actual AST of your Go and Python code. It understands ORM builder chains as first-class SQL — no raw strings required.
|
Go + Goqu — walks builder chains via |
Python + SQLAlchemy — parses ORM chains via Python AST |
|
|
|
No raw SQL in those files. Valk Guard synthesizes SQL from the ORM calls, parses it with a PostgreSQL grammar, and runs all 19 rules against it (a handful of checks use targeted regex on parser-extracted clauses when the AST doesn't expose the needed field, but source scanning is always AST-based).
| Source | How it works |
|---|---|
Raw SQL (.sql) |
Multi-statement parser with dollar-quoting, nested block comments |
Go (go/ast) |
Extracts SQL from db.Query, db.Exec, db.QueryRow and context variants |
| Goqu | Walks builder chains (From/Join/Where/Limit/ForUpdate) via Go AST |
| SQLAlchemy | Parses ORM chains (query/select/join/filter) via Python AST |
For schema-drift rules (VG101+), it also reads ORM model definitions — Go struct tags (db, gorm) and Python __tablename__ / Column(...) — and cross-references them against your migration DDL.
| Valk Guard | SQL formatters/linters | DB-connected advisors | Schema-only drift checks | |
|---|---|---|---|---|
| Needs a running database | No | Usually no | Usually yes | Usually no |
Scans app source (.go, .py) |
Yes | Rarely | No | Rarely |
| Understands ORM/query builders | Yes | Rarely | No | Sometimes |
| Checks schema drift against models | Yes | Rarely | Sometimes | Yes |
| Fits PR review workflows | Yes | Often | Sometimes | Often |
| Auto-fixes SQL | No | Sometimes | No | No |
| Dialect coverage | PostgreSQL only | Often multi-dialect | Varies | Varies |
| Primary value | SQL + ORM static analysis | SQL style and formatting | Live query/runtime insights | Migration/model consistency |
Valk Guard's niche: static analysis across SQL + ORM code with schema-drift detection, no infrastructure required.
Valk Guard is built for CI. This is the minimal full-repo reviewer step; use the copy-paste workflows below for complete jobs:
permissions:
contents: read
pull-requests: write
jobs:
pr-review:
if: github.event_name == 'pull_request'
steps:
- uses: reviewdog/action-setup@v1
- name: Run valk-guard
run: |
set +e
valk-guard scan . --config .valk-guard.yaml --format rdjsonl > valk-guard.rdjsonl
code=$?
set -e
if [ "$code" -gt 1 ]; then
exit "$code"
fi
- name: Post review comments
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
reviewdog \
-f=rdjsonl \
-name="valk-guard" \
-reporter=github-pr-review \
-filter-mode=added \
-fail-level=none \
< valk-guard.rdjsonlFindings (exit 1) are non-blocking. Config/parser errors (exit 2+) fail the job.
Copy-paste workflows:
See valk-guard reviewing real code in ValkDB/valk-guard-example:
- Query rules (SELECT *, missing WHERE, unbounded queries)
- Index and locking rules (leading wildcard, FOR UPDATE)
- Schema-drift and DDL rules
- Query-schema validation (unknown columns/tables)
- Suppression showcase (inline + global config)
Grab a pre-built binary from GitHub Releases for Linux, macOS, or Windows (amd64/arm64).
go install github.com/valkdb/valk-guard/cmd/valk-guard@latestIf valk-guard is still not found, the install likely succeeded but your Go bin directory is not on PATH yet.
macOS / Linux:
export PATH="$(go env GOPATH)/bin:$PATH"Windows PowerShell:
$env:Path += ";$(go env GOPATH)\bin"If you use GOBIN, add that directory instead of $(go env GOPATH)/bin.
go install github.com/valkdb/valk-guard/cmd/valk-guard@vX.Y.ZWhy pin: avoids surprise behavior changes, keeps output processing stable, makes builds reproducible.
git clone https://github.com/ValkDB/valk-guard.git
cd valk-guard
make build
# Try the built-in sample inputs in this repo.
# These intentionally produce findings, so exit code 1 is expected.
./valk-guard scan testdata/sql
./valk-guard scan testdata/python
# Or scan the whole repo / a specific path in your own project.
./valk-guard scan .
./valk-guard scan ./path/to/folder
./valk-guard scan ./path/to/file.sql
# Optional: install into GOBIN or $(go env GOPATH)/bin
make install- Go >= 1.25.8 for building from source
- Python >= 3.6 only when scanning
.pyfiles for SQLAlchemy usage. No pip packages needed — Valk Guard ships an embedded script using only stdlib (ast,json). If scanned.pyfiles are present andpython3is missing or too old, the scan fails fast with an error.
Zero config works out of the box. To customize, create a .valk-guard.yaml:
exclude:
- "vendor/**"
- "generated/**"
# Optional: override which SQL files build the schema snapshot.
# If omitted, Valk Guard uses these defaults:
# migrations/, migration/, migrate/
migration_paths:
- "db/migrations"
- "schema/**/*.sql"
rules:
VG001:
severity: warning
engines: [all] # all | sql | go | goqu | sqlalchemy
VG007:
enabled: false
go_model:
mapping_mode: strict # strict | balanced | permissiveReference: .valk-guard.yaml.example
-- valk-guard:disable VG001
SELECT * FROM users;Works in Go (//) and Python (#) too. Full guide: docs/suppression.md
| Code | Meaning |
|---|---|
0 |
No findings |
1 |
Findings reported (any severity) |
2 |
Config, runtime, or parser error |
flowchart LR
subgraph S1["1. Source Inputs"]
A1[".sql files"]
A2["Go code"]
A3["Goqu usage"]
A4["Python SQLAlchemy"]
end
subgraph S2["2. Statement Extraction"]
B1["Raw SQL Scanner"]
B2["Go AST Scanner"]
B3["Goqu Scanner"]
B4["SQLAlchemy Scanner"]
B5["Statements with file/line mapping"]
end
subgraph S3["3. Parsing and Schema Context"]
C1["postgresparser"]
C2["DDL -> Schema Snapshot"]
C3["Go Model Extractor"]
C4["Python Model Extractor"]
C5["Model Snapshots"]
end
subgraph S4["4. Rule Evaluation"]
D1["Query Rules VG001-VG008"]
D2["Query-Schema Rules VG105-VG108"]
D3["Model Schema Rules VG101-VG104 and VG109-VG111"]
end
subgraph S5["5. Output"]
E0["Findings"]
E1["terminal"]
E2["json"]
E3["sarif"]
E4["rdjsonl"]
end
A1 --> B1 --> B5
A2 --> B2 --> B5
A3 --> B3 --> B5
A4 --> B4 --> B5
B5 --> C1
C1 --> D1
C1 --> C2
C1 --> D2
A2 --> C3 --> C5
A4 --> C4 --> C5
C2 --> D2
C5 --> D2
C2 --> D3
C5 --> D3
D1 --> E0
D2 --> E0
D3 --> E0
E0 --> E1
E0 --> E2
E0 --> E3
Track progress and vote on what matters to you:
- GORM scanner — AST-based scanning for GORM builder chains and model extraction
- Deeper builder semantics — aliases, nested subqueries, richer predicate trees
- SQLAlchemy 2.0
mapped_column()support — modern model extraction - Custom rule authoring — define your own rules in YAML or Go
- Severity-gated CI — block PRs only on errors, not warnings
- All 19 rules — full reference
- Schema-drift detection
- Suppression and noise control
- Output formats (terminal, JSON, rdjsonl, SARIF)
- CI reviewer mode
- Adding new rules
- Adding new scanners/sources
make build # build binary
make test # run tests (-race)
make lint # golangci-lint
make cover # coverage report
make check # fmt + vet + lint + test- Contributing:
CONTRIBUTING.md - Security:
SECURITY.md| Report a vulnerability - License: Apache 2.0