Skip to content

ValkDB/valk-guard

Valk Guard

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.

CI Go Version License Go Report Card Go Reference

valk-guard demo


Why Valk Guard?

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.


Quick Start

# 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.sarif

That'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 ./internal

What It Catches

Valk 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.


Not Regex — Real AST Analysis

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 go/ast

Python + SQLAlchemy — parses ORM chains via Python AST

Goqu AST scanning demo SQLAlchemy AST scanning demo

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.


How It Compares

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.


CI / GitHub Actions

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.rdjsonl

Findings (exit 1) are non-blocking. Config/parser errors (exit 2+) fail the job.

Copy-paste workflows:


Live Demo PRs

See valk-guard reviewing real code in ValkDB/valk-guard-example:


Installation

Download a Binary (easiest)

Grab a pre-built binary from GitHub Releases for Linux, macOS, or Windows (amd64/arm64).

Install via Go

go install github.com/valkdb/valk-guard/cmd/valk-guard@latest

If 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.

Pin in CI (recommended)

go install github.com/valkdb/valk-guard/cmd/valk-guard@vX.Y.Z

Why pin: avoids surprise behavior changes, keeps output processing stable, makes builds reproducible.

Build From Source

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

Requirements

  • Go >= 1.25.8 for building from source
  • Python >= 3.6 only when scanning .py files for SQLAlchemy usage. No pip packages needed — Valk Guard ships an embedded script using only stdlib (ast, json). If scanned .py files are present and python3 is missing or too old, the scan fails fast with an error.

Configuration

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 | permissive

Reference: .valk-guard.yaml.example

Inline Suppression

-- valk-guard:disable VG001
SELECT * FROM users;

Works in Go (//) and Python (#) too. Full guide: docs/suppression.md

Exit Codes

Code Meaning
0 No findings
1 Findings reported (any severity)
2 Config, runtime, or parser error

How It Works

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
Loading

Roadmap

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

Documentation


Development

make build      # build binary
make test       # run tests (-race)
make lint       # golangci-lint
make cover      # coverage report
make check      # fmt + vet + lint + test

Contributing / Security / License

About

Valk Guard scans raw SQL plus application code and catches risky queries before they merge. It parses real source code, synthesizes SQL from supported ORM/query-builder patterns, runs PostgreSQL-aware checks, and can post findings directly into pull requests.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors