{"record":{"id":"d55f50188c7b0488","repo":"MHSanaei/3x-ui","slug":"invalid-filename","errorCode":null,"errorMessage":"invalid filename","messagePattern":"invalid filename","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/web/controller/server.go","lineNumber":343,"sourceCode":"\tconfigJson, err := a.serverService.GetConfigJson()\n\tif err != nil {\n\t\tjsonMsg(c, I18nWeb(c, \"pages.index.getConfigError\"), err)\n\t\treturn\n\t}\n\tjsonObj(c, configJson, nil)\n}\n\n// getDb downloads the database file.\nfunc (a *ServerController) getDb(c *gin.Context) {\n\tdb, err := a.serverService.GetDb()\n\tif err != nil {\n\t\tjsonMsg(c, I18nWeb(c, \"pages.index.getDatabaseError\"), err)\n\t\treturn\n\t}\n\n\tfilename := a.serverService.BackupFilename(c.Request.Host)\n\tif !filenameRegex.MatchString(filename) {\n\t\t_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf(\"invalid filename\"))\n\t\treturn\n\t}\n\n\tc.Header(\"Content-Type\", \"application/octet-stream\")\n\tc.Header(\"Content-Disposition\", \"attachment; filename=\"+filename)\n\t_, _ = c.Writer.Write(db)\n}\n\n// getMigration downloads a cross-engine migration file: a .dump on SQLite or a\n// .db SQLite database on PostgreSQL, so the data can seed the other backend.\nfunc (a *ServerController) getMigration(c *gin.Context) {\n\tdata, filename, err := a.serverService.GetMigration()\n\tif err != nil {\n\t\tjsonMsg(c, I18nWeb(c, \"pages.index.getDatabaseError\"), err)\n\t\treturn\n\t}\n\tif !filenameRegex.MatchString(filename) {\n\t\t_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf(\"invalid filename\"))","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/controller/server.go#L325-L361","documentation":"ServerController.getDb (database download) builds the attachment filename from serverService.BackupFilename(c.Request.Host) and re-validates it against the package-level filenameRegex before setting Content-Disposition; a mismatch aborts with 400 'invalid filename'. Because the filename is derived from the Host header (and date/db engine), a failure means the Host header produced characters outside the safe pattern — this is a defense against header injection via Host.","triggerScenarios":"GET the DB backup endpoint with a Host header containing characters outside filenameRegex — underscores in a raw-IP Host, port suffixes, or exotic Unicode from a proxy layer.","commonSituations":"Accessing the panel through a reverse proxy that forwards an odd Host (e.g. 'panel_my-host' or 'host:443' variants); requesting via curl with a crafted --resolve; older proxies appending ports.","solutions":["Send a clean Host header (plain hostname like panel.example.com, no port/underscore) when downloading the DB.","Fix the reverse proxy to set proxy_set_header Host $host without extra decorations.","Check filenameRegex in the controller's file to see exactly which characters are tolerated and adjust the Host accordingly."],"exampleFix":"# before\ncurl -H 'Host: panel_my.host:443' .../server/getDb   # 400 invalid filename\n\n# after\ncurl -H 'Host: panel.example.com' .../server/getDb","handlingStrategy":"validation","validationCode":"// client side: send a clean Host\nawait fetch(url, { headers: { Host: 'panel.example.com' } }) // or ensure proxy forwards a clean Host\n// server-side harness:\nvar filenameRe = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)\nif !filenameRe.MatchString(host) { /* reject early with clear message */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set proxy_set_header Host $host (no port decoration) in reverse proxies fronting the panel.","Avoid underscores and Unicode in the Host used for downloads.","If downloads 400 with 'invalid filename', check the Host header first."],"tags":["api","http-400","security","header-injection","backup"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}