siyuan-note/siyuan · error
symlink [%s] resolves outside data/assets: [%s]
Error message
symlink [%s] resolves outside data/assets: [%s]
What it means
Thrown by getAssetAbsPath (kernel/model/assets.go:1157) for global assets: after resolving symlinks on both the file and the `data/assets` root, the resolved file target is no longer inside the resolved `data/assets` root. This is the global-assets counterpart of error 404/412, catching symlinks inside `data/assets/` that escape it.
Source
Thrown at kernel/model/assets.go:1157
}
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 251596fc0d)
Solutions
- List symlinks under the assets root: `find <DataDir>/assets -type l -ls`.
- Replace escaping symlinks with real copies stored inside `data/assets/`.
- If importing external media, copy the files into `data/assets/` instead of symlinking across boundaries.
- Treat unexpected occurrences as a security event and audit the source of the reference.
Defensive patterns
Strategy: validation
Validate before calling
// Detect global-asset symlinks that escape data/assets before resolving.
full := filepath.Join(util.DataDir, filepath.FromSlash(rel))
root, _ := filepath.EvalSymlinks(util.GetDataAssetsAbsPath())
if real, err := filepath.EvalSymlinks(full); err == nil && real != full && root != "" {
if !gulu.File.IsSubPath(root, real) {
return errors.New("global asset symlink escapes data/assets")
}
} Try / catch
if _, err := model.GetAssetAbsPath(ref); err != nil && strings.Contains(err.Error(), "resolves outside data/assets") {
// symlink under data/assets escapes it; replace with a copy, do not bypass
} Prevention
- Do not symlink inside data/assets to external locations.
- Copy imported media into data/assets physically.
- Audit `find <DataDir>/assets -type l` periodically.
When it happens
Trigger: Calling GetAssetAbsPath for a global asset like `assets/linked/x.png` where `data/assets/linked` is a symlink to a directory outside `data/assets/` (e.g. to `/home/user/pics`). The lexical path is fine, but EvalSymlinks reveals the target is outside assets.
Common situations: A user symlinked part of `data/assets/` to external storage to save space or import a library; a backup/restore created escaping junctions; a malicious document references a crafted path.
Related errors
- asset path resolves outside assets directory: %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/5bd6e6a88bc1e08e.
Report an issue: GitHub.