siyuan-note/siyuan · warning
asset path resolves outside assets directory
Error message
asset path resolves outside assets directory: %s
What it means
ResolveDataAssetPath resolves the asset path (following symlinks) and then verifies that the resolved real path still lives under the notebook/global assets root. This error is thrown when the final resolved location escapes the assets directory — the path only appeared to be inside assets/ before symlink resolution. It is a path-traversal containment check.
Solutions
- Inspect the symlink chain: `readlink -f <workspace>/data/<path>` to see where it actually points
- Move the real file into <workspace>/data/.../assets/ and replace the symlink with a regular file
- If sharing assets across notebooks, copy the file rather than linking it, or reference the global data/assets/ location
- Verify the link target is not on a bind-mount or network share whose path lies outside the workspace
Example fix
// before: symlink escapes assets ln -s /var/data/img.png data/20250101120000-abc/assets/img.png // after: real file inside assets cp /var/data/img.png data/20250101120000-abc/assets/img.png
Defensive patterns
Strategy: validation
Validate before calling
real, err := filepath.EvalSymlinks(absPath)
if err == nil && !strings.HasPrefix(real, filepath.EvalSymlinks(assetRoot)) {
// symlink escapes assets; do not call the resolver
} Try / catch
rel, abs, err := model.ResolveDataAssetPath(p)
if err != nil && strings.Contains(err.Error(), "resolves outside assets directory") {
// copy the real file into assets/ and replace the symlink, then retry
} Prevention
- Copy shared assets into the notebook instead of symlinking them from outside
- Avoid symlinking directories above assets/ (e.g. linking the whole notebook dir)
- Periodically audit assets dirs for symlinks: `find data -type l`
When it happens
Trigger: Calling ResolveDataAssetPath with a path inside <box>/assets/ that is a symlink (or contains a symlinked parent directory) resolving to a location outside the resolved assets root, e.g. symlink to /etc/passwd or to another notebook's assets when the notebook root check passed but the asset root containment failed.
Common situations: Manually symlinking assets from outside the workspace into a notebook for convenience; restoring a workspace from a backup that captured symlinks; a malicious or corrupted document referencing a crafted asset path.
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
- archive entry resolves outside destination
- child template path is outside the current template package
- notebook asset path resolves outside notebook directory
- symlink [ ] resolves outside assets directory: [ ]
- symlink [ ] resolves outside data/assets: [ ]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/58ebd3b080652937.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1103
}
if assetDirIndex > 0 {
notebookRoot := filepath.Join(util.DataDir, parts[0])
resolvedDataDir, dataEvalErr := ResolveRealPath(util.DataDir)
resolvedNotebookRoot, notebookEvalErr := ResolveRealPath(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 := ResolveAssetPathWithMissingLeaf(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 9f775e8a12)