siyuan-note/siyuan · error
asset path resolves outside assets directory: %s
Error message
asset path resolves outside assets directory: %s
What it means
Thrown by ResolveDataAssetPath (kernel/model/assets.go:971) after successfully resolving symlinks on both the assets root and the asset file: if the resolved file path no longer lives under the resolved assets root, the request is rejected. This catches symlinks inside the assets directory that point outside it, and is the core anti-traversal guard for global assets, explicitly tested by TestResolveDataAssetPath (`assets/linked/outside.png`).
Source
Thrown at kernel/model/assets.go:971
}
if assetDirIndex > 0 {
notebookRoot := filepath.Join(util.DataDir, parts[0])
resolvedDataDir, dataEvalErr := filepath.EvalSymlinks(util.DataDir)
resolvedNotebookRoot, notebookEvalErr := filepath.EvalSymlinks(notebookRoot)
if dataEvalErr != nil || notebookEvalErr != nil ||
!gulu.File.IsSubPath(resolvedDataDir, resolvedNotebookRoot) ||
!gulu.File.IsSubPath(resolvedNotebookRoot, resolvedRoot) {
err = fmt.Errorf("notebook asset path resolves outside notebook directory: %s", assetPath)
return
}
}
resolvedPath, evalErr := filepath.EvalSymlinks(absPath)
if evalErr != nil {
err = fmt.Errorf("resolve asset [%s] failed: %w", absPath, evalErr)
return
}
if !gulu.File.IsSubPath(resolvedRoot, resolvedPath) {
err = fmt.Errorf("asset path resolves outside assets directory: %s", assetPath)
return
}
relativePath = filepath.ToSlash(dataRelativePath)
return
}
// ResolveUnusedDataAssetPath 解析 data 相对资源路径,并确认目标当前未被引用。
func ResolveUnusedDataAssetPath(assetPath string) (relativePath, absPath string, err error) {
relativePath, absPath, err = ResolveDataAssetPath(assetPath)
if err != nil {
return
}
if unusedAssetsContainPath(relativePath, absPath, UnusedAssets(false)) {
return
}
err = fmt.Errorf("asset is not unused: %s", relativePath)View on GitHub (pinned to 251596fc0d)
Solutions
- Inspect symlinks under the assets root: `find <DataDir>/assets -type l -ls`.
- Replace any escaping symlink with a copy of the file inside `data/assets/`, or remove the symlink.
- If importing external assets, copy them into `data/assets/` rather than symlinking across directory boundaries.
- Treat unexpected triggers as a security event and audit the originating document's asset references.
Defensive patterns
Strategy: validation
Validate before calling
// Ensure a global asset symlink target stays inside the assets root before resolving.
full := filepath.Join(util.DataDir, filepath.FromSlash(p))
real, err1 := filepath.EvalSymlinks(full)
root, err2 := filepath.EvalSymlinks(filepath.Join(util.DataDir, "assets"))
if err1 == nil && err2 == nil && !gulu.File.IsSubPath(root, real) {
return errors.New("asset symlink escapes assets directory")
} Try / catch
if _, _, err := model.ResolveDataAssetPath(p); err != nil && strings.Contains(err.Error(), "resolves outside assets directory") {
// a symlink under data/assets points outside; audit and remove the link, do not bypass
} Prevention
- Do not place symlinks inside data/assets that point outside it.
- Copy external media into data/assets rather than linking across boundaries.
- Audit `find <DataDir>/assets -type l` periodically on shared/multi-user workspaces.
When it happens
Trigger: Calling ResolveDataAssetPath with a path like `assets/linked/outside.png` where `data/assets/linked` is a symlink to a directory outside `data/assets/`. The lexical path passes earlier checks, but after EvalSymlinks the real target is outside assets, so this fires.
Common situations: A user symlinked `data/assets/old` to an external folder to save space or import old data; a malicious document references a crafted asset path; a backup tool created junction points/symlinks that leave the assets tree.
Related errors
- symlink [%s] resolves outside data/assets: [%s]
- path is not a child of assets directory: %s
- notebook asset path resolves outside notebook directory: %s
- symlink [%s] resolves outside workspace: [%s]
- symlink [%s] resolves outside assets directory: [%s]
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/58ebd3b080652937.
Report an issue: GitHub.