Releases: KARTIKrocks/sqlguard
Release list
v0.5.0
⚠️ N+1 detection was firing at half your threshold on MySQL
Upgrade if you run the middleware or any integration against MySQL. Every interception point analyzed the query before handing it to the base driver. go-sql-driver/mysql declines a direct Query/Exec with driver.ErrSkip for every parameterized query unless interpolateParams=true, which is off by default — database/sql then falls back to Prepare+Query, which re-enters the wrapper and analyzes the same execution a second time.
reported counts after ONE db.Query: map[select-star:2]
QueryTracker.Track ran twice per execution, so WithN1Detection(10, …) fired at 5 real queries. If you tuned a threshold against the old behaviour, halve what you were compensating for. Duplicate static findings were hidden by the default one-minute dedup window, so this only ever surfaced under WithFindingDedup(0).
driver.ErrBadConn had the same shape and was worse: database/sql retries the query on another connection — twice from the pool, then once on a fresh one — so a stale pool (MySQL's wait_timeout, a restart, a failover) produced three analyses of one query.
Analysis now happens once the base driver has answered, and an attempt that declined with ErrSkip or ErrBadConn executed nothing, so it is not counted. Two knock-on effects, both deliberate: findings on that path are produced after execution rather than before (nothing consumes them earlier), and the latency window is read before the rules run, so analysis time can no longer push a query past the slow-query threshold.
This reached every integration — they are all built on middleware.Guard — so gormguard, sqlxguard, pgxguard, bunguard, xormguard and entguard all inherit the fix.
insert-without-columns covers every row-inserting keyword
Expect new findings on an existing codebase. The rule only recognized INSERT INTO, so these bound by column order and said nothing:
| Statement | Why it was missed |
|---|---|
REPLACE INTO t VALUES (…) |
MySQL/SQLite keyword |
UPSERT INTO t VALUES (…) |
Accepted by the grammar behind pgparser |
INSERT t VALUES (…) |
MySQL's INTO is optional |
INSERT LOW_PRIORITY IGNORE t VALUES (…) |
Modifier run before the table |
WITH c AS (…) UPSERT INTO t SELECT … |
CTE puts the keyword mid-statement |
All carry the same schema-change risk as a bare INSERT. A column literally named into no longer defeats the rule either — the target table was found by scanning for INTO anywhere in the statement, so INSERT t (`into`) VALUES (1) had its column list read as the table name.
The message changed with the coverage: "Row-inserting statement without an explicit column list", since it no longer fires only on INSERT.
pgparser no longer flags INSERT INTO t DEFAULT VALUES
Opting into the exact parser made this rule strictly worse than the zero-dependency default, which inverts the trade-off the docs promise. The grammar encodes DEFAULT VALUES as an absent row source, and the parser refilled InsertColumnsListed from len(Columns) alone — then marked the result Exact.
A parser may only remove findings the fallback reports, never add one. That is now an invariant pinned by a test: TestParser_NeverAddsFindingTheFallbackDoesNot runs a corpus through both the dialect grammar and the fallback in each parsers/* module.
sqlguard explain accepts REPLACE and UPSERT
Both were refused as unrecognized statements. They are now admitted under --allow-dml, planned and rolled back like any other DML. On a server where the keyword isn't valid, you get the server's syntax error instead of sqlguard's refusal.
Install
go get github.com/KARTIKrocks/sqlguard@v0.5.0
go install github.com/KARTIKrocks/sqlguard/cmd/sqlguard@v0.5.0All eight satellite modules are tagged v0.5.0 and pinned to this core. Full detail in CHANGELOG.md; docs for this version are at /docs/, with 0.4 still served for anyone not upgrading yet.
What's Changed
- release: pin sub-modules to v0.4.0 by @KARTIKrocks in #79
- fix: stop pgparser flagging INSERT ... DEFAULT VALUES by @KARTIKrocks in #80
- chore: configure CodeAnt AI and sync the other reviewer configs by @KARTIKrocks in #84
- fix: analyze a query once per execution, not once per attempt by @KARTIKrocks in #85
- chore: scope the docs name-exactness rule to sqlguard's own API by @KARTIKrocks in #86
- release: prepare 0.5.0 by @KARTIKrocks in #87
Full Changelog: v0.4.0...v0.5.0
v0.4.0
⚠️ Two breaking changes — migrate first
1. The slow-query threshold moved into rules.settings, so every per-rule tunable lives in one place:
rules:
+ settings:
+ slow-query:
+ threshold: 200ms
-slow-query:
- threshold: 200msThe old top-level key is now an unknown key: a warning by default, and a hard error under strict: true. Config.SlowQueryThreshold and the SlowQueryConfig type are removed; read Profile().Settings["slow-query"].Duration("threshold", d) instead.
2. middleware.NewQueryTracker takes a severity argument. Only affects code constructing a tracker directly:
-NewQueryTracker(threshold, window, reportFn)
+NewQueryTracker(threshold, window, analyzer.SeverityWarning, reportFn)Every documented rule is addressable now
The rules reference lists 21 rules, but only the 14 statement rules were registered. Naming any of the other seven — slow-query, n-plus-one, or the five EXPLAIN plan rules — warned with unknown rule, and failed outright under strict: true, on a config the docs' own table invites you to write. There was also no way at all to switch slow-query off or move it off WARNING.
All seven are registered. disable, severity and settings now reach the runtime findings in the middleware and the plan findings in explain exactly as they reach a statement rule.
rules:
disable: [high-cost]
severity:
slow-query: critical
settings:
slow-query:
threshold: 500ms
n-plus-one: # both keys switch N+1 on from a file — new in 0.4
threshold: 10
window: 1mBehaviour changes to know about
explain now honours rules:. In 0.3 it ignored the file by design, which is what its docs said. A severity override also beats seq-scan's row-count-derived severity.
only: is scoped to the rules evaluated against a statement. It narrows the scanner and the statement rules at runtime, and deliberately does not reach slow-query, n-plus-one or the plan rules. A whitelist is written to focus a scan and names statement rules; if it reached the rest, only: [select-star] in a repository's config would also switch off latency and N+1 reporting in the running application and make sqlguard explain report nothing — without naming any of them, and without warning.
A bad setting is now reported and dropped
Every read path falls back to a default rather than failing, so a value the loader could not use became a silently wrong threshold. Reporting alone was not enough: in lenient mode — the default — the load continues, so slow-query.threshold: 0 warned and then matched every successful query anyway, flooding the reporter it exists to protect. A rejected value no longer reaches the rules.
Each of these is now reported, and the rule falls back to its built-in default:
| Config | Was |
|---|---|
slow-query: {threshold: 0} or 0.5 |
Matched every query (0.5 truncated to zero on read) |
n-plus-one: {threshold: "10"} |
A quoted number is a string in YAML → read as 0 → detection never switched on |
n-plus-one: {window: 1m} alone |
Half a pair does nothing |
slow-query: {threshhold: 1s} |
Misspelled key → built-in default stood |
high-cost: {threshold: 500} |
Rule has no tunables → silently ignored |
only: [slow-query] |
Valid names, but no rule runs over a statement → clean scan on any codebase |
only: [selct-star] |
Every name unknown → empty whitelist → every rule runs |
An unknown rule name in disable, only, severity or settings is now warned about and ignored rather than honoured.
Install
go get github.com/KARTIKrocks/sqlguard@v0.4.0
go install github.com/KARTIKrocks/sqlguard/cmd/sqlguard@v0.4.0All eight satellite modules are tagged v0.4.0 and pinned to this core. Full detail in CHANGELOG.md; docs for this version are at /docs/, with 0.3 still served for anyone not upgrading yet.
What's Changed
- release: pin sub-modules to v0.3.0 by @KARTIKrocks in #76
- feat(config): make every documented rule addressable by @KARTIKrocks in #77
- release: prepare 0.4.0 by @KARTIKrocks in #78
Full Changelog: v0.3.0...v0.4.0
v0.3.0
⚠️ Security — redaction leaked literal values
Upgrade if you run the middleware or any integration. Result.Query and Result.Fingerprint could carry raw literal values out of the process, breaking the invariant in SECURITY.md that no finding leaves with a literal in it. Three cases:
- A literal containing
\'— the default escape on MySQL and in PostgreSQLE'…'strings — closed at the wrong quote, and the scanner then copied the following literal's contents out as query structure. - PostgreSQL dollar-quoted strings (
$$…$$,$tag$…$tag$) passed through untouched. 0x4142redacted only its leading0, leaving the payload intact.
Audit any log sink or metrics label that already captured findings from such queries. A fingerprint is designed to be a safe metric label, so a leak there may have reached a high-cardinality store.
Fixing this changes behaviour you may notice: where a dialect is genuinely ambiguous, Redact now covers both readings and blanks the union, so structure can be lost around a backslash and the fingerprint moves. A query whose values only sometimes contain a backslash now groups under two fingerprints, which splits N+1 counts and de-duplication across both. Bind parameters avoid it.
The CLI now works
Three things the docs described that did not actually run:
| Was | |
|---|---|
sqlguard explain |
Linked no SQL driver — every invocation died with unknown driver "postgres" |
sqlguard scan ./... |
lstat ./...: no such file or directory, on the form used in six places in the docs |
--format json > out.json |
JSON went to stderr, so the file was empty — and a clean run emitted nothing at all, not even [] |
explain also refuses more input: a ; counts as a statement separator whenever any dialect reading leaves it outside a literal, because neither reading of $$ was safe alone. A single statement carrying a ; inside a dollar-quoted body is now rejected — a one-statement query occasionally refused is the safe error here.
Known gap
A "double-quoted" run is still treated as an identifier and preserved. That is correct for ANSI SQL, PostgreSQL, and MySQL under ANSI_QUOTES, but MySQL's default sql_mode reads "…" as a string literal, so those values are not redacted. Use '…' or bind parameters on MySQL. Tracked in #62.
Install
go get github.com/KARTIKrocks/sqlguard@v0.3.0
go install github.com/KARTIKrocks/sqlguard/cmd/sqlguard@v0.3.0All eight satellite modules are tagged v0.3.0 and pinned to this core. Full detail in CHANGELOG.md; docs for this version are at /docs/.
What's Changed
- Pin sub-modules to v0.2.0 by @KARTIKrocks in #57
- Docs/website and readme by @KARTIKrocks in #58
- build(deps): bump the website group in /website with 4 updates by @dependabot[bot] in #59
- build(deps-dev): bump @biomejs/biome from 2.5.13 to 2.5.14 in /website in the website group by @dependabot[bot] in #60
- Fix/redaction literal leaks by @KARTIKrocks in #61
- docs(agents): record the dual-reading invariant, correct the EXPLAIN tx claim by @KARTIKrocks in #63
- fix(cli): accept the ./... package pattern in scan by @KARTIKrocks in #72
- fix(cli): write JSON to stdout, always as an array by @KARTIKrocks in #73
- fix(cli): report an ambiguous
...path instead of skipping it silently by @KARTIKrocks in #74 - release: prepare 0.3.0 by @KARTIKrocks in #75
Full Changelog: v0.2.0...v0.3.0
v0.2.0
What's Changed
- build(deps): bump golang.org/x/tools from 0.47.0 to 0.48.0 in the go-dependencies group by @dependabot[bot] in #19
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /integrations/gormguard in the go-dependencies group by @dependabot[bot] in #20
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /integrations/sqlxguard in the go-dependencies group across 1 directory by @dependabot[bot] in #21
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /parsers/pgparser in the go-dependencies group across 1 directory by @dependabot[bot] in #27
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /integrations/xormguard in the go-dependencies group across 1 directory by @dependabot[bot] in #26
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /integrations/entguard in the go-dependencies group across 1 directory by @dependabot[bot] in #25
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /parsers/mysqlparser in the go-dependencies group by @dependabot[bot] in #24
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /integrations/bunguard in the go-dependencies group across 1 directory by @dependabot[bot] in #23
- build(deps): bump github.com/KARTIKrocks/sqlguard from 0.1.0 to 0.1.1 in /integrations/pgxguard in the go-dependencies group across 1 directory by @dependabot[bot] in #22
- Stop tracking go.work.sum by @KARTIKrocks in #28
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.47 to 1.14.48 in the go-dependencies group by @dependabot[bot] in #33
- build(deps): bump actions/setup-go from 6 to 7 in the github-actions group by @dependabot[bot] in #34
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.48 to 1.14.49 in the go-dependencies group by @dependabot[bot] in #39
- build(deps): bump golang.org/x/tools from 0.48.0 to 0.49.0 in the go-dependencies group by @dependabot[bot] in #40
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.49 to 1.14.50 in the go-dependencies group by @dependabot[bot] in #44
- build(deps): bump github.com/go-sql-driver/mysql from 1.10.0 to 1.10.1 in the go-dependencies group by @dependabot[bot] in #46
- chore: add Greptile code-review configuration by @KARTIKrocks in #47
- Upgrade to Go 1.27 and modernize lint/CI tooling by @KARTIKrocks in #54
- build(deps): bump the go-dependencies group across 1 directory with 3 updates by @dependabot[bot] in #55
- Add 0.2.0 changelog entry by @KARTIKrocks in #56
Full Changelog: v0.1.1...v0.2.0
v0.1.1
What's Changed
- chore: update sqlguard dependency to v0.1.0 across all integrations by @KARTIKrocks in #5
- build(deps): bump golang.org/x/tools from 0.45.0 to 0.46.0 in the go-dependencies group by @dependabot[bot] in #6
- build(deps): bump actions/checkout from 6 to 7 by @dependabot[bot] in #8
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.45 to 1.14.47 in the go-dependencies group by @dependabot[bot] in #7
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.45 to 1.14.47 in /integrations/xormguard by @dependabot[bot] in #12
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.45 to 1.14.47 in /integrations/sqlxguard by @dependabot[bot] in #11
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.45 to 1.14.47 in /integrations/bunguard by @dependabot[bot] in #9
- build(deps): bump github.com/mattn/go-sqlite3 from 1.14.45 to 1.14.47 in /integrations/entguard by @dependabot[bot] in #10
- build(deps): bump xorm.io/xorm from 1.3.11 to 1.4.1 in /integrations/xormguard by @dependabot[bot] in #15
- build(deps): bump gorm.io/gorm from 1.31.1 to 1.31.2 in /integrations/gormguard by @dependabot[bot] in #14
- build(deps): bump golang.org/x/tools from 0.46.0 to 0.47.0 in the go-dependencies group by @dependabot[bot] in #13
- Add gosec + correctness linters to golangci-lint by @KARTIKrocks in #16
- CI tooling fixes and docs cleanup by @KARTIKrocks in #17
- Fix EXPLAIN on MySQL 9 and MariaDB, add live-database integration tests by @KARTIKrocks in #18
Full Changelog: v0.1.0...v0.1.1
v0.1.0
What's Changed
- Add CodeRabbit configuration for sqlguard by @KARTIKrocks in #1
- feat: add PostgreSQL parser and reporting capabilities by @KARTIKrocks in #2
- build(deps): bump codecov/codecov-action from 5 to 7 by @dependabot[bot] in #3
- build(deps): bump github.com/jackc/pgx/v5 from 5.7.6 to 5.10.0 in /integrations/pgxguard by @dependabot[bot] in #4
New Contributors
- @KARTIKrocks made their first contribution in #1
- @dependabot[bot] made their first contribution in #3
Full Changelog: https://github.com/KARTIKrocks/sqlguard/commits/v0.1.0