Overview
The CVE-2026-25116 vulnerability in Runtipi describes an unauthenticated path traversal flaw in the UserConfigController. By abusing insecure URN parsing and allowing an attacker to influence the file path used for critical configuration (notably docker-compose.yml), an adversary could overwrite the stack configuration. This could cascade into Remote Code Execution (RCE) on restart, enabling host filesystem compromise. The issue maps to CWE-22 (Path Traversal) and CWE-306 (Missing Input Validation). In real Go (Gin) deployments, similar risks arise when user input is used to compute file paths or command targets without strict validation, enabling attackers to redirect writes to arbitrary files. The vulnerability referenced CVE-2026-25116, with vulnerable versions 4.5.0 through 4.7.1 and a fix in 4.7.2. To mitigate, upgrade to 4.7.2 or apply defensive coding patterns that never trust client-provided paths.
Affected Versions
4.5.0 - 4.7.1 vulnerable; 4.7.2 fixed
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"path/filepath"
"os"
"strings"
"github.com/gin-gonic/gin"
)
const baseDir = "./config"
// Vulnerable (unsafe) pattern: path is derived directly from user input
func vulnerableWriteConfig(c *gin.Context) {
file := c.Query("file")
// Insecure: user controls target path
target := filepath.Join(baseDir, file)
if err := os.MkdirAll(baseDir, 0755); err != nil {
c.String(http.StatusInternalServerError, "server error")
return
}
if err := os.WriteFile(target, []byte("vulnerable config"), 0644); err != nil {
c.String(http.StatusInternalServerError, "write error")
return
}
c.String(http.StatusOK, "Wrote %s", target)
}
// Fixed (safe) pattern: validate and constrain input to prevent path traversal
func safeWriteConfig(c *gin.Context) {
file := c.Query("file")
allowed := map[string]bool{"docker-compose.yml": true, "config.yaml": true}
if !allowed[file] {
c.String(http.StatusBadRequest, "filename not allowed")
return
}
absBase, _ := filepath.Abs(baseDir)
target := filepath.Join(baseDir, file)
absTarget, _ := filepath.Abs(target)
if !strings.HasPrefix(absTarget, absBase) {
c.String(http.StatusBadRequest, "invalid path")
return
}
if err := os.MkdirAll(baseDir, 0755); err != nil {
c.String(http.StatusInternalServerError, "server error")
return
}
if err := os.WriteFile(absTarget, []byte("safe config"), 0644); err != nil {
c.String(http.StatusInternalServerError, "write error")
return
}
c.String(http.StatusOK, "Wrote %s", absTarget)
}
func main() {
r := gin.Default()
r.GET("/vulnerable/write-config", vulnerableWriteConfig)
r.GET("/fixed/write-config", safeWriteConfig)
r.Run(":8080")
}