{"record":{"id":"2bd0cdf32a3ea42c","repo":"siyuan-note/siyuan","slug":"resource-path-s-is-not-in-workspace","errorCode":null,"errorMessage":"resource path [%s] is not in workspace","messagePattern":"resource path \\[(.+?)\\] is not in workspace","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/export.go","lineNumber":805,"sourceCode":"\tzipFilePath := filepath.Join(exportBasePath, exportID+\"-\"+zipFileName)\n\tif err = os.MkdirAll(exportFolderPath, 0755); err != nil {\n\t\tlogging.LogErrorf(\"create export temp folder failed: %s\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tos.RemoveAll(exportFolderPath)\n\t\tif err != nil {\n\t\t\tos.Remove(zipFilePath)\n\t\t\tos.Remove(zipFilePath + \".partial\")\n\t\t}\n\t}()\n\n\t// 将需要导出的文件/文件夹复制到临时文件夹\n\tfor _, resourcePath := range resourcePaths {\n\t\tresourceFullPath := filepath.Join(util.WorkspaceDir, resourcePath) // 资源完整路径\n\t\tif !util.IsAbsPathInWorkspace(resourceFullPath) {\n\t\t\tlogging.LogErrorf(\"resource path [%s] is not in workspace\", resourceFullPath)\n\t\t\terr = errors.New(\"resource path [\" + resourcePath + \"] is not in workspace\")\n\t\t\treturn\n\t\t}\n\n\t\tresourceBaseName := filepath.Base(resourceFullPath)                   // 资源名称\n\t\tresourceCopyPath := filepath.Join(exportFolderPath, resourceBaseName) // 资源副本完整路径\n\t\tif err = copyExportResource(resourceFullPath, resourceCopyPath); err != nil {\n\t\t\tlogging.LogErrorf(\"copy resource will be exported from [%s] to [%s] failed: %s\", resourcePath, resourceCopyPath, err)\n\t\t\terr = fmt.Errorf(Conf.Language(14), err.Error())\n\t\t\treturn\n\t\t}\n\t}\n\n\tzipPartialPath := zipFilePath + \".partial\"\n\tzip, err := gulu.Zip.Create(zipPartialPath)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"create export zip [%s] failed: %s\", zipFilePath, err)\n\t\treturn\n\t}","sourceCodeStart":787,"sourceCodeEnd":823,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/export.go#L787-L823","documentation":"Returned by ExportResources when a resource path, joined under util.WorkspaceDir, fails util.IsAbsPathInWorkspace — i.e. the resolved absolute path escapes the workspace boundary. This is a security guard against path traversal: the resource path must resolve strictly inside the workspace. Both a log line and a Go error are produced (the error echoes the offending path).","triggerScenarios":"POST /api/export/exportResources with a resourcePaths entry containing '..' segments, an absolute path outside the workspace, or a symlink-laden relative path that resolves outside. Can also occur if the workspace dir is misconfigured/moved so that previously-valid relative paths no longer resolve inside it.","commonSituations":"A malicious or buggy plugin/client passes crafted paths. The workspace was relocated on disk but stale relative paths remain in a document. A path with a leading slash or drive letter is treated as absolute and outside the workspace.","solutions":["Sanitize resourcePaths before calling ExportResources — strip '..', ensure they are relative to the workspace and resolve inside it.","If the workspace moved, re-open documents from the new workspace location so asset paths regenerate correctly.","Reject absolute paths and traversal segments at the caller/API boundary.","Audit plugin-supplied paths against util.IsAbsPathInWorkspace before forwarding them."],"exampleFix":"// before — caller passes raw, possibly-traversing paths\nmodel.ExportResources(resourcePaths, name)\n// after — sanitize at the boundary\nvar safe []string\nfor _, p := range resourcePaths {\n    full := filepath.Join(util.WorkspaceDir, p)\n    if !util.IsAbsPathInWorkspace(full) {\n        continue // or return an error\n    }\n    safe = append(safe, p)\n}\nmodel.ExportResources(safe, name)","handlingStrategy":"validation","validationCode":"// Validate each path resolves strictly inside the workspace before exporting\nfor _, p := range resourcePaths {\n    full := filepath.Join(util.WorkspaceDir, p)\n    if !util.IsAbsPathInWorkspace(full) {\n        return fmt.Errorf(\"reject path outside workspace: %s\", p)\n    }\n}","typeGuard":"// allPathsInWorkspace reports whether every path resolves inside the workspace.\nfunc allPathsInWorkspace(paths []string) bool {\n    for _, p := range paths {\n        if !util.IsAbsPathInWorkspace(filepath.Join(util.WorkspaceDir, p)) {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":null,"preventionTips":["Sanitize resourcePaths at the API/plugin boundary: strip '..' and reject absolute escaping paths.","If the workspace moved, re-open docs from the new location so asset paths regenerate.","Never forward untrusted client-supplied paths straight to ExportResources."],"tags":["security","path-traversal","workspace","export","resources"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}