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
- Fix the asset reference in the document to a workspace-relative assets/... path
- Copy the referenced file into the notebook's assets folder and update the link
- Sanitize/normalize asset URLs at import time so they never contain .. segments
- If a legitimate asset lives outside the workspace, move it into data/ rather than linking across
Example fix
// before (in markdown)  // after  // 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
- Reject asset URLs containing ../ before inserting them into documents
- Sanitize asset links at import/clip time
- Keep all assets inside the notebook's assets/ folder
- Review plugins/extensions that rewrite asset paths for traversal
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 .
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
- import path is not sub path of import dir
- invalid custom emoji name
- path escapes workspace: %s
- path escapes templates dir: %s
- path [%s] must not contain '..'
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/4bfcb084c67d8e25.
Report an issue: GitHub.