Part VI — Security-focused Go
A different angle on everything so far: the same language, aimed
specifically at not shipping vulnerabilities. Secure coding practices first,
then three services that put them into practice — password hashing and JWT
issuance, a WAF-style rate-limiting middleware, and a full mutual-TLS setup —
the security guide in this Wiki's companion volume, applied to Go's own
standard library (crypto/tls, net/http middleware) rather than covered
in the abstract.
47. Secure coding practices in Go
47.1 Input validation at the boundary
Validate and normalize every external input (HTTP body, query params, CLI args, file uploads) at the point it enters your system — not deep inside business logic where it's easy to forget a path. Go's explicit error returns make "validate, then return early" natural:
func validateEmail(s string) error {
if len(s) == 0 || len(s) > 254 {
return fmt.Errorf("email length invalid")
}
if _, err := mail.ParseAddress(s); err != nil {
return fmt.Errorf("invalid email format: %w", err)
}
return nil
}
47.2 SQL injection: always use placeholders
// NEVER — string-formats untrusted input directly into SQL
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", userInput)
// ALWAYS — the driver escapes/parameterizes the value safely
row := db.QueryRow("SELECT * FROM users WHERE email = ?", userInput)
database/sql's placeholder syntax (? for MySQL/SQLite, $1 for
Postgres) sends the value separately from the query text — the database
driver never treats it as SQL syntax, closing the injection vector
entirely. There is no legitimate reason to string-format untrusted input
into a query in Go.
47.3 Command injection: avoid the shell entirely
// DANGEROUS if userInput is attacker-controlled — invokes a shell that
// interprets ;, |, `` etc.
exec.Command("sh", "-c", "ping "+userInput)
// SAFE — arguments are passed directly to execve, never interpreted by a shell
exec.Command("ping", "-c", "4", userInput)
exec.Command's variadic arguments go straight to the OS's process-exec
call, bypassing shell parsing entirely, as long as you don't explicitly
route through sh -c.
47.4 Secrets: never in source, never logged
// Read from environment or a secrets manager, never hardcode
apiKey := os.Getenv("API_KEY")
// Redact before logging anything that might contain a secret
logger.Info("api call", "key_prefix", apiKey[:4]+"...")
Go's go vet and third-party tools (gitleaks, trufflehog) catch
committed secrets; the cheaper fix is simply never letting them near
source control or structured logs in the first place.
47.5 Real-world example
Chapter 48–50 build three of the most common security-adjacent Go services from scratch: an auth service issuing/verifying JWTs, a WAF-style rate-limiting middleware, and a full mutual-TLS handshake — the same territory covered conceptually in this Wiki's networking/security field guides, here implemented directly in Go.
Applied in the series: the HTTPS guide's SSRF guard blocks internal addresses at connect time, and its Slowloris lab shows why server timeouts are a security control.