siyuan-note/siyuan · error
symlink [ ] resolves outside data/assets: [ ]
Error message
symlink [%s] resolves outside data/assets: [%s]
What it means
After resolving the asset symlink, getAssetAbsPath verifies the real target path is still a sub-path of the resolved data/assets root. The error "symlink [%s] resolves outside data/assets: [%s]" is returned when a symlinked asset points outside data/assets — a defense against symlink-based escapes from the assets sandbox.
Solutions
- Replace the symlink with a real copy of the target file inside data/assets
- If sharing assets across notebooks, move them into data/assets and reference via the standard assets/ path
- Recreate the symlink so its target lives inside data/assets (e.g. data/assets/shared/foo.png -> data/assets/foo.png)
- On containerized setups, mount the shared assets directory at data/assets instead of symlinking to it
Example fix
// before (filesystem) // ln -s /home/user/photos/cat.png <workspace>/data/assets/cat.png // after (filesystem) // cp /home/user/photos/cat.png <workspace>/data/assets/cat.png
Defensive patterns
Strategy: validation
Validate before calling
real, _ := filepath.EvalSymlinks(candidate)
if !strings.HasPrefix(real, realAssetsRoot) {
return errors.New("symlink target must stay inside data/assets")
} Try / catch
if err != nil && strings.Contains(err.Error(), "resolves outside data/assets") {
return fmt.Errorf("replace symlink %s with a real copy inside data/assets", relPath)
} Prevention
- Copy shared files into data/assets instead of symlinking from outside
- In containers, mount shared assets at data/assets rather than linking
- Audit data/assets for symlinks after restores or migrations
When it happens
Trigger: getAssetAbsPath finds a file p that is a symlink whose EvalSymlinks target realP is not under the real data/assets directory — e.g. data/assets/foo.png -> /home/user/secret.png or -> a path in another notebook outside assets.
Common situations: Users manually symlinking shared assets from outside the workspace into data/assets; docker/container setups where assets are mounted elsewhere and linked; backup/restore tools recreating symlinks with wrong targets.
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
- notebook asset path resolves outside notebook directory
- archive entry resolves outside destination
- asset path resolves outside assets directory
- boot appearance asset forbidden
- child template path is outside the current template package
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/5bd6e6a88bc1e08e.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1303
}
func getAssetAbsPath(relativePath string, includeEncrypted bool) (absPath string, err error) {
relativePath = filepath.ToSlash(relativePath)
// 在 data 文件夹下搜索,主要是 data/assets 文件夹
p := filepath.Join(util.DataDir, relativePath)
if gulu.File.IsExist(p) {
if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
return "", fmt.Errorf("[%s] is not sub path of workspace", p)
}
// 解析符号链接,验证真实路径仍在 data/assets/ 下
if realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {
assetsRoot := util.GetDataAssetsAbsPath()
realAssetsRoot, rootEvalErr := filepath.EvalSymlinks(assetsRoot)
if rootEvalErr != nil {
return "", fmt.Errorf("resolve assets root [%s] failed: %w", assetsRoot, rootEvalErr)
}
if !gulu.File.IsSubPath(realAssetsRoot, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside data/assets: [%s]", p, realP)
}
// 安全校验使用解析后的路径,返回原路径以便下游与 DataDir 保持同一路径形式
return p, nil
}
return p, nil
}
// 在文档同级 assets 文件夹下搜索
if !strings.HasPrefix(relativePath, "assets/") {
return "", nil
}
notebooks, err := ListNotebooks()
if err != nil {
return "", errors.New(Conf.Language(0))
}
for _, notebook := range notebooks {
if !includeEncrypted && IsEncryptedBox(notebook.ID) {
continue // 加密笔记本的资源不参与全局路径解析(孤岛,资源不跨边界)View on GitHub (pinned to 9f775e8a12)