Skip to content

Latest commit

 

History

79 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sqlguard

sqlguard

Production-safe SQL query analyzer for Go. Catches SELECT *, missing WHERE clauses, N+1 loops and slow queries — at runtime through a database/sql driver wrapper, statically in CI, or from the query plan.

Go Reference Latest release Go version CI CodeQL Coverage Go Report Card License: MIT

Documentation · API Reference · Changelog

A terminal running sqlguard scan and reporting a critical delete-without-where, a select-star and a leading-wildcard finding, each with a redacted query, an issue line and a fix line

sqlguard scan ./... — the same rules the runtime middleware applies to every query your app sends.

Why sqlguard?

Query logging tells you what ran. It does not tell you that the query was a full-table DELETE, that the same lookup just ran 400 times in a loop, or that a LIKE '%…' can never use the index. sqlguard sits at the one place every query passes through — the database/sql driver — and turns those into findings with a rule name, a redacted query and a fix:

Capability sqlguard Hand-rolled query logging
Every query analyzed, ORM or raw, no wrapper type to thread through You build it
21 SQL anti-pattern rules with tunable severities You build it
N+1 and slow-query detection at the driver You build it
Literal redaction + stable, PII-free fingerprints You build it
De-duplication and a per-query analysis cache You build it
Static scan of Go source for CI (exit 1 on findings) You build it
EXPLAIN plan analysis that never executes the query You build it
GORM / sqlx / pgx / bun / xorm / ent adapters You build it

sqlguard is not a query rewriter and not a tracer. It observes, reports, and never touches the SQL or the arguments on their way to the database.

Features

  • Driver-layer interceptionsqlguard.Register wraps any database/sql driver and hands back a real *sql.DB. Everything on top (sqlc, ent, sqlx, GORM, pgx-stdlib) is covered with no method list to keep in sync.
  • 21 detection rulesselect-star, delete-without-where, leading-wildcard, non-sargable-predicate, cartesian-join, large-offset, in-list-too-large and more; slow-query and n-plus-one at runtime; seq-scan, no-index-used, filesort from EXPLAIN.
  • Redaction by default — string and numeric literals become ? before a finding leaves the process. Every finding carries a Fingerprint that is safe as a metrics label.
  • Quiet in production — each finding is reported once per window, and a repeated query hits an exact-string LRU instead of being re-parsed: ~20 ns and zero allocations on a hit.
  • Static scannersqlguard scan ./... resolves literals, constants across packages and fmt.Sprintf formats via go/types.
  • EXPLAIN analyzer — plans a query on live PostgreSQL, MySQL or MariaDB inside a read-only, always-rolled-back transaction. Never ANALYZE.
  • One YAML config.sqlguard.yml drives the middleware, the scanner and the CLI. Inline -- sqlguard:ignore / // sqlguard:ignore need no config at all.
  • Pluggable parser — a zero-dependency fallback by default; opt into a real PostgreSQL or MySQL grammar from a separate module.
  • Near-zero dependenciesanalyzer, middleware and reporter import nothing outside the standard library.

Installation

Requires Go 1.27+.

go get github.com/KARTIKrocks/sqlguard                      # library
go install github.com/KARTIKrocks/sqlguard/cmd/sqlguard@latest  # CLI

Quick Start

package main

import (
    "database/sql"
    "log"
    "time"

    _ "github.com/jackc/pgx/v5/stdlib" // registers the "pgx" driver

    "github.com/KARTIKrocks/sqlguard"
    "github.com/KARTIKrocks/sqlguard/middleware"
)

func main() {
    if err := sqlguard.Register("sqlguard-pg", "pgx",
        middleware.WithSlowQueryThreshold(500*time.Millisecond),
        middleware.WithN1Detection(5, 2*time.Second),
    ); err != nil {
        log.Fatal(err)
    }

    db, err := sql.Open("sqlguard-pg", "postgres://app@localhost/app") // a plain *sql.DB
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.Query("SELECT * FROM users WHERE email = 'a@b.c'")
    // [SQLGUARD WARNING] select-star
    //   Query: SELECT * FROM users WHERE email = ?
    //   Issue: SELECT * detected. Selecting all columns can hurt performance.
    //   Fix:   Select only the columns you need.
}

Then, in CI:

sqlguard scan ./...        # exit 1 on findings

And for one query you are worried about:

