Skip to content
cccskills
FreePublished on GitHub

go-security-pitfalls

Use when reviewing Go changes - net/http and Fiber handlers, database/sql and GORM queries, os/exec, filepath and os.Root, html/template, crypto, TLS and SSH clients, JSON binding

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md6.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Go Security Pitfalls

Overview

Go removes whole classes of bugs (memory safety, implicit shell, unsafe default deserialization), which makes the remaining ones look innocent: a fmt.Sprintf in a query builder, a HasPrefix path check, a template.HTML conversion. Memory-safety findings in pure Go are excluded; unsafe and cgo are the exception.

1. SQL: database/sql, sqlx, GORM (CWE-89)

// ❌
db.QueryContext(ctx, fmt.Sprintf("SELECT * FROM tasks WHERE owner = '%s'", owner))
// ✅
db.QueryContext(ctx, "SELECT * FROM tasks WHERE owner = $1", owner)

GORM accepts raw SQL in more places than people expect:

// ❌ all of these take SQL text
db.Where(fmt.Sprintf("name = '%s'", name)).Find(&users)
db.Order(r.URL.Query().Get("sort")).Find(&users)   // ORDER BY injection
db.First(&user, r.PathValue("id"))                // a string id is used as an inline condition
db.Raw("SELECT … " + filter).Scan(&rows)
// ✅
db.Where("name = ?", name).Find(&users)
db.Order(clause.OrderByColumn{Column: clause.Column{Name: sortCols[key]}}).Find(&users)
db.First(&user, "id = ?", id)

2. Commands: os/exec (CWE-78, CWE-88)

exec.Command never invokes a shell — unless you pass sh -c. With separate args the risk is argument injection:

// ❌ shell
exec.Command("sh", "-c", "git clone "+repoURL).Run()
// ❌ no shell, but repoURL = "--upload-pack=…" is an option
exec.Command("git", "clone", repoURL, dir).Run()
// ✅
exec.Command("git", "clone", "--", repoURL, dir).Run()

Also check cmd.Env built from request data (NODE_OPTIONS, LD_PRELOAD, GIT_SSH_COMMAND — injection-and-dangerous-sinks).

3. Paths: filepath, os.Root (CWE-22)

// ❌ lexical check: a symlink inside base escapes it, and
//    "/srv/data-other/x" passes HasPrefix(p, "/srv/data")
p := filepath.Join(base, name)
if !strings.HasPrefix(p, base) { … }

// ✅ Go 1.24+: lookups confined to the root, symlinks included
root, err := os.OpenRoot(base)
f, err := root.Open(name)
// ✅ older Go: reject anything non-local before joining
if !filepath.IsLocal(name) { return errBadPath }

http.FileServer(http.Dir(dir)) blocks .. but follows symlinks inside dir and serves dotfiles — a finding when dir is the repo root or an upload directory. Archive extraction: check filepath.IsLocal(hdr.Name) per entry.

4. Templates (CWE-79)

  • html/template escapes by context; text/template does not — rendering HTML with text/template is XSS when data is user-controlled.
  • Conversions switch escaping off: template.HTML(userBio), template.JS(…), template.URL(…), template.HTMLAttr(…) on user data.
  • template.New("x").Parse(userText) — the user writes the template (server-side template injection; with registered funcs that touch the system, worse).

5. JSON Binding (CWE-915)

// ❌ the request decodes straight into the domain type, Role and OrgID included
var u domain.User
json.NewDecoder(r.Body).Decode(&u)
// ✅ a request DTO with only client-settable fields, mapped explicitly
var req struct{ Name, Email string }
json.NewDecoder(r.Body).Decode(&req)

encoding/json matches keys case-insensitively and lets the last duplicate win: a gateway or validator that checks "role" misses "ROLE". Relevant only when a different component validates the raw JSON.

6. Crypto and Transport (CWE-295, CWE-338)

  • math/rand / math/rand/v2 for tokens → crypto/rand (rand.Text() in Go 1.24+).
  • tls.Config{InsecureSkipVerify: true} on a production client.
  • ssh.InsecureIgnoreHostKey() → MITM on every SSH connection (CWE-322).
  • Secret comparison: subtle.ConstantTimeCompare / hmac.Equal (a note at most when missing).

7. HTTP Handlers and Middleware

  • A route added on the bare router/app instead of the authenticated group (security-diff-review-method).
  • http.Redirect(w, r, r.URL.Query().Get("next"), http.StatusFound) — open redirect; high confidence only.
  • Client IP from X-Forwarded-For or a Fiber/Echo/Gin proxy-header setting without a trusted-proxy list, used for an allowlist or an auth decision.
  • An error from the authorization lookup that falls through to the handler (fail open).
  • Outbound http.Client fetching user URLs: use a dialer Control check and Proxy: nil (ssrf-and-url-allowlists).

8. unsafe and cgo

unsafe.Pointer arithmetic, unsafe.Slice/unsafe.String over attacker-sized buffers, and C code reached through cgo are memory-unsafe again — trace sizes and lengths from input (native-memory-safety-and-ffi has the bounds, integer and cgo pointer rules).

Common Mistakes

  • Reporting exec.Command(bin, args...) as shell injection.
  • Reporting db.Query("… $1", v) or GORM Where("x = ?", v) as SQL injection.
  • Reporting gopkg.in/yaml.v3 or encoding/gob as unsafe deserialization — they do not instantiate arbitrary types.
  • Reporting races between goroutines on non-security state, or integer-conversion lint hits (gosec G115) with no attacker-controlled size.

Red Flags

  • fmt.Sprintf feeding Query, Exec, Raw, Where, Order, Group, Having.
  • "sh", "-c" / "bash", "-c" in exec.Command.
  • strings.HasPrefix on a joined path.
  • template.HTML( around anything not a constant.
  • InsecureSkipVerify: true, InsecureIgnoreHostKey(), math/rand near "token" or "secret".

References (names and links only)

Go security best practices · os.Root (Go 1.24) · gosec rules · GORM security · govulncheck

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

See all skills by makifbaysal

Report a problem with this skill