siyuan-note/siyuan · error

asset path must be relative to data directory: %s

Error message

asset path must be relative to data directory: %s

What it means

Returned by ResolveDataAssetPath when the path is absolute, has a Windows volume name (C:), or begins with a path separator. ResolveDataAssetPath only accepts paths relative to util.DataDir; absolute inputs are rejected as the first anti-traversal layer. The offending path is interpolated.

Source

Thrown at kernel/model/assets.go:900

	}
	return
}

func GetAssetAbsPath(relativePath string) (string, error) {
	return GetAssetAbsPathWithOpt(relativePath, false)
}

// ResolveDataAssetPath 解析 data 相对资源路径,并确保目标位于全局或普通笔记本的资源目录中。
func ResolveDataAssetPath(assetPath string) (relativePath, absPath string, err error) {
	if assetPath == "" {
		err = errors.New("asset path is required")
		return
	}

	nativePath := filepath.FromSlash(assetPath)
	if filepath.IsAbs(nativePath) || filepath.VolumeName(nativePath) != "" ||
		(len(nativePath) > 0 && os.IsPathSeparator(nativePath[0])) {
		err = fmt.Errorf("asset path must be relative to data directory: %s", assetPath)
		return
	}

	nativePath = filepath.Clean(nativePath)
	absPath = filepath.Join(util.DataDir, nativePath)
	dataRelativePath, relErr := filepath.Rel(util.DataDir, absPath)
	if relErr != nil || dataRelativePath == "." || dataRelativePath == ".." ||
		strings.HasPrefix(dataRelativePath, ".."+string(filepath.Separator)) {
		err = fmt.Errorf("asset path escapes data directory: %s", assetPath)
		return
	}

	parts := strings.Split(filepath.ToSlash(dataRelativePath), "/")
	assetDirIndex := -1
	switch {
	case len(parts) > 1 && parts[0] == "assets":
		assetDirIndex = 0
	case len(parts) > 2 && ast.IsNodeIDPattern(parts[0]):

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Strip leading slashes and volume names at the call site, or reject them with a 400.
  2. Only ever pass data-directory-relative paths (e.g. "assets/x.png" or "<boxID>/assets/x.png").

Example fix

// before
rel, abs, err := model.ResolveDataAssetPath("/data/assets/x.png")

// after — pass a data-relative path
rel, abs, err := model.ResolveDataAssetPath("assets/x.png")
Defensive patterns

Strategy: validation

Validate before calling

np := filepath.FromSlash(assetPath)
if filepath.IsAbs(np) || filepath.VolumeName(np) != "" || (len(np) > 0 && os.IsPathSeparator(np[0])) {
    return fmt.Errorf("asset path must be data-relative: %s", assetPath)
}

Prevention

When it happens

Trigger: Passing "/etc/passwd", "C:\\Windows\\x", "/home/user/data/assets/x.png", or any leading-"/" path. These would bypass the data-dir containment if joined naively.

Common situations: A client sends a full filesystem path instead of a data-relative one; a debug tooling path leaks through; an attempt at path traversal via absolute path.

Related errors


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