Thanks for your interest in improving sqlguard! This guide covers the project-specific things that aren't obvious from a quick look at the repo.
sqlguard is a multi-module repo — nine Go modules on Go 1.26, kept in lockstep:
- root (
github.com/KARTIKrocks/sqlguard) — core analyzer, middleware, reporter, config, and CLI. Deliberately near-zero-dependency. parsers/pgparser,parsers/mysqlparser— opt-in real SQL grammars, isolated so their heavy parser deps never enter a consumer's build.integrations/{gormguard,sqlxguard,pgxguard,bunguard,xormguard,entguard}— ORM/driver adapters, each a separate module so its deps stay opt-in.
The satellite modules use a local replace directive pointing at the root, so
you can develop across modules without publishing.
Important:
go test ./...(andgo build/go vet/go mod tidy) from the root does not reach the satellite modules. Always use the Makefile targets, which loop over every module.
make setup # install pinned golangci-lint + goimports (one-time)
make all # tidy, fmt, vet, lint, build, test across all nine modules
make ci # what CI runs: fmt-check, vet, lint, test-race
make test-race # race detector (required for anything touching middleware)
make help # list every targetBefore opening a PR, run make ci and make sure it's green.
- Run a single test:
go test ./middleware/ -run TestName -count=1. - Use
-racefor anything touchingmiddleware(the driver chain andQueryTrackerare concurrent). - After any dependency change, run
make tidy(tidies all nine modules — tidying only the root leaves the others stale).
- Pre-1.0, no backward-compatibility burden. Prefer the clean design over preserving an existing public API; don't add deprecation shims or compat layers.
- Modern Go idioms are expected (range-over-int,
any, compile-time interface assertsvar _ I = (*T)(nil)). - Keep the core dependency-light:
analyzer,middleware, andreportermust stay free of third-party deps and of YAML.configis the only YAML-aware package. - Redaction is the default. Never let raw literal values reach a
Resultthat leaves the process. There is one canonical normalizer (analyzer.Redact/Fingerprint) — don't add a second. - See
CLAUDE.mdfor the deeper architecture notes and invariants, andPRODUCTION_READINESS.mdfor the roadmap.
Rules self-register. Write the rule, then add one analyzer.Register(RuleSpec{ ... }) call in analyzer/rules.go (a stable name, default severity, and a
settings-aware factory). Being addressable by name is what makes enable/disable,
severity overrides, per-rule settings, and suppressions all work uniformly —
do not hand-maintain a rule list. Rules read the normalized Statement,
never raw SQL.
If your rule has a tunable, read it from Settings in the factory and document
it in .sqlguard.example.yml.
Every integration must build on the exported middleware.Guard core —
integrations/pgxguard is the reference. Hand-rolling analysis silently loses
redaction, fingerprints, the parser seam, config, N+1, and dedup. Each
integration should expose ResetN1() for per-request scoping.
- Fork and branch from
main. - Keep changes focused; update docs (
README.md,CLAUDE.md,.sqlguard.example.yml) when behavior or config changes. - Add tests for new behavior; where practical, also prove the failure mode (e.g. a bug-reintroduction check).
- Add a line under
## [Unreleased]inCHANGELOG.md. - Run
make ciand ensure it passes.
Please do not open a public issue for security vulnerabilities. See
SECURITY.md for the private reporting process.
By contributing, you agree that your contributions are licensed under the project's MIT License.