Fix cards for Go and net/http
A01:2025 Broken Access Control
- Missing object-level authorization (IDOR and BOLA)CWE-862 · CWE-639 · CWE-863
Build the signed-in user in middleware from the verified session and the users table, never from request data, and put the same scope in every query's WHERE clause: tenant_id = :tenant AND (owner_id = :user OR :is_billing_admin).
- Open redirectCWE-601
Refuse a next value containing a backslash, a control character or a space, then url.Parse it: accept a path starting with exactly one slash, or a URL whose scheme and host exactly match an allowlisted origin, without user info; otherwise a fixed page.
- Path traversalCWE-22 · CWE-23 · CWE-36 · CWE-59
Open the base directory once with root, err := os.OpenRoot(base) and serve requested files through root.Open(name). os.Root (Go 1.24+) refuses names that leave the directory, whether by ../, an absolute path or a symlink, at the moment it opens the file. filepath.Join is not a check, and a strings.HasPrefix test on its result accepts files-secret and follows symlinks.
- Server-side request forgery (SSRF)CWE-918
Attach the policy to the connection, not the URL string. Give http.Transport a DialContext that resolves the host, rejects loopback, private, link-local and other non-public addresses with net/netip, and dials the vetted IP.
A04:2025 Cryptographic Failures
- Insecure randomness for security tokensCWE-330 · CWE-338
Generate the token with crypto/rand: rand.Read into 32 bytes, encoded with base64.RawURLEncoding, is 256 bits from the OS CSPRNG (rand.Text() gives at least 128).
- Weak password hashingCWE-916 · CWE-759 · CWE-327
Hash new passwords with argon2.IDKey from golang.org/x/crypto at or above OWASP's minimum (memory 19456 KiB, 2 passes, 1 lane) with a 16-byte salt from crypto/rand, and store a PHC string with the parameters.
A05:2025 Injection
- Cross-site scripting (XSS)CWE-79
Render HTML with html/template, never text/template: it escapes each value for where it lands: text, attribute or URL.
- OS command and argument injectionCWE-78 · CWE-88
Run the program itself with each argument as its own string: exec.CommandContext(ctx, tool, "--", name). os/exec never invokes a shell unless you call sh -c, so metacharacters stay characters, and "--" keeps a name that starts with a dash from posing as an option.
- SQL injectionCWE-89
Keep the SQL text constant and pass values as arguments: db.QueryContext(ctx, "... WHERE c.name = ?
A06:2025 Insecure Design
- Unbounded resource consumptionCWE-770 · CWE-400
Cap the body with r.Body = http.MaxBytesReader(w, r.Body, limit): it counts bytes as they are read and stops at the limit whatever the client declares, so a chunked or lying request is bounded too.
- Unrestricted file uploadCWE-434 · CWE-646 · CWE-73 · CWE-770
Decide the type from the bytes, and never serve uploads from the web root.
A07:2025 Authentication Failures
- Improper TLS certificate validationCWE-295 · CWE-297 · CWE-296
Leave verification on and change only the trust anchor: put the partner's CA in x509.NewCertPool(), set it as tls.Config.RootCAs with MinVersion tls.VersionTLS12, and keep InsecureSkipVerify false and ServerName empty so crypto/tls checks the chain, the dates and the host name.
- JWT validation in APIs (bearer tokens)CWE-347 · CWE-287
Verify with jwt.ParseWithClaims, a keyfunc that returns the issuer's public key from configuration, and options: jwt.WithValidMethods([]string{"RS256"}), jwt.WithIssuer, jwt.WithAudience, jwt.WithExpirationRequired and jwt.WithLeeway(30*time.Second).
- Missing authentication for a critical functionCWE-306 · CWE-287 · CWE-288 · CWE-290
Make every critical route go through one authentication step that verifies a credential the server issued: here a Bearer token whose HMAC-SHA256 is checked with hmac.Equal.
A08:2025 Software or Data Integrity Failures
- Mass assignment (over-posting)CWE-915
Decode the body into an input struct that declares only the fields users may edit (here ProfileUpdate with DisplayName and Bio), never into the stored struct, call DisallowUnknownFields, and copy each field across yourself. encoding/json matches keys to fields case-insensitively, so a blocklist that refuses "role" by its exact name lets "Role" through.
Not covered in Go yet
These weaknesses have fix cards in other stacks but not in this one.
- Cross-site request forgery (CSRF)
- CORS misconfiguration on credentialed APIs
- XML external entity (XXE) processing
- Code injection
- CSV formula injection
- LDAP filter injection
- NoSQL operator injection
- Server-side template injection
- XPath injection
- Clickjacking
- Regular-expression denial of service (ReDoS): Go needs no card: its regexp package implements RE2 syntax, and its documentation guarantees that matching runs in time linear in the size of the input, so the catastrophic backtracking the other cards demonstrate cannot happen. Linear is still proportional to the input, so cap request bodies with
http.MaxBytesReader. Two cases still need care: github.com/dlclark/regexp2, a port of the .NET engine used for lookarounds and backreferences, does backtrack, so set itsMatchTimeoutand bound the input; and a pattern built from request data needsregexp.QuoteMeta. - Hard-coded secrets
- Session fixation and session cookies
- Insecure deserialization
- CRLF injection in logs and response headers
Supported versions
net/http, html/template and database/sql ship with the Go toolchain and get security fixes only in Go minor releases. Each major Go release is supported until there are two newer major releases, and Go has a major release every six months, so Go 1.27 and Go 1.26 get fixes and Go 1.25 no longer does. Go standard library support policy.
| Version | Latest | Status | Released | End of support |
|---|---|---|---|---|
| 1.27 | 1.27.2 | SupportedSupported until Go 1.29 is released; 1.27.2 (2026-10-08) is a security release. | 19 August 2026 | No date set |
| 1.26 | 1.26.9 | SupportedSupported until Go 1.28 is released; 1.26.9 (2026-10-08) is a security release. | 10 February 2026 | No date set |
| 1.25 | 1.25.14 | End of lifeSupport ended when Go 1.27 was released, so it did not get the October 2026 fixes. | 12 August 2025 | 19 August 2026 |
| Version | Latest | Status | Released | End of support |
|---|---|---|---|---|
| 1.27 | 1.27.2 | SupportedThe cards run in golang:1.27.2-trixie, pinned by digest. | 19 August 2026 | No date set |
| 1.26 | 1.26.9 | SupportedEvery card's tests also pass in golang:1.26.9-trixie. | 10 February 2026 | No date set |
| 1.25 | 1.25.14 | End of life | 12 August 2025 | 19 August 2026 |
The go line in go.mod is a minimum: the cards say go 1.26.0, the version Go 1.27's go mod init writes, so both supported lines build them. With GOTOOLCHAIN=auto, the default in Go's own downloads, the go command fetches a newer toolchain when a module asks for one; the golang images set GOTOOLCHAIN=local and use the Go they ship. Go toolchain release schedule
Minimum secure versions
The lowest release in each supported line that fixes every published advisory listed here. Anything older is missing a security fix.
| Package | At least | Why |
|---|---|---|
Go 1.27 | 1.27.2 | 15 advisories, among them html/template escaping (GO-2026-6599, GO-2026-6600), HTTP/1 desynchronization after CONNECT (GO-2026-6605, GO-2026-6613), unbounded Range headers (GO-2026-6609) and five HTTP/2 issues (GO-2026-6603 and others). Advisory |
Go 1.26 | 1.26.9 | The same 15 fixes; in 1.26 the HTTP/2 ones land in net/http's bundled copy of HTTP/2. Advisory |
golang.org/x/net | 0.60.0 | The five HTTP/2 fixes above (GO-2026-6603 and others), for programs that import golang.org/x/net/http2 directly. Advisory |
github.com/golang-jwt/jwt/v5 | 5.2.2 | Excessive memory allocation while parsing a token's header (GO-2025-3553, CVE-2025-30204). Advisory |
Middleware and security order
- Wrap your own mux: Middleware is a
func(http.Handler) http.Handler, and the outermost wrapper sees the request first. Build the chain around your ownhttp.ServeMux, nothttp.DefaultServeMux, which any imported package can register handlers on. - Server limits: Serve through an
http.ServerwithReadHeaderTimeout,ReadTimeout,WriteTimeoutandIdleTimeoutset;http.ListenAndServecannot set them. - Body size:
http.MaxBytesHandler, orhttp.MaxBytesReaderin each handler, before anything reads a request body. - Cross-origin protection:
http.CrossOriginProtection(Go 1.25) around routes that change state, when browsers authenticate with cookies;AddTrustedOriginfor other origins you serve. - Authentication, then authorization: Authentication middleware puts the caller in the request context; each handler checks that the caller may use the object whose ID comes from
r.PathValue. - Security headers: net/http adds none to normal responses (
http.Errorsets onlyX-Content-Type-Options); setContent-Security-Policy,Strict-Transport-SecurityandX-Content-Type-Optionsin a middleware.
Secure defaults and settings
| Setting | Value | Why |
|---|---|---|
http.Server ReadHeaderTimeout, ReadTimeout, WriteTimeout, IdleTimeout | Zero: no timeouts | Set them, or a slow client can hold a connection open indefinitely. gosec G112 reports a server without ReadHeaderTimeout, and G114 the http.ListenAndServe helpers. |
http.Server MaxHeaderBytes | DefaultMaxHeaderBytes, 1 MB | Lower it if your API needs less; it does not limit the body. |
http.Server MaxHeaderValueCount | DefaultMaxHeaderValueCount, 500 | New in Go 1.27: the number of header values a request may carry. |
| Request body size | No limit, except the 10 MB cap ParseForm puts on URL-encoded forms | Wrap the body with http.MaxBytesReader before json.NewDecoder(r.Body). |
http.Client Timeout, including http.DefaultClient and http.Get | Zero: no timeout | Use your own http.Client with a Timeout, or a request context with a deadline. |
tls.Config MinVersion | TLS 1.2 | Raise it to TLS 1.3 when every client supports it. |
http.Cookie Secure, HttpOnly, SameSite | Off and unset | Set all three on session cookies; gosec G124 reports cookies without them. |
import _ "net/http/pprof" | Registers /debug/pprof/ handlers on http.DefaultServeMux | Serve the app from its own ServeMux, and pprof, if at all, on a separate listener bound to localhost. |
Escape hatches to search for
Framework APIs that switch a protection off. Each one is sometimes needed; search for them in review and make each use justify itself.
| API | Risk | Instead |
|---|---|---|
tls.Config{InsecureSkipVerify: true} | Any certificate for any host is accepted, so anyone on the network path can read and change the traffic. | Your CA in an x509.CertPool set as RootCAs. |
template.HTML, template.JS, template.URL, template.HTMLAttr | html/template treats the value as already safe and skips escaping it. | Plain strings, which the template escapes for their context. |
text/template for HTML | No escaping at all. | html/template. |
fmt.Sprintf or + into db.QueryContext | SQL injection. | Placeholders and arguments. |
exec.Command("sh", "-c", ...) | A shell parses the request data. | exec.Command(program, args...) with a fixed program. |
math/rand or math/rand/v2 for tokens | Not a cryptographically secure generator. | crypto/rand.Text or crypto/rand.Read. |
jwt.ParseUnverified (golang-jwt) | Returns the claims without checking the signature. | jwt.Parse with jwt.WithValidMethods, jwt.WithIssuer and jwt.WithAudience. |
filepath.Join(base, name), http.Dir, os.DirFS with request paths | .. in a joined name resolves out of base, and http.Dir and os.DirFS follow symlinks out of the tree. | os.OpenRoot and Root.Open, or http.FileServerFS(root.FS()). |
httputil.ReverseProxy Director | Deprecated in Go 1.26: a client can name headers the Director adds as hop-by-hop, and the proxy then removes them. | ReverseProxy.Rewrite. |
Checklist, mapped to ASVS 5.0.0
- v5.0.0-15.2.1 Builds use Go 1.27.2 or 1.26.9 or later, and govulncheck reports no reachable vulnerabilities.
- v5.0.0-15.2.2 The
http.Serversets its timeouts, and handlers cap request bodies withhttp.MaxBytesReader. - v5.0.0-1.2.4 Every query keeps its SQL constant and passes values as arguments.
- v5.0.0-1.2.1 HTML comes from
html/template, and everytemplate.HTMLconversion is reviewed. - v5.0.0-8.2.2 Handlers that take an object ID from
r.PathValuecheck that the caller may use that object. - v5.0.0-15.3.3 Request bodies decode into input structs that hold only the fields the caller may set.
- v5.0.0-5.3.2 Files named in requests are opened through an
os.Root. - v5.0.0-12.3.2 No
tls.Configoutside tests setsInsecureSkipVerify. - v5.0.0-11.5.1 Tokens and identifiers that must not be guessed come from
crypto/rand. - v5.0.0-13.4.5
net/http/pprofand other debug handlers are not reachable on the public listener.
References
- Go: release history and support policy
- Go: security policy
- Go: security best practices for Go developers
- Go: vulnerability management and govulncheck
- Go: toolchains (go line and GOTOOLCHAIN)
- Go blog: Routing enhancements for Go 1.22
- Go blog: Traversal-resistant file APIs (os.Root)
- net/http: Server (timeouts and limits)
- net/http: CrossOriginProtection
- regexp: linear-time matching guarantee
- gosec 2.29.0: rule documentation
- Docker Official Image: golang