{"record":{"id":"a2ea20f4a865766f","repo":"Tencent/WeKnora","slug":"invalid-path-w","errorCode":null,"errorMessage":"invalid path: %w","messagePattern":"invalid path: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/application/service/file/local.go","lineNumber":62,"sourceCode":"\t\texternalURL: strings.TrimRight(externalURL, \"/\"),\n\t}\n}\n\n// SaveFile stores an uploaded file to the local file system\n// The file is stored in a directory structure: baseDir/tenantID/knowledgeID/filename\n// Returns the full file path or an error if saving fails\nfunc (s *localFileService) SaveFile(ctx context.Context,\n\tfile *multipart.FileHeader, tenantID uint64, knowledgeID string,\n) (string, error) {\n\tlogger.Info(ctx, \"Starting to save file locally\")\n\tlogger.Infof(ctx, \"File information: name=%s, size=%d, tenant ID=%d, knowledge ID=%s\",\n\t\tfile.Filename, file.Size, tenantID, knowledgeID)\n\n\t// Create storage directory with tenant and knowledge ID\n\tdir := filepath.Join(s.baseDir, fmt.Sprintf(\"%d\", tenantID), knowledgeID)\n\tif _, err := secutils.SafePathUnderBase(s.baseDir, dir); err != nil {\n\t\tlogger.Errorf(ctx, \"Path traversal denied for SaveFile dir: %v\", err)\n\t\treturn \"\", fmt.Errorf(\"invalid path: %w\", err)\n\t}\n\tlogger.Infof(ctx, \"Creating directory: %s\", dir)\n\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\tlogger.Errorf(ctx, \"Failed to create directory: %v\", err)\n\t\treturn \"\", fmt.Errorf(\"failed to create directory: %w\", err)\n\t}\n\n\t// Generate unique filename using timestamp\n\text := filepath.Ext(file.Filename)\n\tfilename := fmt.Sprintf(\"%d%s\", time.Now().UnixNano(), ext)\n\tfilePath := filepath.Join(dir, filename)\n\tlogger.Infof(ctx, \"Generated file path: %s\", filePath)\n\n\t// Open source file for reading\n\tlogger.Info(ctx, \"Opening source file\")\n\tsrc, err := file.Open()\n\tif err != nil {\n\t\tlogger.Errorf(ctx, \"Failed to open source file: %v\", err)","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/file/local.go#L44-L80","documentation":"localFileService.SaveFile wraps SafePathUnderBase failures with \"invalid path: %w\" when the constructed storage directory (baseDir/tenantID/knowledgeID) escapes the configured base directory. SafePathUnderBase canonicalizes both paths and refuses any path not strictly under baseDir, blocking path-traversal uploads. knowledgeID is the main attacker-controlled component.","triggerScenarios":"SaveFile called with a knowledgeID containing \"../\", absolute-path segments, or a baseDir that canonicalizes outside itself (e.g. baseDir containing symlinks or relative segments) so that filepath.Join produces a path escaping the base.","commonSituations":"Client-supplied knowledge ID passed unsanitized from an HTTP handler; knowledge IDs with slashes or dot-dot from a legacy importer; baseDir configured with trailing relative components or symlinked subpaths that resolve outside the base.","solutions":["Sanitize knowledgeID before SaveFile: restrict it to a safe charset (UUID/alphanumeric/dash) and reject any value containing \"/\", \"\\\\\", or \"..\".","Configure baseDir as a fully-resolved absolute path without symlinks (filepath.EvalSymlinks) so validation and the real layout agree.","Log the offending knowledgeID and return HTTP 400 — treat as malicious input rather than retrying.","If IDs must contain separators, map them to a flat safe form (e.g. hash or url-encode) before building the directory."],"exampleFix":"// before\nsvc.SaveFile(ctx, fh, 1, r.URL.Query().Get(\"knowledgeId\")) // \"../../evil\"\n// after\nid := r.URL.Query().Get(\"knowledgeId\")\nif !regexp.MustCompile(`^[A-Za-z0-9-]+$`).MatchString(id) {\n    http.Error(w, \"invalid knowledge id\", 400); return\n}\nsvc.SaveFile(ctx, fh, 1, id)","handlingStrategy":"validation","validationCode":"var safeID = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)\nfunc safeKnowledgeID(id string) bool { return safeID.MatchString(id) }","typeGuard":null,"tryCatchPattern":"if _, err := svc.SaveFile(ctx, fh, tenantID, knowledgeID); err != nil {\n\tif strings.Contains(err.Error(), \"invalid path\") {\n\t\thttp.Error(w, \"invalid knowledge id\", http.StatusBadRequest)\n\t\treturn\n\t}\n}","preventionTips":["Whitelist-validate every path component derived from user input (tenant/knowledge IDs)","Configure baseDir as an absolute, symlink-free path","Treat 'invalid path' as a security signal: log and alert on repeated attempts"],"tags":["path-traversal","security","local-storage","input-validation","go"],"backgroundTag":"path-traversal-denied","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}