{"record":{"id":"f14b5fc6bb677e00","repo":"router-for-me/CLIProxyAPI","slug":"invalid-name","errorCode":null,"errorMessage":"invalid name","messagePattern":"invalid name","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/api/handlers/management/auth_files_crud.go","lineNumber":345,"sourceCode":"\tout := make([]string, 0, len(names))\n\tfor _, name := range names {\n\t\tname = strings.TrimSpace(name)\n\t\tif name == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[name]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tseen[name] = struct{}{}\n\t\tout = append(out, name)\n\t}\n\treturn out\n}\n\nfunc (h *Handler) deleteAuthFileByName(ctx context.Context, name string) (string, int, error) {\n\tname = strings.TrimSpace(name)\n\tif isUnsafeAuthFileName(name) {\n\t\treturn \"\", http.StatusBadRequest, fmt.Errorf(\"invalid name\")\n\t}\n\n\ttargetPath := filepath.Join(h.cfg.AuthDir, filepath.Base(name))\n\ttargetID := \"\"\n\tif targetAuth := h.findAuthForDelete(name); targetAuth != nil {\n\t\tif !isPluginVirtualSourceDelete(name, targetAuth) {\n\t\t\treturn filepath.Base(name), http.StatusConflict, errPluginVirtualAuth\n\t\t}\n\t\ttargetID = strings.TrimSpace(targetAuth.ID)\n\t\tif path := strings.TrimSpace(authAttribute(targetAuth, \"path\")); path != \"\" {\n\t\t\ttargetPath = path\n\t\t}\n\t}\n\tif !filepath.IsAbs(targetPath) {\n\t\tif abs, errAbs := filepath.Abs(targetPath); errAbs == nil {\n\t\t\ttargetPath = abs\n\t\t}\n\t}","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/api/handlers/management/auth_files_crud.go#L327-L363","documentation":"deleteAuthFileByName first trims the requested name and runs isUnsafeAuthFileName (auth_files.go:573), which rejects empty names, any name containing `/` or `\\\\`, and Windows volume names (`C:`). A rejection yields HTTP 400 `invalid name` — this is the path-traversal/empty-name guard for the delete endpoint, not a signal about the file itself.","triggerScenarios":"DELETE with name like `../auths/secret.json`, `dir/file.json`, `C:\\\\auths\\\\x.json`, empty string after trimming, or a path-style identifier from another API surface; clients passing a full path where only a basename is allowed.","commonSituations":"Automation using absolute paths from the host filesystem; accidental inclusion of leading `/`; names built by joining directories; Windows clients sending backslash paths.","solutions":["Send only the base filename: strip directories before calling delete (name = filepath.Base(name)).","Reject empty/whitespace names client-side before issuing the request.","Never send absolute paths or drive letters; the handler always joins with the configured authDir."],"exampleFix":"// before\nname := \"/etc/cli-proxy-api/auths/my.json\"\n// after\nname := filepath.Base(\"/etc/cli-proxy-api/auths/my.json\") // \"my.json\"","handlingStrategy":"validation","validationCode":"func safeAuthFileName(name string) bool {\n    n := strings.TrimSpace(name)\n    return n != \"\" && !strings.ContainsAny(n, \"/\\\\\") && filepath.VolumeName(n) == \"\"\n}","typeGuard":"func isInvalidNameError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"invalid name\")\n}","tryCatchPattern":null,"preventionTips":["Always reduce to filepath.Base(name) before calling the delete API.","Reject empty names client-side.","Never send absolute paths, drive letters, or directory components."],"tags":["management-api","validation","path-traversal","security"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}