sqlguard explain --db "postgres://app@localhost/app?sslmode=disable" \
    "SELECT id FROM orders WHERE customer_id = 42"

Configure once with a .sqlguard.yml at the repo root and feed it to the middleware with config.Middleware("", "."), or silence a single query inline with -- sqlguard:ignore:select-star.

Documentation

Full guides live at kartikrocks.github.io/sqlguard:

Guide Covers
Getting Started Install, wrap a driver, first finding
Runtime Middleware Entry points, every option, what is intercepted, the Guard core
N+1 Detection Windows, thresholds, per-request scoping
Noise Control Finding de-duplication and the analysis cache
Redaction & Fingerprints Why literals never reach a log, what a fingerprint is safe for
Detection Rules All 21 rules: trigger, why it matters, fix, tunables
Suppressions sqlguard:ignore in SQL and in Go
Configuration .sqlguard.yml reference and the config package
Static Scanner What it resolves, JSON output, CI wiring
EXPLAIN Analyzer Plan rules and the never-executes safety model
Integrations GORM, sqlx, pgx/pgxpool, bun, xorm, ent
SQL Parsers Fallback vs. real grammars, what each derives
Analyzer API Custom rules, rule subsets, reporters

Exact type signatures are generated from source on pkg.go.dev.

Integrations

The driver wrapper already covers every library built on database/sql. The adapters exist for APIs that bypass it (native pgx) and for a handle to call ResetN1() on at a request boundary. All six are built on the same middleware.Guard and take the same options.

Module Hooks Install
gormguard gorm.Plugin callbacks on all six chains go get github.com/KARTIKrocks/sqlguard/integrations/gormguard
sqlxguard Wrapper around *sqlx.DB go get github.com/KARTIKrocks/sqlguard/integrations/sqlxguard
pgxguard pgx.QueryTracer + pgx.BatchTracer; composes with otelpgx go get github.com/KARTIKrocks/sqlguard/integrations/pgxguard
bunguard bun.QueryHook go get github.com/KARTIKrocks/sqlguard/integrations/bunguard
xormguard xorm contexts.Hook go get github.com/KARTIKrocks/sqlguard/integrations/xormguard
entguard Decorates ent's dialect.Driver go get github.com/KARTIKrocks/sqlguard/integrations/entguard

Real-grammar parsers are opt-in modules too: parsers/pgparser (PostgreSQL) and parsers/mysqlparser (MySQL), both pure Go.

Performance

Analysis runs on every intercepted query, so it has to be cheap. Rule configuration is resolved once at construction; repeated queries hit an exact-string LRU.

Guard.Check, cache hit       20 ns/op     0 allocs   (repeated query)
Guard.Check, cache miss      22 µs/op    32 allocs   (full parse + all static rules)

Measured on an Intel i5-11400H @ 2.70GHz, Go 1.27, Linux: go test -bench GuardCheck -benchmem ./middleware/.

Security

sqlguard's findings flow into logs, so by default it never emits a raw literal value: Result.Query is redacted before any finding leaves the process, and Fingerprint is always PII-free. The EXPLAIN analyzer never executes the statement it plans — input is validated with a comment- and string-aware multi-statement check, ANALYZE is never used, and every plan runs inside a transaction that is always rolled back. Both are treated as security invariants, not style preferences.

Every push and pull request to main is scanned by CodeQL, with a weekly re-scan; govulncheck gates every merge on advisories reachable from this code's call graph. Both run separately against each of the nine modules, because a scan started from the root stops at nested go.mod boundaries. Dependabot tracks all nine, the docs site, and the GitHub Actions themselves.

See SECURITY.md for the threat model and how to report a vulnerability privately.

Development

make help       # every target
make all        # tidy, fmt, vet, lint, build, test — all nine modules
make ci         # what CI runs: fmt-check, vet, lint, vuln, test-race, lint-docs
make cli        # build bin/sqlguard

The satellite modules (integrations/*, parsers/*) are separate Go modules; the Makefile targets loop over all of them, go test ./... from the root does not. See CONTRIBUTING.md.

License

MIT

Contributing

Contributions welcome — a new rule is one analyzer.Register call and a test. Please read CONTRIBUTING.md first.

About

Production-safe SQL query analyzer for Go — detects slow queries, N+1, and dangerous SQL patterns at runtime and in CI. Like golangci-lint, but for SQL.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages