{"record":{"id":"61d4b550db4d7137","repo":"Tencent/WeKnora","slug":"invalid-source-path-w-61d4b5","errorCode":null,"errorMessage":"invalid source path: %w","messagePattern":"invalid source path: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/file/local.go","lineNumber":176,"sourceCode":"// The destination uses the same layout as SaveFile (baseDir/{tenantID}/{knowledgeID}/{unique}{ext}),\n// and the copy is a real byte-for-byte copy (no hardlink) so deleting the source\n// never affects it. Returns ErrCrossBackendCopy when srcPath is not a local path.\nfunc (s *localFileService) CopyFile(ctx context.Context,\n\tsrcPath string, tenantID uint64, knowledgeID string,\n) (string, error) {\n\t// Only local paths are accepted. A provider scheme other than local://\n\t// (e.g. s3://, minio://) means a cross-backend copy, which this service\n\t// does not support. Legacy bare/absolute paths have no scheme and pass.\n\tif i := strings.Index(srcPath, \"://\"); i >= 0 && srcPath[:i+3] != localScheme {\n\t\treturn \"\", fmt.Errorf(\"local file service cannot copy %q: %w\", srcPath, ErrCrossBackendCopy)\n\t}\n\n\t// Validate and resolve the source path under baseDir (same guard as GetFile).\n\tsrcCandidate := s.normalizePathForBase(srcPath)\n\tsrcResolved, err := secutils.SafePathUnderBase(s.baseDir, srcCandidate)\n\tif err != nil {\n\t\tlogger.Errorf(ctx, \"Path traversal denied for CopyFile src: %v\", err)\n\t\treturn \"\", fmt.Errorf(\"invalid source path: %w\", err)\n\t}\n\n\t// Build destination path with the knowledge-owned layout.\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 CopyFile dir: %v\", err)\n\t\treturn \"\", fmt.Errorf(\"invalid path: %w\", err)\n\t}\n\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create directory: %w\", err)\n\t}\n\n\text := filepath.Ext(srcPath)\n\tfilename := fmt.Sprintf(\"%d%s\", time.Now().UnixNano(), ext)\n\tdstPath := filepath.Join(dir, filename)\n\n\tsrc, err := os.Open(srcResolved)\n\tif err != nil {","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/file/local.go#L158-L194","documentation":"CopyFile wraps the error from secutils.SafePathUnderBase for the SOURCE path when it does not resolve inside baseDir — the same traversal guard GetFile applies. The copy is refused before reading anything. The wrapped error identifies the path violation.","triggerScenarios":"Calling CopyFile with a source path containing '..' escaping baseDir, an absolute path outside baseDir, a bare legacy path that normalizes outside the base, or a path from a differently-configured instance.","commonSituations":"Legacy DB rows with absolute paths predating a baseDir change; client-supplied source paths passed through unvalidated; baseDir moved (e.g. /var/data -> /data) making old stored paths resolve outside the new base.","solutions":["Ensure the source is the relative local:// path originally returned by SaveFile","Re-baseline stored paths after any baseDir change (migration script stripping the old prefix)","Reject '..'/absolute paths at the API layer before invoking CopyFile","Verify LOCAL_STORAGE_BASE_DIR matches the one used when the source was saved"],"exampleFix":"// before: legacy absolute path stored in DB\nsrc := record.StoragePath // /var/data/old/uploads/a.pdf\n// after: migrate to relative path at read time\nsrc := strings.TrimPrefix(record.StoragePath, oldBaseDir)\ncopied, err := localSvc.CopyFile(ctx, src, tenantID, knowledgeID)","handlingStrategy":"validation","validationCode":"func validSourcePath(p string) bool {\n\treturn p != \"\" && !filepath.IsAbs(p) && !strings.Contains(p, \"..\") &&\n\t\t(!strings.Contains(p, \"://\") || strings.HasPrefix(p, \"local://\"))\n}","typeGuard":null,"tryCatchPattern":"copied, err := svc.CopyFile(ctx, src, tenantID, kid)\nif err != nil {\n\tif errors.Is(err, secutils.ErrPathTraversal) {\n\t\treturn fmt.Errorf(\"bad source path: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Migrate stored paths after any baseDir change so they stay relative","Never copy from client-supplied path strings; use stored local:// paths","Validate source paths before calling CopyFile","Keep LOCAL_STORAGE_BASE_DIR identical across deployments"],"tags":["go","path-traversal","security","copy"],"backgroundTag":"path-traversal-denied","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}