Harden the (*gorm.DB).Scan guard test: three evasions, an overstated doc comment, and a weak file-count floor #232
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #222, which added
internal/gormlog/scan_guard_test.goto fail if production code calls(*gorm.DB).Scan— the one GORM path that bypasses theParamsFiltervalue suppression and would leak bound values (session key, password hash) into the log.The guard works for accidental use: a reviewer planted twelve
Scanshapes and it reported nine — local variable, struct field, function return value, slice index, map index, parenthesised receiver, chain split across lines,*gorm.DBvia a type alias, and a directRaw(...).Scan(...). Merged on that basis; the items below are hardening, not a live leak. The tree today has exactly one(*gorm.DB).Scancaller,internal/database/database_test.go:91, and itsSELECT 1binds nothing.Three evasions found:
h.QueryRow("SELECT 1").Scan(&n)whereQueryRowis a repo-local method returning*gorm.DBh.Row().Scan(&n)— same, via a method namedRowf := d.db.Scan; f(&n)— a method value. TheScanselector is never theFunof aCallExpr, sounguardedScansnever inspects it. This one is OUTSIDE the fail-closed rule rather than covered by it.The first two work because
isRowProducermatches the receiver's selector NAME only, with no type resolution — so naming a gorm-returning helperRoworQueryRowdefeats it.The doc comment overstates what the code does.
scan_guard_test.go:22-25describesisRowProduceras identifying methods that return adatabase/sqlrow handle, implying type resolution. It is a name allowlist. #222 was failed once already for shipping a false claim in a comment; the same standard applies here.Two weaknesses in the vacuity guards:
minNonTestFiles = 40against 60 actual files: the walk could skip all 16 files ofinternal/databaseand still pass at 44.TestScanGuard_ReportsPlantedCalls's "seven snippets" are really two AST receiver forms among the four positives. The struct-field form its own doc comment names is untested, and thesql rowssnippet would not compile (Rows()returns two values) — snippets are parsed, never type-checked.Definition of done:
isRowProducereither resolves types (go/types) or its comment says plainly that it is a name allowlist and names the evasionf := db.Scan) are detected, or explicitly documented as out of scope with the reasonAlso worth folding in, from the same review: six test-only
gorm.Opensites ininternal/delivery(archive_sweeper_test.gox3,engine_test.go,engine_integration_test.go,target_database_test.go) still use a bare&gorm.Config{}rather thangormlog.New. No secrets are involved — fixture data only, and all three PRODUCTIONgorm.Opensites route throughgormlog.New— but it leaves the unfiltered idiom in-tree to be copied, which is the same concern that failed the first round of #222. Route them through an explicit discard sink; done when nogorm.Openin the tree uses a bare&gorm.Config{}.