๐ช Golang Vulnerability Examples and Fixes
Use this page when: reviewing Go APIs, platform services, operators, or tooling where developers assume that โcompiled and simpleโ automatically means safe.
How to read these examples
- Vulnerable snippet shows the unsafe habit.
- Safer pattern shows the direction you want in production code.
- Why it matters ties the defect to attacker value and business impact.
- Review cue is phrased so it can become a pull-request comment or checklist item.
Example 1 โ SQL injection in database/sql
Vulnerable snippet
id := r.URL.Query().Get("id")
rows, err := db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %s", id))
if err != nil { return err }
Safer pattern
id := r.URL.Query().Get("id")
rows, err := db.Query("SELECT * FROM users WHERE id = ?", id)
if err != nil { return err }
Why it matters
- Formatting SQL strings before sending them to the database merges attacker input into the query itself.
Business impact
- Data disclosure, auth bypass, destructive queries, and higher blast radius when service accounts are over-privileged.
Review cue
- With
database/sql, use placeholders and arguments. Banfmt.Sprintffor SQL construction.
Example 2 โ Path traversal in file download handler
Vulnerable snippet
name := r.URL.Query().Get("name")
path := filepath.Join("/srv/public", name)
http.ServeFile(w, r, path)
Safer pattern
name := r.URL.Query().Get("name")
base := "/srv/public"
clean := filepath.Clean("/" + name)
path := filepath.Join(base, clean)
absBase, _ := filepath.Abs(base)
absPath, _ := filepath.Abs(path)
if !strings.HasPrefix(absPath, absBase+string(os.PathSeparator)) {
http.Error(w, "invalid path", http.StatusBadRequest)
return
}
http.ServeFile(w, r, absPath)
Why it matters
filepath.Joinalone is not an authorization boundary; attackers can still reach parent directories if you never prove the final location.
Business impact
- Exposure of env files, credentials, kubeconfigs, tokens, or source code from the host.
Review cue
- After cleaning and resolving, assert the final path remains under the expected base directory.
Example 3 โ SSRF through arbitrary outbound requests
Vulnerable snippet
rawURL := r.URL.Query().Get("url")
resp, err := http.Get(rawURL)
if err != nil { return }
defer resp.Body.Close()
Safer pattern
rawURL := r.URL.Query().Get("url")
u, err := url.Parse(rawURL)
if err != nil || u.Scheme != "https" {
http.Error(w, "bad url", http.StatusBadRequest)
return
}
allowed := map[string]bool{"status.example.com": true, "cdn.example.com": true}
if !allowed[u.Hostname()] {
http.Error(w, "destination not allowed", http.StatusForbidden)
return
}
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(u.String())
if err != nil { return }
defer resp.Body.Close()
Why it matters
- Server-side fetches inherit the network position and credentials of the service, not of the user who supplied the URL.
Business impact
- Cloud metadata exposure, internal API access, reconnaissance, and unexpected use of trusted outbound integrations.
Review cue
- Every feature that fetches a URL must define which hosts are allowed and which network ranges are forbidden.
Example 4 โ Command injection with sh -c
Vulnerable snippet
archive := r.FormValue("archive")
cmd := exec.Command("sh", "-c", "tar -xf "+archive+" -C /srv/import")
_ = cmd.Run()
Safer pattern
archive := r.FormValue("archive")
if strings.Contains(archive, "..") || strings.HasPrefix(archive, "-") {
http.Error(w, "bad archive name", http.StatusBadRequest)
return
}
cmd := exec.Command("tar", "-xf", archive, "-C", "/srv/import")
_ = cmd.Run()
Why it matters
sh -cdelegates parsing to a shell, so attacker input can become additional commands or flags.
Business impact
- RCE on build/import workers, tampered artifacts, host compromise, and supply-chain evidence corruption.
Review cue
- Avoid
sh -cfor request-derived data. Pass fixed commands and validated arguments directly.
Example 5 โ Broken object-level authorization in handler logic
Vulnerable snippet
invoiceID := chi.URLParam(r, "invoiceID")
row := db.QueryRow("SELECT id, total FROM invoices WHERE id = ?", invoiceID)
// render invoice to currently authenticated user
Safer pattern
invoiceID := chi.URLParam(r, "invoiceID")
userID := auth.UserIDFromContext(r.Context())
row := db.QueryRow(
"SELECT id, total FROM invoices WHERE id = ? AND owner_user_id = ?",
invoiceID,
userID,
)
// if no row, return 404/403
Why it matters
- Authenticated does not mean authorized. Object IDs without owner/tenant scope create a direct cross-account data path.
Business impact
- Data leakage, support incidents, regulatory exposure, and high-severity customer trust failures.
Review cue
- Authorization must be checked where the record is fetched, not where a UI route decides what to show.
Related pages
- Spring / ASP.NET / Go Security Review Guide
- API Testing, Observability, and Release Gates
- Security Quality Gates and Release Blocking
Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.