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 init with a real module path; since 1.26 it writes
the previous minor as the go directive (e.g. go 1.25.0) for ecosystem compatibility — keep that unless you need newer language features; pin the toolchain directive to the current patch release. Add golangci-lint config and a CI step running go vet, golangci-lint run, go test -race ./..., govulncheck ./... from day one (see rules/07).
- Prefer stdlib. Each dependency must earn its place (see
rules/05 supply
chain section).
- Write table tests alongside the code, not after. Exported behavior gets a
test; concurrency gets a -race test; parsers get a fuzz target.
- When generating code that violates a rule for a legitimate reason (e.g.
sync.Pool complexity, 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
%w and context — never
discarded with _, never logged-and-ignored on a path that must abort. Compare with errors.Is/errors.As, never string matching. (rules/01)
- No panics for control flow.
panic is 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 a WaitGroup/errgroup join. 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.Server sets ReadHeaderTimeout, ReadTimeout, WriteTimeout,
IdleTimeout; clients set timeouts and defer resp.Body.Close() with drain.** Default zero timeouts are a DoS. (rules/04)
- SQL only via parameterized queries (
database/sql placeholders, pgx,
or sqlc-generated code). String-built SQL is CRITICAL, no exceptions for "internal" values. (rules/05)
- **
os/exec with argv lists, never sh -c with interpolated input;
file paths validated against a root** (os.Root on 1.24+, else filepath.Clean + prefix check after resolving symlinks). (rules/05)
- **
context.Context is 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 ./... and golangci-lint gate CI; go.sum committed;
dependencies minimal and justified. (rules/05, rules/07)