A Unified Defense Against MITRE’s Top Injection Attacks
Attackers continue to exploit injection flaws — all ranked among the most dangerous weaknesses in MITRE’s 2025 CWE Top 25 list with 41 actively exploited vulnerabilities.
Join the DZone community and get the full member experience.
Join For FreeThis is how I created a Go library to address 41 actively exploited vulnerabilities.
The Problem That Keeps Security Teams Up at Night
On December 11, 2025, MITRE released its annual 2025 CWE Top 25 Most Dangerous Software Weaknesses list, analyzing 39,080 CVE records from the past year. The results should concern every developer.
Four injection vulnerabilities alone account for 41 Known Exploited Vulnerabilities (KEVs) — these aren’t theoretical risks. They’re being actively exploited by attackers right now:
|
Rank |
Vulnerability |
KEVs |
Score |
|---|---|---|---|
| #1 | Cross-site scripting (XSS) | 76 | 0.38 |
| #2 | SQL injection | 42 | 8.72 |
| #6 | Path traversal | 10 | 8.99 |
| #9 | OS command injection | 20 | 7.85 |
Look at that last row: 20 KEVs for command injection. That’s 20 vulnerabilities in production systems that attackers are actively using to compromise organizations.
As a Go developer building web applications and APIs, I went looking for a unified library that would help me defend against all of these. Here’s what I found:
- bluemonday: Excellent for HTML/XSS sanitization, but nothing else
- go-sanitize: General purpose, but incomplete coverage
- sqlx/database/sql: Parameterized queries help, but don’t validate identifiers
- Standard library: Requires knowing exactly what to call and when
No unified solution existed. So I built one.
Introducing go-safeinput
go-safeinput is a context-aware input sanitization library for Go that addresses the top injection vulnerabilities in the MITRE CWE Top 25. It’s built with three core principles:
1. Context-Aware Sanitization
Different output contexts require different sanitization strategies. What’s safe for HTML isn’t safe for SQL, and what’s safe for SQL isn’t safe for file paths.
package main
import (
"fmt"
"github.com/ravisastryk/go-safeinput"
)
func main() {
s := safeinput.Default()
userInput := "<script>alert('xss')</script>Hello!"
// HTML context: strip dangerous tags
htmlSafe, _ := s.Sanitize(userInput, safeinput.HTMLBody)
fmt.Println(htmlSafe) // Output: Hello!
// File path context: prevent traversal
pathInput := "../../etc/passwd"
_, err := s.Sanitize(pathInput, safeinput.FilePath)
fmt.Println(err) // Output: path traversal detected
// SQL identifier context: validate pattern
sqlInput := "users; drop tbl--"
_, err = s.Sanitize(sqlInput, safeinput.SQLIdentifier)
fmt.Println(err) // Output: invalid SQL identifier
}
2. Defense-in-Depth
Even when using prepared statements (and you should always use prepared statements for values), there are cases where you need to dynamically construct SQL — like table names or column names. These can’t be parameterized.
// The problem: table names can't be parameterized
tableName := r.URL.Query().Get("table")
db.Query("SELECT * FROM " + tableName + " WHERE id = ?", id) // Dangerous!
// The solution: validate the identifier first
s := safeinput.Default()
// Defense layer 1: Validate identifier pattern
tableName, err := s.Sanitize(userInput, safeinput.SQLIdentifier)
if err != nil {
return fmt.Errorf("invalid table name: %w", err)
}
// Defense layer 2: Parameterized query for values
db.Query("SELECT * FROM " + tableName + " WHERE id = ?", id) // Safe!
3. Zero External Dependencies
The entire library uses only Go’s standard library. No transitive dependencies, no supply chain risks, no version conflicts.
module github.com/ravisastryk/go-safeinput
go 1.24
That’s the entire go.mod. Your security library shouldn't add attack surface.
Implementation Deep Dive
XSS Prevention (CWE-79)
Cross-site scripting has held the #1 spot for two consecutive years with a score of 60.38 — more than double the #2 vulnerability. The HTML sanitizer strips dangerous elements while preserving safe content:
// html/sanitizer.go
var (
scriptPattern = regexp.MustCompile(`(?i)<script[\s\S]*?</script>`)
stylePattern = regexp.MustCompile(`(?i)<style[\s\S]*?</style>`)
iframePattern = regexp.MustCompile(`(?i)<iframe[\s\S]*?</iframe>`)
eventPattern = regexp.MustCompile(`(?i)\s+on\w+\s*=\s*["'][^"']*["']`)
)
func (s *Sanitizer) SanitizeBody(input string) string {
result := scriptPattern.ReplaceAllString(input, "")
result = stylePattern.ReplaceAllString(result, "")
result = iframePattern.ReplaceAllString(result, "")
result = eventPattern.ReplaceAllString(result, "")
// ... more patterns
return strings.TrimSpace(result)
}
For attribute values, we escape rather than strip:
func (s *Sanitizer) SanitizeAttribute(input string) string {
return html.EscapeString(input) // Uses Go's standard library
}
Path Traversal Prevention (CWE-22)
With 10 KEVs, path traversal is one of the most actively exploited vulnerabilities. The path sanitizer blocks multiple attack vectors:
// path/sanitizer.go
var blockedSequences = []string{
"..", "../", "..\\", // Basic traversal
"..%2f", "..%5c", "%2e%2e", // URL-encoded
"..%252f", "..%255c", // Double-encoded
".%2e", "%2e.", // Mixed encoding
}
func (s *Sanitizer) Sanitize(input string) (string, error) {
// Block null bytes (truncation attacks)
if strings.ContainsRune(input, 0) {
return "", ErrInvalidCharacter
}
// Check for traversal sequences
lower := strings.ToLower(input)
for _, seq := range blockedSequences {
if strings.Contains(lower, seq) {
return "", ErrPathTraversal
}
}
// Ensure path stays within base directory
if s.basePath != "" {
absResult, _ := filepath.Abs(filepath.Join(s.basePath, cleaned))
if !strings.HasPrefix(absResult, absBase) {
return "", ErrOutsideBasePath
}
}
return cleaned, nil
}
SQL Injection Prevention (CWE-89)
SQL injection moved up to #2 this year. While parameterized queries handle values, identifiers need pattern validation:
// sql/sanitizer.go
var dangerousPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)(\bor\b|\band\b)\s*[\d'"]+\s*=\s*[\d'"]+`),
regexp.MustCompile(`(?i)--`),
regexp.MustCompile(`(?i)/\*`),
regexp.MustCompile(`(?i)\bunion\b.*\bselect\b`),
regexp.MustCompile(`(?i)\b(benchmark|sleep|waitfor)\b`),
}
var validIdentifier = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
func (s *Sanitizer) SanitizeIdentifier(input string) (string, error) {
if !validIdentifier.MatchString(input) {
return "", ErrInvalidIdentifier
}
if reservedWords[strings.ToLower(input)] {
return "", ErrReservedWord
}
return input, nil
}
Command Injection Prevention (CWE-78)
With a staggering 20 KEVs, OS command injection is the most actively exploited vulnerability on this list. The shell argument sanitizer uses an allowlist approach:
// safeinput.go
func SanitizeShellArg(input string) string {
var b strings.Builder
for _, r := range input {
if isAllowedShellChar(r) {
b.WriteRune(r)
}
}
return b.String()
}
func isAllowedShellChar(r rune) bool {
return (r >= 'a' && r <= 'z') ||
(r >= 'A' && r <= 'Z') ||
(r >= '0' && r <= '9') ||
r == '-' || r == '_' || r == '.' || r == '/'
}
Production-Ready Features
CI/CD
The repository includes GitHub Actions workflows for:
- golangci-lint: 20+ linters including gosec, staticcheck, and revive
- gosec: Security-focused static analysis with SARIF output
- govulncheck: Go vulnerability database checking
- CodeQL: GitHub’s semantic security analysis
- Trivy: Container vulnerability scanning
Docker Support
Multi-stage Dockerfile for minimal production images:
FROM golang:1.24-alpine AS builder
# Build and test
FROM scratch AS production
COPY --from=builder /app/bin/safeinput /safeinput
USER 65534:65534
ENTRYPOINT ["/safeinput"]
Test Coverage
The library maintains >90% test coverage with tests for:
- Normal inputs
- Edge cases
- Known attack vectors
- URL-encoded bypasses
- Double-encoding attempts
- Null byte injections
Getting Started
Installation:
go get github.com/ravisastryk/go-safeinput
Basic usage:
package main
import (
"log"
"github.com/ravisastryk/go-safeinput"
)
func main() {
// Create sanitizer with secure defaults
s := safeinput.Default()
// Or customize configuration
s = safeinput.New(safeinput.Config{
MaxInputLength: 5000,
StrictMode: true,
StripNullBytes: true,
BasePath: "/var/www/uploads",
})
// Sanitize based on context
safe, err := s.Sanitize(userInput, safeinput.HTMLBody)
if err != nil {
log.Printf("Invalid input: %v", err)
return
}
// Use the sanitized value
renderHTML(safe)
}
What’s Next
The library is actively maintained with plans for:
- CWE-94: Code injection prevention for template engines
- CWE-918: SSRF prevention helpers
- Framework middleware: Drop-in integration for Gin, Echo, and Chi
Conclusion
MITRE’s 2025 CWE Top 25 makes one thing clear: injection vulnerabilities aren’t going away. They’ve been at the top of these lists for years, and attackers continue to exploit them successfully.
As developers, we have a responsibility to build secure software. Libraries like go-safeinput won’t solve every security problem, but they can eliminate entire classes of vulnerabilities when used correctly.
The code is open source, the tests are comprehensive, and the API is simple. Give it a try:
- GitHub: github.com/ravisastryk/go-safeinput
- Documentation: pkg.go.dev/github.com/ravisastryk/go-safeinput
References
- MITRE 2025 CWE Top 25 Most Dangerous Software Weaknesses
- CISA Announcement: 2025 CWE Top 25
- MITRE Unveils 2025 List of Top 25 Most Dangerous Software Weaknesses
- CISA Secure by Design
Ravi Sastry Kadali is a security-focused software developer. Connect on LinkedIn or follow on GitHub.
Opinions expressed by DZone contributors are their own.
Comments