MHSanaei/3x-ui · error
invalid filename: contains unsafe characters or path travers
Error message
invalid filename: contains unsafe characters or path traversal patterns
What it means
ServerController.updateGeofile passes the user-supplied fileName path parameter through serverService.IsValidGeofileName, a traversal/character guard protecting the geo file download path (files like geosite.dat, geoip.dat under the Xray asset dir). Failure returns the localized popover message plus the detail 'invalid filename: contains unsafe characters or path traversal patterns'. This is a security control — do not bypass it.
Source
Thrown at internal/web/controller/server.go:260
// setUpdateChannel toggles whether self-update tracks the rolling dev release.
func (a *ServerController) setUpdateChannel(c *gin.Context) {
dev, err := strconv.ParseBool(c.PostForm("dev"))
if err != nil {
jsonMsg(c, "invalid data", err)
return
}
err = a.settingService.SetDevChannelEnable(dev)
jsonMsg(c, I18nWeb(c, "pages.index.updateChannelChanged"), err)
}
// updateGeofile updates the specified geo file for Xray.
func (a *ServerController) updateGeofile(c *gin.Context) {
fileName := c.Param("fileName")
if fileName != "" && !a.serverService.IsValidGeofileName(fileName) {
jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"),
fmt.Errorf("invalid filename: contains unsafe characters or path traversal patterns"))
return
}
err := a.serverService.UpdateGeofile(fileName)
jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"), err)
}
// stopXrayService stops the Xray service.
func (a *ServerController) stopXrayService(c *gin.Context) {
err := a.serverService.StopXrayService()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.xray.stopError"), err)
websocket.BroadcastXrayState("error", err.Error())
return
}
jsonMsg(c, I18nWeb(c, "pages.xray.stopSuccess"), err)
websocket.BroadcastXrayState("stop", "")
websocket.BroadcastNotification(View on GitHub (pinned to ad32144c42)
Solutions
- Use a plain file name from the allowed set, e.g. geosite.dat, geoip.dat, geosite-ir.dat — no directories, no dots at start.
- If you need a custom geo file, place it in the asset folder manually rather than through this endpoint.
- Check IsValidGeofileName's implementation in serverService for the exact allowed pattern before automating calls.
Example fix
# before GET /panel/api/server/updateGeofile/../x-ui/x-ui.db # blocked # after GET /panel/api/server/updateGeofile/geosite.dat
Defensive patterns
Strategy: validation
Validate before calling
var safeGeofile = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*\.(dat|mmdb|metadb|db|locoalist|dl)$`) // be conservative; mirror serverService rules
func plausibleGeofileName(name string) bool {
return name != "" && !strings.Contains(name, "/") && !strings.Contains(name, "\\") && !strings.Contains(name, "..") && safeGeofile.MatchString(name)
} Type guard
function isSafeGeofileName(name: string): boolean {
return /^[A-Za-z0-9][A-Za-z0-9._-]*\.(dat|mmdb|db)$/.test(name) && !name.includes('..')
} Prevention
- Offer geofile names from a fixed dropdown instead of free text.
- Never accept directory components in this field.
- Remember URL-decoding happens before the check — reject '..' in any encoded form at the edge.
When it happens
Trigger: POST/GET the geofile update endpoint with fileName containing '../', '..\\', absolute paths, slashes, NUL/control bytes, shell metacharacters, or any character outside the allowed geofile-name set.
Common situations: Attempting to refresh a custom geo file with a nested path ('custom/geoip.dat'); URL-encoded traversal (%2e%2e%2f) that decodes to '../'; passing an empty-ish or whitespace name; probing the endpoint.
Related errors
- The new username and password are empty
- tg_id must be a positive integer
- token not found
- invalid metric
- invalid bucket
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/5d5daf8f0f2ef2cc.
Report an issue: GitHub.