SOTA Go (2026)
Expert-level rules for producing and auditing production Go. Baseline language
version: Go 1.25+, the oldest release still in security support (Go fixes the
last two majors; 1.24 left support with 1.26's release, 2026-02). Feature
notes: loop-var scoping from 1.22, b.Loop/os.Root/tool directives from
1.24, testing/synctest and container-aware GOMAXPROCS from 1.25,
errors.AsType and the default-on Green Tea GC from 1.26 — noted where
relevant. Every rule states the why; every rules file
ends with an audit checklist of grep/vet/lint patterns.
Purpose
Two consumers, one source of truth:
- BUILD mode — generating new Go code: follow the rules as defaults, not suggestions. Deviate only with an explicit comment justifying it.
- AUDIT mode — reviewing existing Go code: hunt violations using the audit checklists, classify by severity, report in the finding format below.
BUILD mode
- Before writing code, read the rules files relevant to the task (see index).
A service touching HTTP + DB + goroutines needs
03,04,05. - Apply the top-10 non-negotiables (below) unconditionally.
- New modules:
go mod initwith a real module path; since 1.26 it writes the previous minor as thegodirective (e.g.go 1.25.0) for ecosystem compatibility — keep that unless you need newer language features; pin thetoolchaindirective to the current patch release. Addgolangci-lintconfig and a CI step runninggo vet,golangci-lint run,go test -race ./...,govulncheck ./...from day one (seerules/07). - Prefer stdlib. Each dependency must earn its place (see
rules/05supply chain section). - Write table tests alongside the code, not after. Exported behavior gets a
test; concurrency gets a
-racetest; parsers get a fuzz target. - When generating code that violates a rule for a legitimate reason (e.g.
sync.Poolcomplexity,unsafe), leave a// NOTE(sota):comment explaining the trade-off so auditors don't flag it blind.
AUDIT mode
Work through each relevant rules file's audit checklist against the target repo. Run the listed grep/vet/lint commands; confirm each hit manually before reporting (greps are recall-oriented, expect false positives).
Severity conventions
| Severity | Meaning | Examples |
|---|---|---|
| CRITICAL | Exploitable or guaranteed-incorrect in production | SQL built with fmt.Sprintf, command injection via sh -c, unbounded goroutine leak on hot path, InsecureSkipVerify: true, data race confirmed by -race |
| HIGH | Likely production incident or security weakness | Missing http.Server timeouts, no ctx cancellation on blocking goroutine, unchecked integer truncation on attacker input (G115), resp.Body never closed, panic for control flow in a server |
| MEDIUM | Correctness/maintainability hazard, latent bug | Error strings compared with strings.Contains, context stored in struct, time.After in a loop, map writes without lock under suspected concurrency, missing errors.Is/As |
| LOW | Idiom/perf debt, works but wrong shape | Returning interfaces, util package dumps, missing preallocation on hot path, non-table tests, no t.Parallel |
| INFO | Style, doc, or hygiene note | Naming, missing doc comments, gofumpt drift |
Finding format
[SEVERITY] file.go:LINE — short title
Rule: rules/NN-name.md § section
Evidence: the offending line(s), verbatim
Impact: one sentence — what goes wrong, under what conditions
Fix: concrete replacement code or action
Effort: trivial | small | medium | large
Group findings by severity, CRITICAL first. End the audit with: counts per severity, the three highest-leverage fixes, and which checklists were run.
Rules index
| File | Read this when... |
|---|---|
rules/01-errors.md |
Writing/reviewing any error path: wrapping with %w, errors.Is/As, sentinel vs typed errors, in-band sentinels (absence encoded as -1/0/"") and comma-ok, panic/recover policy, error API design for libraries vs apps |
rules/02-design.md |
Designing packages or APIs: interface placement and size, package layout and internal/, naming, zero values, generics restraint, embedding, functional options, context.Context discipline |
rules/03-concurrency.md |
Anything with go, chan, sync, or select: goroutine lifecycle ownership, leak catalog, errgroup fan-out, channels-vs-mutex decision, race patterns, worker pools, semaphores, time.After traps |
rules/04-http-services.md |
Building or auditing HTTP servers/clients: all five server timeouts, client timeouts and body hygiene, connection reuse, graceful shutdown, middleware, slog structured logging, request-scoped values |
rules/05-security.md |
Any input crossing a trust boundary: SQL parameterization, os/exec safety, path traversal and os.Root, integer overflow (G115), output encoding (html/template), CSPRNG (crypto/rand vs math/rand), TLS config, unsafe/cgo policy, govulncheck, supply chain and go.sum |
rules/06-performance.md |
Latency/memory work: pprof workflow, testing.B + b.Loop, allocation reduction, strings.Builder, sync.Pool criteria, escape analysis, GOGC/GOMEMLIMIT, PGO |
rules/07-tooling-ci.md |
Setting up or auditing CI and tests: golangci-lint curated config, staticcheck/gofumpt/vet, table tests, t.Parallel correctness, testcontainers, golden files, fuzzing, go.mod hygiene and tool directives. Test strategy — suite shape, TDD, doubles, test data, flake policy — lives in sota-testing; load it for any build that writes logic. This file owns Go runner mechanics only. |
Top-10 non-negotiables
- Every error is handled or wrapped with
%wand context — never discarded with_, never logged-and-ignored on a path that must abort. Compare witherrors.Is/errors.As, never string matching. (rules/01) - No panics for control flow.
panicis for unreachable programmer errors only; servers recover at goroutine boundaries and log. (rules/01) - Every goroutine has an owner and a guaranteed exit path — tied to a
context.Context, a closed channel, or aWaitGroup/errgroupjoin. If you can't say how it stops, don't start it. (rules/03) go test -race ./...in CI, always. A race detector failure is a CRITICAL finding, not flaky-test noise. (rules/03,rules/07)http.ServersetsReadHeaderTimeout,ReadTimeout,WriteTimeout,IdleTimeout; clients set timeouts anddefer resp.Body.Close()with drain. Default zero timeouts are a DoS. (rules/04)- SQL only via parameterized queries (
database/sqlplaceholders, pgx, or sqlc-generated code). String-built SQL is CRITICAL, no exceptions for "internal" values. (rules/05) os/execwith argv lists, neversh -cwith interpolated input; file paths validated against a root (os.Rooton 1.24+, elsefilepath.Clean+ prefix check after resolving symlinks). (rules/05)context.Contextis the first parameter, flows down, is never stored in a struct, and carries only request-scoped metadata — never dependencies. (rules/02)- Accept interfaces, return structs; define interfaces at the consumer,
keep them small. No premature interfaces "for mocking". (
rules/02) govulncheck ./...andgolangci-lintgate CI;go.sumcommitted; dependencies minimal and justified. (rules/05,rules/07)