Why Even Go Developers Fall for Hackers' Tricks?
\n\nGo is renowned for its speed, simplicity, and built-in security tools. But reassuring yourself with the phrase "I write in Go, so I'm safe" is a dangerous illusion. SQL injections and XSS attacks are not relics of the past. They still bring down websites and leak databases daily, and Go is no exception.
\n\nThe problem is that Go gives developers powerful tools but doesn't use them for you. You might accidentally concatenate an SQL query using fmt.Sprintf or output a user comment without escaping — and an attacker will gain access to your database or steal visitors' cookies.
In this article, we'll break down the two most dangerous types of attacks — SQL injections and Cross-Site Scripting (XSS) — and show how to prevent them using native Go and popular libraries. You'll get not just theory, but ready-made code templates you can insert into your project today.
\n\nSQL Injections in Go: How They Occur and How to Nip Them in the Bud
\n\nTypical Mistake: String Concatenation
\n\nImagine you have an endpoint for retrieving a user by ID. The most naive (and dangerous) way to write the query looks like this:
\n\npackage main\n\nimport (\n "database/sql"\n "fmt"\n "log"\n _ "github.com/lib/pq"\n)\n\nfunc getUserByID(db *sql.DB, userID string) {\n // DANGEROUS! String concatenation\n query := fmt.Sprintf("SELECT id, name, email FROM users WHERE id = '%s'", userID)\n rows, err := db.Query(query)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... processing\n}\n\nIf an attacker passes the string 1' OR '1'='1 into userID, the query becomes:
SELECT id, name, email FROM users WHERE id = '1' OR '1'='1'\n\nVoilà — they get all users from the database. And if they enter 1'; DROP TABLE users; --, you lose the table.
Solution: Parameterized Queries (Prepared Statements)
\n\nThe Go driver for PostgreSQL, MySQL, and SQLite supports prepared statements. They separate SQL code from data, and the driver automatically escapes dangerous characters.
\n\nfunc getUserByIDSafe(db *sql.DB, userID string) {\n // SAFE: parameterized query\n query := "SELECT id, name, email FROM users WHERE id = $1"\n rows, err := db.Query(query, userID)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... processing\n}\n\nNotice the $1 — this is a placeholder for the first argument. MySQL uses the ? symbol. Now even if userID contains 1' OR '1'='1, the driver treats it as a string value, not as part of the SQL code.
Working with the sqlx Package
\n\nThe github.com/jmoiron/sqlx library makes the code even cleaner. It supports named parameters and automatic scanning into structs:
import "github.com/jmoiron/sqlx"\n\ntype User struct {\n ID int `db:"id"`\n Name string `db:"name"`\n Email string `db:"email"`\n}\n\nfunc getUserByIDSqlx(db *sqlx.DB, userID string) (*User, error) {\n user := &User{}\n // Named parameter :id\n err := db.Get(user, "SELECT * FROM users WHERE id = :id", sqlx.Named("id", userID))\n if err != nil {\n return nil, err\n }\n return user, nil\n}\n\nImportant: Even if you use an ORM like GORM or Ent, make sure it is configured to use prepared statements. Some older versions of ORMs allowed "raw" queries.
\n\nXSS in Go: When Your Site Shoots Itself in the Foot
\n\nHow an XSS Attack Occurs
\n\nXSS (Cross-Site Scripting) occurs when you insert user content onto a page without escaping. An ev