{"record":{"id":"1b7cd8471d1fd64d","repo":"Tencent/WeKnora","slug":"invalid-file-path-w-1b7cd8","errorCode":null,"errorMessage":"invalid file path: %w","messagePattern":"invalid file path: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/file/local.go","lineNumber":118,"sourceCode":"\n\tlogger.Infof(ctx, \"File saved successfully: %s\", filePath)\n\t// Return provider:// path format: local://{relative_path}\n\trelPath, _ := filepath.Rel(s.baseDir, filePath)\n\treturn localScheme + filepath.ToSlash(relPath), nil\n}\n\n// GetFile retrieves a file from the local file system by its path\n// Returns a ReadCloser for reading the file content\n// Supports both provider scheme: local://{relative_path} and legacy absolute paths.\n// 路径必须在 baseDir 下，防止路径遍历（如 ../../）\nfunc (s *localFileService) GetFile(ctx context.Context, filePath string) (io.ReadCloser, error) {\n\tlogger.Infof(ctx, \"Getting file: %s\", filePath)\n\n\tcandidate := s.normalizePathForBase(filePath)\n\tresolved, err := secutils.SafePathUnderBase(s.baseDir, candidate)\n\tif err != nil {\n\t\tlogger.Errorf(ctx, \"Path traversal denied for GetFile: %v\", err)\n\t\treturn nil, fmt.Errorf(\"invalid file path: %w\", err)\n\t}\n\n\tfile, err := os.Open(resolved)\n\tif err != nil {\n\t\t// baseDir/resolved are logged so a storage base-dir mismatch (e.g.\n\t\t// writer and reader started with different LOCAL_STORAGE_BASE_DIR)\n\t\t// is immediately visible instead of just \"no such file or directory\".\n\t\tlogger.Errorf(ctx, \"Failed to open file: baseDir=%s resolvedPath=%s err=%v\", s.baseDir, resolved, err)\n\t\treturn nil, fmt.Errorf(\"failed to open file: %w\", err)\n\t}\n\n\tlogger.Info(ctx, \"File opened successfully\")\n\treturn file, nil\n}\n\n// DeleteFile removes a file from the local file system\n// Returns an error if deletion fails\n// 路径必须在 baseDir 下，防止路径遍历（如 ../../）","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/file/local.go#L100-L136","documentation":"GetFile wraps the error from secutils.SafePathUnderBase when the requested filePath does not resolve to a location inside the storage base directory. This is the path-traversal guard: paths like '../secrets.txt' or absolute paths outside baseDir are rejected before any file is opened. The wrapped error explains which path check failed.","triggerScenarios":"Calling GetFile with a path containing '..' segments that escape baseDir, an absolute path pointing outside baseDir, a different-provider scheme (e.g. s3://...) reaching the local service, or a symlink trick that resolves outside the base.","commonSituations":"Storing raw client-supplied filenames and passing them straight to GetFile; storage rows containing paths written by a different backend or with a different LOCAL_STORAGE_BASE_DIR; URL-encoded traversal (%2e%2e%2f) not decoded before validation.","solutions":["Remove '..' segments and ensure the stored path is relative to baseDir before calling GetFile","Verify the record was written by the same local service with the same LOCAL_STORAGE_BASE_DIR (provider scheme must be local://)","Decode/sanitize URL-encoded paths before lookup and reject suspicious input at the API boundary","Use the returned local:// path from SaveFile rather than constructing paths manually"],"exampleFix":"// before: unsafe, raw user input\nf, err := svc.GetFile(ctx, r.URL.Query().Get(\"path\"))\n// after: validate the path shape first\np := r.URL.Query().Get(\"path\")\nif strings.Contains(p, \"..\") || filepath.IsAbs(p) {\n\thttp.Error(w, \"invalid path\", http.StatusBadRequest)\n\treturn\n}\nf, err := svc.GetFile(ctx, p)","handlingStrategy":"validation","validationCode":"func safeLocalPath(p string) bool {\n\tif strings.Contains(p, \"..\") || filepath.IsAbs(p) {\n\t\treturn false\n\t}\n\tif i := strings.Index(p, \"://\"); i >= 0 && p[:i+3] != \"local://\" {\n\t\treturn false\n\t}\n\treturn true\n}","typeGuard":"func isLocalPath(p string) bool {\n\treturn !filepath.IsAbs(p) && !strings.Contains(p, \"..\")\n}","tryCatchPattern":"file, err := svc.GetFile(ctx, path)\nif err != nil {\n\tif errors.Is(err, secutils.ErrPathTraversal) {\n\t\thttp.Error(w, \"invalid path\", http.StatusBadRequest)\n\t\treturn\n\t}\n\thttp.Error(w, \"internal error\", http.StatusInternalServerError)\n}","preventionTips":["Never pass raw user input as a storage path; use paths returned by SaveFile","Decode URL-encoded input before validation","Keep LOCAL_STORAGE_BASE_DIR consistent across writer and reader instances","Pen-test traversal patterns (../, %2e%2e, symlinks) in CI"],"tags":["go","path-traversal","security","validation"],"backgroundTag":"path-traversal-denied","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}