siyuan-note/siyuan · error

resource path [%s] is not in workspace

Error message

resource path [%s] is not in workspace

What it means

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).

Source

Thrown at kernel/model/export.go:805

	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 251596fc0d)

Solutions

  1. Sanitize resourcePaths before calling ExportResources — strip '..', ensure they are relative to the workspace and resolve inside it.
  2. If the workspace moved, re-open documents from the new workspace location so asset paths regenerate correctly.
  3. Reject absolute paths and traversal segments at the caller/API boundary.
  4. Audit plugin-supplied paths against util.IsAbsPathInWorkspace before forwarding them.

Example fix

// before — caller passes raw, possibly-traversing paths
model.ExportResources(resourcePaths, name)
// after — sanitize at the boundary
var safe []string
for _, p := range resourcePaths {
    full := filepath.Join(util.WorkspaceDir, p)
    if !util.IsAbsPathInWorkspace(full) {
        continue // or return an error
    }
    safe = append(safe, p)
}
model.ExportResources(safe, name)
Defensive patterns

Strategy: validation

Validate before calling

// Validate each path resolves strictly inside the workspace before exporting
for _, p := range resourcePaths {
    full := filepath.Join(util.WorkspaceDir, p)
    if !util.IsAbsPathInWorkspace(full) {
        return fmt.Errorf("reject path outside workspace: %s", p)
    }
}

Type guard

// allPathsInWorkspace reports whether every path resolves inside the workspace.
func allPathsInWorkspace(paths []string) bool {
    for _, p := range paths {
        if !util.IsAbsPathInWorkspace(filepath.Join(util.WorkspaceDir, p)) {
            return false
        }
    }
    return true
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/2bd0cdf32a3ea42c. Report an issue: GitHub.