Go Security Code Review Guide
1. Introduction
I put this guide together as a structured approach to security-focused code review for Go applications. Whether you're just starting to identify security vulnerabilities in Go code or you're an experienced developer looking for a language-specific checklist, I've tried to make it useful at both levels.
Go's strong type system, built-in concurrency primitives, and opinionated standard library make it a popular choice for backend services and infrastructure tooling. However, what I've found through reviewing Go codebases is that Go's simplicity can mask subtle security issues, unchecked errors, goroutine data races, unsafe pointer usage, and the ease of shelling out via os/exec all create attack surface that requires careful review. What follows covers manual review strategies, common anti-patterns, recommended tooling, and vulnerability patterns organised by class, with cross-references to the intentionally vulnerable examples in this project.
Audience: Security trainees, application developers, code reviewers, and anyone evaluating Go codebases for security weaknesses.
2. Manual Review Best Practices
2.1 Trace Data from Entry to Sink
Go web frameworks (Gin, Echo, net/http) accept user input through query parameters (c.Query()), path parameters (c.Param()), JSON body binding (c.ShouldBindJSON()), headers (c.GetHeader()), and cookies. The approach I've found most effective is to trace every external input to where it's consumed, database queries, shell commands, HTTP responses, outbound HTTP requests, or file operations. Any path from source to sink without validation or sanitisation is a potential vulnerability.
2.2 Inspect String Formatting in Sensitive Contexts
Go's fmt.Sprintf, string concatenation (+), and strings.Builder are frequently used to construct SQL queries, shell commands, HTML output, and URLs. When any of these are used with user-controlled data in a security-sensitive context, treat it as a high-priority finding. Pay special attention to fmt.Sprintf calls inside loops that build query clauses incrementally.
2.3 Review Import Statements
Scan imports for packages known to introduce risk:
os/exec, command injection when used withsh -cand string interpolationcrypto/md5,crypto/sha1,crypto/des, weak or broken cryptographic algorithmsmath/rand, non-cryptographic PRNG, unsuitable for tokens or secretsencoding/gob, deserialisation of untrusted data can cause DoS or unexpected type instantiationplugin, loading shared objects executes arbitrary code via init functionsunsafe, bypasses Go's type safety and memory safety guaranteesruntime/debug,debug.Stack()in HTTP responses leaks internal details
2.4 Check for Hardcoded Secrets
Search for string literals that look like passwords, API keys, tokens, or connection strings. Common patterns include package-level var or const declarations with names like apiKey, secretKey, password, dbDSN, or connection strings containing user:pass@host.
2.5 Examine Error Handling
Go's explicit error handling (if err != nil) is a strength, but review how errors are surfaced. Look for:
debug.Stack()returned in HTTP responses- Raw
err.Error()messages sent to clients (may contain DSN, file paths, or internal details) - Errors silently discarded with
_, especially fromexec.Command, crypto operations, or database calls
2.6 Evaluate Cryptographic Choices
Flag uses of crypto/md5 or crypto/sha1 for password hashing or integrity verification. Check for crypto/des, ECB-mode implementations (manual block-by-block encryption), and hardcoded encryption keys. Verify that math/rand (not crypto/rand) is not used for session tokens, API keys, or other security-sensitive values.
2.7 Assess Access Control Logic
For every Gin/Echo handler, verify:
- Authentication is checked before any business logic executes
- Authorisation checks use server-side session data, not client-supplied headers (e.g.,
X-User-Role) - Object-level access control prevents users from accessing resources belonging to others (IDOR)
- Sensitive struct fields are excluded from JSON serialisation (use separate response structs or
json:"-"tags)
2.8 Review Concurrency Patterns
Go's goroutines make concurrency easy but data races subtle. Look for:
- Shared struct fields or map entries accessed by multiple goroutines without
sync.Mutex,sync.RWMutex, orsync/atomic - Check-then-act patterns (read a value, make a decision, then modify) without holding a lock
- Package-level variables modified by concurrent handlers
- Singleton initialisation without
sync.Once
2.9 Review Configuration Defaults
Check for gin.SetMode(gin.DebugMode), permissive CORS (Access-Control-Allow-Origin reflecting any origin), Server or X-Powered-By headers exposing version information, and unauthenticated settings or diagnostics endpoints.
3. Common Security Pitfalls
3.1 Injection
| Anti-Pattern | Risk |
|---|---|
"SELECT * FROM t WHERE x = '" + userInput + "'" |
SQL injection via string concatenation |
fmt.Sprintf("SELECT ... WHERE status = '%s'", status) |
SQL injection via Sprintf |
fmt.Sprintf("... ORDER BY %s %s", col, dir) with unvalidated dir |
SQL injection via ORDER BY clause |
Loop building SQL with fmt.Sprintf per iteration |
SQL injection in dynamic query builder |
exec.Command("sh", "-c", "ping -c 3 " + host) |
Command injection via shell execution |
exec.Command("sh", "-c", fmt.Sprintf("dig %s %s", rt, domain)) |
Command injection via Sprintf in helper |
fmt.Sprintf("tail -n %s %s | grep '%s'", lines, path, kw) |
Multi-parameter injection in shell pipeline |
fmt.Sprintf("<h2>%s</h2>", userInput) in HTML response |
Reflected and stored XSS |
fmt.Sprintf("%s(%s)", callback, data) for JSONP |
XSS via JSONP callback injection |
3.2 Broken Access Control
| Anti-Pattern | Risk |
|---|---|
Handler returns full struct via c.JSON(200, user) including SSN, salary |
Excessive data exposure via JSON tags |
c.GetHeader("X-User-Role") used for authorisation |
Client-controlled role spoofing |
| No ownership check on resource access after authentication | IDOR, any authenticated user accesses any resource |
| Role update endpoint with authentication but no admin check | Privilege escalation |
| Debug/config endpoints with no authentication | Credential and secret leakage |
3.3 Cryptographic Failures
| Anti-Pattern | Risk |
|---|---|
crypto/des with manual block-by-block encryption |
DES in ECB mode, broken cipher |
desKey = []byte("s3cr3t!!") at package level |
Hardcoded encryption key |
crypto/md5 for password hashing |
Weak, unsalted password hash |
| MD5 concatenation for integrity signatures | Collision-vulnerable integrity check |
math/rand seeded with time.Now().Unix() for tokens |
Predictable PRNG |
SHA-1 hash of "tkn-{seq}-{timestamp}" for API tokens |
Predictable token generation |
3.4 Insecure Design
| Anti-Pattern | Risk |
|---|---|
Database DSN with credentials as package-level var |
Hardcoded infrastructure credentials |
debug.Stack() and DSN returned in error JSON response |
Stack trace and credential leakage |
c.JSON(200, user) where User struct has json:"password" tag |
Password exposure via struct serialisation |
| Password reset token returned in API response body | Token leakage, bypasses email verification |
| Config endpoint returning DSN and SMTP credentials | Credential exposure through API |
3.5 Security Misconfiguration
| Anti-Pattern | Risk |
|---|---|
gin.SetMode(gin.DebugMode) |
Debug mode in production |
c.Header("Server", "Go/1.21 Gin/1.9") |
Technology fingerprinting |
Origin reflection into Access-Control-Allow-Origin with credentials |
Overly permissive CORS |
xml.NewDecoder with Strict = false |
Relaxed XML parsing (defence-in-depth failure) |
debug.Stack() in error handler responses |
Stack trace information disclosure |
| Unauthenticated settings endpoint accepting arbitrary key-value pairs | Security control manipulation |
os.Environ() returned in diagnostics endpoint |
Environment variable leakage |
3.6 Vulnerable Components
| Anti-Pattern | Risk |
|---|---|
Outdated Gin version in go.mod |
Known framework vulnerabilities |
gopkg.in/yaml.v2 instead of yaml.v3 |
Legacy YAML parser with known issues |
Outdated golang.org/x/crypto transitive dependency |
Missing security patches |
Outdated go-playground/validator |
Input validation bypass |
3.7 Auth Failures
| Anti-Pattern | Risk |
|---|---|
const internalAPIKey = "iak_prod_..." |
Hardcoded API key |
crypto/md5 for password hashing in auth flow |
Weak password storage |
| No rate limiting on login endpoint | Brute-force and credential stuffing |
| Hardcoded debug access code accepted as valid token | Authentication bypass backdoor |
| Debug endpoint returning active session tokens | Session hijacking |
3.8 Integrity Failures (Deserialisation)
| Anti-Pattern | Risk |
|---|---|
gob.NewDecoder(reader).Decode(&target) on untrusted input |
DoS via crafted gob payloads |
plugin.Open(path) on downloaded .so file |
Arbitrary code execution via plugin loading |
exec.Command("sh", "-c", body.Script+" "+body.Args) |
Direct shell command injection |
| Gob-encoded data stored and retrieved without integrity check | Tampered deserialisation |
3.9 Logging and Monitoring Failures
| Anti-Pattern | Risk |
|---|---|
Login handler with no log.Printf calls |
Failed auth attempts go undetected |
| Successful login returns password and API key in response | Credential leakage |
| Role change endpoint with no audit logging | Silent privilege escalation |
| Financial transactions with no persistent audit log | Fraud goes undetected |
| Data export endpoint with no access logging | Mass exfiltration invisible |
3.10 SSRF
| Anti-Pattern | Risk |
|---|---|
http.Client.Get(userURL) without validation |
Unrestricted SSRF |
Blocklist checking only localhost and 127.0.0.1 |
SSRF blocklist bypass via [::1], 0.0.0.0, etc. |
| Webhook callback URL fetched without re-validation | Two-step SSRF |
| Hostname string check instead of resolved IP validation | DNS rebinding bypass |
Service proxy with user-supplied base_url fallback |
Open relay to internal services |
3.11 Race Conditions
| Anti-Pattern | Risk |
|---|---|
| Read-sleep-write on shared struct field without mutex | Lost updates (deposits, counters) |
if balance >= amount then sleep then balance -= amount |
TOCTOU, overdraw / double-spend |
| Cross-account transfer modifying two structs without locks | Money creation/destruction |
if available > 0 then sleep then available-- |
Ticket overselling |
| Map read-check-write without synchronization | Inventory overselling |
Singleton if instance == nil without sync.Once |
Multiple instances, data loss |
3.12 Null Pointer Dereference
| Anti-Pattern | Risk |
|---|---|
Map lookup returning nil pointer, then m.Value without check |
Panic, denial of service |
Map lookup returning nil function, then fn() call |
Panic, nil function call |
Function returning nil on parse error, caller dereferences fields |
Panic on malformed input |
Nil check on wrong variable in conditional (best == nil || m.Value > best.Value) |
Masked nil dereference |
3.13 Integer Overflow
| Anti-Pattern | Risk |
|---|---|
int32 * int32 without widening to int64 |
Silent wraparound, incorrect calculations |
Intermediate int64 result cast back to int32 on return |
Truncation, data loss |
Negative check (< 0) as overflow guard |
Incomplete, overflow can produce small positive values |
4. Recommended SAST Tools & Linters
4.1 gosec
gosec (Go Security Checker) is a Go-specific static analysis tool that inspects Go source code for security issues by scanning the AST.
Installation:
go install github.com/securego/gosec/v2/cmd/gosec@latest
Basic usage, scan a single directory:
gosec ./injection/sql-injection/go/...
Scan the entire project recursively:
gosec ./security-bug-examples/...
Generate a JSON report:
gosec -fmt=json -out=gosec_report.json ./security-bug-examples/...
Filter by severity (medium and above):
gosec -severity=medium ./security-bug-examples/...
Exclude specific rules:
gosec -exclude=G104 ./security-bug-examples/...
What gosec catches: Use of exec.Command with sh -c, crypto/md5 and crypto/des usage, hardcoded credentials, math/rand for security-sensitive operations, SQL string concatenation, binding to 0.0.0.0, unhandled errors, and weak TLS configurations.
4.2 staticcheck
staticcheck is a state-of-the-art Go linter that finds bugs, performance issues, and code simplifications. Its SA (static analysis) checks include several security-relevant rules.
Installation:
go install honnef.co/go/tools/cmd/staticcheck@latest
Basic usage, scan a package:
staticcheck ./injection/command-injection/go/...
Scan the entire project:
staticcheck ./security-bug-examples/...
Show only specific check categories:
staticcheck -checks "SA*" ./security-bug-examples/...
Output in JSON format:
staticcheck -f json ./security-bug-examples/...
What staticcheck catches: Deprecated function usage, incorrect use of sync primitives, unreachable code, unused results of important function calls, and various code patterns that indicate bugs. While not security-focused, its SA checks complement gosec by catching code quality issues that can have security implications.
4.3 Semgrep
Semgrep is a multi-language static analysis tool with pattern-based rules. It supports custom rules and has a large community rule registry with Go-specific security rules.
Installation:
pip install semgrep
# or
brew install semgrep
Scan with the default Go security ruleset:
semgrep --config "p/golang" security-bug-examples/
Scan with OWASP Top 10 rules:
semgrep --config "p/owasp-top-ten" security-bug-examples/
Scan a single file:
semgrep --config "p/golang" injection/sql-injection/go/main.go
Run with auto configuration (recommended for first-time scans):
semgrep --config auto security-bug-examples/
What Semgrep catches: SQL injection patterns in Go (string concatenation in db.Query), command injection via exec.Command with shell, SSRF patterns with http.Client, insecure cryptographic usage, hardcoded secrets, and many framework-specific issues. Semgrep's pattern matching is particularly effective at detecting fmt.Sprintf-based injection that gosec may miss in complex expressions.
5. Language-Specific Vulnerability Patterns
5.1 SQL Injection (CWE-89)
Pattern: String concatenation in queries
// Vulnerable, user input concatenated directly
keyword := c.Query("q")
query := "SELECT id, name FROM products WHERE name LIKE '%" + keyword + "%'"
rows, err := db.Query(query)
Pattern: fmt.Sprintf in query construction
// Vulnerable, Sprintf builds WHERE clause from user input
status := c.Query("status")
query += fmt.Sprintf(" AND status = '%s'", status)
Pattern: Unvalidated ORDER BY injection
// Vulnerable, sortDir not validated against allowlist
query := fmt.Sprintf("SELECT * FROM products ORDER BY %s %s", sortCol, sortDir)
Pattern: Loop-based query building with Sprintf
// Vulnerable, each tag injected via Sprintf inside loop
tags := strings.Split(c.Query("tags"), ",")
for _, tag := range tags {
query += fmt.Sprintf(" AND name LIKE '%%%s%%'", strings.TrimSpace(tag))
}
Safe alternative:
rows, err := db.Query("SELECT id, name FROM products WHERE name LIKE ?", "%"+keyword+"%")
5.2 Command Injection (CWE-78)
Pattern: exec.Command with sh -c and concatenation
cmd := exec.Command("sh", "-c", "ping -c 3 "+host)
Pattern: exec.Command with sh -c and fmt.Sprintf
cmdStr := fmt.Sprintf("dig %s %s +short", recordType, domain)
cmd := exec.Command("sh", "-c", cmdStr)
Pattern: Shell pipeline with multiple injection points
cmdStr := fmt.Sprintf("tail -n %s %s | grep '%s'", lines, logPath, keyword)
cmd := exec.Command("sh", "-c", cmdStr)
Safe alternative:
// Use argument list, no shell interpretation
cmd := exec.Command("ping", "-c", "3", host)
5.3 Cross-Site Scripting, XSS (CWE-79)
Pattern: Reflected XSS via fmt.Sprintf in HTML
// Vulnerable, query parameter embedded without escaping
tag := c.Query("tag")
html := fmt.Sprintf("<p>Filtering by tag: <strong>%s</strong></p>", tag)
Pattern: Stored XSS via struct fields in HTML
// Vulnerable, stored post content rendered without escaping
html := fmt.Sprintf("<h2>%s</h2><div>%s</div>", post.Title, post.Content)
Pattern: XSS via href attribute (javascript: protocol)
html := fmt.Sprintf(`<a href="%s">%s</a>`, profile.Website, profile.Website)
Pattern: JSONP callback injection
callback := c.Query("callback")
response := fmt.Sprintf("%s(%s)", callback, string(jsonData))
c.Data(200, "application/javascript", []byte(response))
Safe alternative:
import "html"
safe := html.EscapeString(userInput)
output := fmt.Sprintf("<p>%s</p>", safe)
5.4 Broken Access Control (CWE-200, CWE-284, CWE-639)
Pattern: No authentication on sensitive endpoint
func getUserProfile(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
user := users[id]
c.JSON(200, user) // Returns SSN, salary, no auth check
}
Pattern: Client-controlled authorisation header
role := c.GetHeader("X-User-Role")
if role != "admin" {
c.JSON(403, gin.H{"error": "Admin access required"})
return
}
Pattern: Missing object-level authorisation
// Any authenticated user can access any ticket, no ownership check
ticket := tickets[ticketID]
c.JSON(200, ticket) // Includes internal_notes
5.5 Cryptographic Failures (CWE-327, CWE-328, CWE-330)
Pattern: DES in ECB mode with hardcoded key
var desKey = []byte("s3cr3t!!")
block, _ := des.NewCipher(desKey)
// Manual block-by-block encryption, ECB mode
for i := 0; i < len(padded); i += des.BlockSize {
block.Encrypt(encrypted[i:i+des.BlockSize], padded[i:i+des.BlockSize])
}
Pattern: MD5 for password hashing
hash := md5.Sum([]byte(password))
return hex.EncodeToString(hash[:])
Pattern: Predictable PRNG for session tokens
src := rand.NewSource(time.Now().Unix())
rng := rand.New(src)
// Generates "random" token from predictable seed
Safe alternative:
import "golang.org/x/crypto/bcrypt"
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
import "crypto/rand"
b := make([]byte, 32)
crypto_rand.Read(b)
token := hex.EncodeToString(b)
5.6 Insecure Design (CWE-209, CWE-522)
Pattern: Hardcoded credentials in package-level variables
var dbDSN = "host=db.internal port=5432 user=appuser password=Pg_Pr0d#2024 dbname=mydb"
Pattern: Stack trace and DSN in error response
c.JSON(500, gin.H{
"error": err.Error(),
"trace": string(debug.Stack()),
"database": dbDSN,
})
Pattern: Password reset token returned in response
c.JSON(200, gin.H{
"message": "Reset email sent",
"reset_token": token, // Should only be sent via email
"smtp_server": smtpHost, // Leaks internal infrastructure
})
5.7 Security Misconfiguration (CWE-16, CWE-611)
Pattern: Debug mode and version disclosure
gin.SetMode(gin.DebugMode)
c.Header("Server", fmt.Sprintf("Go/%s Gin/%s", runtime.Version(), gin.Version))
Pattern: Overly permissive CORS
origin := c.GetHeader("Origin")
if origin == "" { origin = "*" }
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Credentials", "true")
Pattern: Relaxed XML parsing
decoder := xml.NewDecoder(strings.NewReader(rawXML))
decoder.Strict = false // Accepts malformed XML
5.8 Vulnerable and Outdated Components (CWE-1104)
Pattern: Outdated dependencies in go.mod
require (
github.com/gin-gonic/gin v1.7.4 // Known CVEs
gopkg.in/yaml.v2 v2.4.0 // Legacy, use yaml.v3
)
Detection approach:
# Use govulncheck for vulnerability scanning
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# Use nancy for dependency auditing
go list -json -deps ./... | nancy sleuth
5.9 Integrity Failures, Deserialisation (CWE-502, CWE-829)
Pattern: Gob deserialisation of untrusted input
decoded, _ := base64.StdEncoding.DecodeString(payload)
dec := gob.NewDecoder(bytes.NewReader(decoded))
dec.Decode(&target) // Crafted payloads can cause DoS
Pattern: Remote plugin loading
resp, _ := http.Get(pluginURL)
data, _ := io.ReadAll(resp.Body)
os.WriteFile(path, data, 0755)
p, _ := plugin.Open(path) // Executes init(), arbitrary code execution
Pattern: Shell command execution from user input
cmd := exec.Command("sh", "-c", body.Script+" "+body.Args)
5.10 Logging and Monitoring Failures (CWE-778)
Pattern: No logging of authentication events
func handleLogin(c *gin.Context) {
// ... validates credentials ...
if user.Password != hash(body.Password) {
c.JSON(401, gin.H{"error": "Invalid credentials"})
return // No log.Printf, brute force invisible
}
c.JSON(200, gin.H{"token": token})
// No log of successful login either
}
Pattern: Credentials in API responses
c.JSON(200, gin.H{
"token": token,
"password": user.Password, // Plaintext password in response
"api_key": user.APIKey, // API key in response
})
5.11 SSRF (CWE-918)
Pattern: Unrestricted URL fetch
resp, err := http.Get(userSuppliedURL) // No validation
Pattern: Incomplete blocklist
blocked := map[string]bool{"localhost": true, "127.0.0.1": true}
// Missing: [::1], 0.0.0.0, 169.254.169.254, 10.x.x.x, 172.16.x.x, 192.168.x.x
Pattern: Hostname check instead of resolved IP check (DNS rebinding)
if strings.HasPrefix(parsed.Hostname(), "169.254") {
// Bypassable via DNS rebinding, domain resolves to safe IP first, then 169.254.x.x
}
Safe alternative:
ips, _ := net.LookupIP(parsed.Hostname())
for _, ip := range ips {
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
c.JSON(403, gin.H{"error": "Blocked destination"})
return
}
}
5.12 Race Conditions (CWE-362)
Pattern: Check-then-act without locking
func (a *BankAccount) Withdraw(amount int64) bool {
if a.Balance >= amount {
time.Sleep(time.Millisecond) // Simulates processing delay
a.Balance -= amount // Another goroutine may have changed balance
return true
}
return false
}
Pattern: Non-atomic read-modify-write on shared map
stock := inv.Products[name]
if stock >= qty {
time.Sleep(time.Millisecond)
inv.Products[name] = stock - qty // Lost update
}
Pattern: Broken singleton without sync.Once
func GetConfig() *Config {
if configInstance == nil {
time.Sleep(time.Millisecond)
configInstance = &Config{} // Multiple goroutines create separate instances
}
return configInstance
}
Safe alternative:
var mu sync.Mutex
func (a *BankAccount) Withdraw(amount int64) bool {
mu.Lock()
defer mu.Unlock()
if a.Balance >= amount {
a.Balance -= amount
return true
}
return false
}
// Or use sync.Once for singletons
var once sync.Once
func GetConfig() *Config {
once.Do(func() { configInstance = &Config{} })
return configInstance
}
5.13 Null Pointer Dereference (CWE-476)
Pattern: Unchecked map lookup returning nil pointer
m := store.Get(name) // Returns nil if name not in map
total += m.Value // Panic: nil pointer dereference
Pattern: Nil function call from map lookup
fn := widgets[name] // Returns nil func if name not in map
return fn() // Panic: nil function call
Pattern: Nil check on wrong variable
// best is checked for nil, but m is not, m can be nil when best is non-nil
if best == nil || m.Value > best.Value {
best = m
}
Safe alternative:
m := store.Get(name)
if m == nil {
continue // or return error
}
total += m.Value
5.14 Integer Overflow (CWE-190)
Pattern: int32 multiplication without widening
func ComputeCharge(rate int32, nights int32) int32 {
return rate * nights // Silently wraps on overflow
}
Pattern: Correct intermediate but truncated return
func convert(amount int32, rate int32) int32 {
intermediate := int64(amount) * int64(rate) // Correct
return int32(intermediate / 1000000) // Truncation!
}
Pattern: Incomplete overflow guard
totalSize := rooms * dataPerRoom
if totalSize < 0 { return nil } // Misses overflow to small positive values
Safe alternative:
func ComputeCharge(rate int32, nights int32) int64 {
return int64(rate) * int64(nights)
}
6. Cross-References to Examples
The table below maps each vulnerability class to the Go source file and companion documentation in this project. All paths are relative to the docs/ directory.
7. Quick-Reference Checklist
Use this checklist during Go code reviews. Each item maps to a vulnerability class covered in this guide.
- Parameterized queries, All SQL queries use
?placeholders viadb.Query(query, args...), never string concatenation orfmt.Sprintf - No
sh -cwith user input,exec.Commanduses argument lists (exec.Command("ping", "-c", "3", host)), neverexec.Command("sh", "-c", userString) - HTML escaping, All user-controlled data rendered in HTML uses
html.EscapeString()or Go'shtml/templatepackage with autoescaping - No JSONP, JSONP callbacks are validated against
^[a-zA-Z_$][a-zA-Z0-9_$]*$or replaced with CORS - Authentication on every endpoint, All sensitive handlers verify the session/token before processing
- Server-side authorisation, Role and permission checks use server-side session data, not
c.GetHeader("X-User-Role") - Object-level access control, Resource access verifies the requesting user owns or is authorized for the specific resource
- Minimal JSON serialisation, Response structs exclude sensitive fields; use separate response types or
json:"-"tags - Strong password hashing, Passwords are hashed with
golang.org/x/crypto/bcryptorargon2, nevercrypto/md5orcrypto/sha1 - Modern encryption, AES-GCM via
crypto/aesandcrypto/cipheris used; nocrypto/des, no ECB mode - Cryptographic randomness, Tokens and secrets use
crypto/rand, notmath/rand - No hardcoded secrets, API keys, passwords, DSNs, and encryption keys are loaded from environment variables or a secrets manager
- Safe deserialisation,
encoding/gobis not used on untrusted input; preferencoding/jsonwith size limits - No
plugin.Openon untrusted files, Go plugins are loaded only from trusted, integrity-verified sources - SSRF protection, Outbound HTTP requests resolve hostnames and validate IPs against private/loopback/link-local ranges
- Minimal error responses, Error responses do not include
debug.Stack(), rawerr.Error(), DSNs, or internal paths - No debug mode,
gin.SetMode(gin.ReleaseMode)is used; noServerorX-Powered-Byheaders expose versions - Secure CORS,
Access-Control-Allow-Originis set to specific trusted origins, not reflected from the request - Strict XML parsing,
xml.NewDecoderusesStrict = true - Audit logging, Authentication events, privilege changes, financial transactions, and data exports are logged with
log.Printf - No credentials in responses, API responses do not include passwords, API keys, session tokens, or DSNs
- Goroutine safety, Shared mutable state uses
sync.Mutex,sync.RWMutex, orsync/atomic; singletons usesync.Once - Nil pointer checks, All map lookups and function returns that yield pointers are checked for
nilbefore dereferencing - Integer overflow prevention, Arithmetic on
int32values is widened toint64before multiplication; results are not narrowed back without range checks - Dependency hygiene,
go.modpins current versions; dependencies are checked withgovulncheckornancy - Race detector,
go test -race ./...andgo run -raceare part of the CI pipeline