siyuan-note/siyuan · error

resource path [ ] is not in workspace

Error message

resource path [%s] is not in workspace

What it means

ExportResources validates that every requested resource path resolves to an absolute path inside the workspace. If filepath.Join(util.WorkspaceDir, resourcePath) escapes the workspace, it logs and returns 'resource path [%s] is not in workspace' — a workspace-escape guard against arbitrary file reads.

Solutions

  1. Pass resource paths relative to the workspace data dir (e.g. 'assets/foo.png', 'notebooks/...') without '..' segments
  2. Sanitize input with filepath.Clean and reject paths containing '..' before calling
  3. Verify with util.IsAbsPathInWorkspace(filepath.Join(util.WorkspaceDir, p)) in the caller

Example fix

// before
model.ExportResources([]string{"/etc/passwd"})
// after
p := filepath.Clean("assets/img.png") // relative, inside workspace
model.ExportResources([]string{p})
Defensive patterns

Strategy: validation

Validate before calling

func safeResource(p string) bool {
    full := filepath.Join(util.WorkspaceDir, p)
    return util.IsAbsPathInWorkspace(full) && !strings.Contains(filepath.Clean(p), "..")
}

Type guard

func inWorkspace(p string) bool {
    return util.IsAbsPathInWorkspace(filepath.Join(util.WorkspaceDir, p))
}

Try / catch

if _, err := model.ExportResources(paths); err != nil {
    // path escape: filter paths with safeResource and retry
}

Prevention

When it happens

Trigger: Passing relative paths that traverse outside the workspace (e.g. '../..' segments), absolute-ish resource paths, or paths from another workspace into ExportResources.

Common situations: Scripting exports from user-supplied paths; callers concatenating external paths; workspace relocated so cached relative paths no longer match.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/2bd0cdf32a3ea42c. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/export.go:881

	zipFilePath := filepath.Join(exportBasePath, exportID+"-"+zipFileName)
	if err = os.MkdirAll(exportFolderPath, 0755); err != nil {
		logging.LogErrorf("create export temp folder failed: %s", err)
		return
	}
	defer func() {
		os.RemoveAll(exportFolderPath)
		if err != nil {
			os.Remove(zipFilePath)
			os.Remove(zipFilePath + ".partial")
		}
	}()

	// 将需要导出的文件/文件夹复制到临时文件夹
	for _, resourcePath := range resourcePaths {
		resourceFullPath := filepath.Join(util.WorkspaceDir, resourcePath) // 资源完整路径
		if !util.IsAbsPathInWorkspace(resourceFullPath) {
			logging.LogErrorf("resource path [%s] is not in workspace", resourceFullPath)
			err = errors.New("resource path [" + resourcePath + "] is not in workspace")
			return
		}

		resourceBaseName := filepath.Base(resourceFullPath)                   // 资源名称
		resourceCopyPath := filepath.Join(exportFolderPath, resourceBaseName) // 资源副本完整路径
		if err = copyExportResource(resourceFullPath, resourceCopyPath); err != nil {
			logging.LogErrorf("copy resource will be exported from [%s] to [%s] failed: %s", resourcePath, resourceCopyPath, err)
			err = fmt.Errorf(Conf.Language(14), err.Error())
			return
		}
	}

	zipPartialPath := zipFilePath + ".partial"
	zip, err := gulu.Zip.Create(zipPartialPath)
	if err != nil {
		logging.LogErrorf("create export zip [%s] failed: %s", zipFilePath, err)
		return
	}

View on GitHub (pinned to 9f775e8a12)