siyuan-note/siyuan · error

asset path escapes data directory

Error message

asset path escapes data directory

What it means

deferredAssetPathFromFiles builds an absolute path under util.DataDir from a candidate lookup path and verifies it is a strict sub-path of the data directory with gulu.File.IsSubPath. If the resolved candidate escapes DataDir (e.g. via ../ or an absolute path from another location), it returns 'asset path escapes data directory'. This is a path-traversal guard protecting the deferred asset download pipeline from reading or downloading outside the workspace.

Source

Thrown at kernel/model/asset_download_read.go:94

			}
			lookupPath = target
		} else {
			switch {
			case candidate == relativePath, strings.HasSuffix(candidate, "/"+relativePath):
			case strings.HasPrefix(candidate, relativePath+"/"):
				lookupPath = relativePath
			case strings.Contains(candidate, "/"+relativePath+"/"):
				lookupPath = candidate[:strings.Index(candidate, "/"+relativePath+"/")+len(relativePath)+1]
			default:
				continue
			}
		}
		absPath := filepath.Join(util.DataDir, filepath.FromSlash(lookupPath))
		if !includeEncrypted && IsEncryptedAssetPath(absPath) {
			continue
		}
		if !gulu.File.IsSubPath(util.DataDir, absPath) {
			return "", errors.New("asset path escapes data directory")
		}
		if boxID == "" && !IsEncryptedAssetPath(absPath) {
			if _, _, resolveErr := ResolveDataAssetPath(lookupPath); resolveErr != nil {
				return "", resolveErr
			}
		} else {
			resolvedBoxID := boxID
			if resolvedBoxID == "" {
				resolvedBoxID = ExtractBoxIDFromAssetsPath(absPath)
			}
			root, rootErr := ResolveAssetPathWithMissingLeaf(filepath.Join(util.DataDir, resolvedBoxID, "assets"))
			resolved, resolveErr := ResolveAssetPathWithMissingLeaf(absPath)
			notebookRoot, notebookErr := ResolveRealPath(filepath.Join(util.DataDir, resolvedBoxID))
			dataRoot, dataErr := ResolveRealPath(util.DataDir)
			if rootErr != nil || resolveErr != nil || notebookErr != nil || dataErr != nil ||
				!gulu.File.IsSubPath(dataRoot, notebookRoot) ||
				!gulu.File.IsSubPath(notebookRoot, root) || !gulu.File.IsSubPath(root, resolved) {
				return "", errors.New("asset path resolves outside notebook assets directory")

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Fix the asset reference in the document to a workspace-relative assets/... path
  2. Copy the referenced file into the notebook's assets folder and update the link
  3. Sanitize/normalize asset URLs at import time so they never contain .. segments
  4. If a legitimate asset lives outside the workspace, move it into data/ rather than linking across

Example fix

// before (in markdown)
![img](../../../etc/passwd)
// after
![img](assets/passwd.png) // file actually placed under the notebook's assets dir
Defensive patterns

Strategy: validation

Validate before calling

func safeAssetPath(urlPath string) error {
    cleaned := path.Clean("/" + strings.ReplaceAll(urlPath, "\\", "/"))
    if strings.Contains(cleaned, "..") {
        return errors.New("asset url contains traversal segments")
    }
    return nil
}

Try / catch

p, err := model.DeferredAssetPath(urlPath)
if err != nil && strings.Contains(err.Error(), "escapes data directory") {
    log.Warnf("rejected escaping asset link %q", urlPath)
    return nil // treat as unresolvable, skip download
}

Prevention

When it happens

Trigger: A document references an asset URL whose normalized lookup path contains .. segments or resolves outside the workspace (deferredAssetPath / deferredAssetPathFromFiles), including crafted markdown like ![x](../../outside/secret).

Common situations: Markdown imported from external tools contains relative paths that climb above the notebook; a plugin or third-party sync inserted absolute asset links pointing elsewhere on disk; malicious or mistyped asset links in imported documents.

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@8641553a1f (2026-09-11). Data as JSON: /api/errors/4bfcb084c67d8e25. Report an issue: GitHub.