siyuan-note/siyuan · error
symlink [%s] resolves outside assets directory: [%s]
Error message
symlink [%s] resolves outside assets directory: [%s]
What it means
Thrown by GetAssetAbsPathInBox (kernel/model/assets.go:1110) when a box-scoped asset is a symlink that resolves to a target outside the expected `<boxID>/assets/` prefix (even if it is still inside the workspace). It is a tighter guard than 411: the resolved real path must remain specifically under that box's assets directory (or the global `data/assets/`).
Source
Thrown at kernel/model/assets.go:1110
}
p := filepath.Join(util.DataDir, boxID, relativePath)
if gulu.File.IsExist(p) {
if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
return "", fmt.Errorf("[%s] is not sub path of workspace", p)
}
// 解析符号链接/目录联接,防止软链接跳出资产根目录
if realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {
if !gulu.File.IsSubPath(util.WorkspaceDir, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside workspace: [%s]", p, realP)
}
// 验证解析后的路径仍在 <boxID>/assets/ 或全局 data/assets/ 下
expectedPrefix := filepath.Join(util.DataDir, "assets")
if boxID != "" {
expectedPrefix = filepath.Join(util.DataDir, boxID, "assets")
}
if !gulu.File.IsSubPath(expectedPrefix, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside assets directory: [%s]", p, realP)
}
}
return p, nil
}
// 非加密 box 的资源可能回退到全局 data/assets(兼容旧笔记本结构)
if !IsEncryptedBox(boxID) {
return GetAssetAbsPathWithOpt(relativePath, false)
}
return "", fmt.Errorf(Conf.Language(12), relativePath)
}
// GetAssetAbsPathWithOpt 与 GetAssetAbsPath 一致,但可通过 includeEncrypted 控制是否遍历加密 box。
// serveAssets 传 true(下游 serveEncryptedAsset 会按锁定状态 fail-closed),其他调用方传 false(安全跳过)。
func GetAssetAbsPathWithOpt(relativePath string, includeEncrypted bool) (string, error) {
relativePath = strings.TrimSpace(relativePath)
if idx := strings.Index(relativePath, "?"); idx >= 0 {
relativePath = relativePath[:idx]
}View on GitHub (pinned to 251596fc0d)
Solutions
- Identify the link with `find <DataDir>/<boxID>/assets -type l -ls` and read its target.
- Replace it with a copy of the file that physically resides under `<boxID>/assets/`.
- If cross-box asset sharing is needed, copy the asset into each box's assets directory rather than linking.
- Treat unexpected occurrences as a security signal and audit the source document/request.
Defensive patterns
Strategy: validation
Validate before calling
// Ensure a box-asset symlink target stays within the box's assets dir.
full := filepath.Join(util.DataDir, boxID, filepath.FromSlash(rel))
expected := filepath.Join(util.DataDir, boxID, "assets")
if real, err := filepath.EvalSymlinks(full); err == nil && real != full {
if !gulu.File.IsSubPath(expected, real) {
return errors.New("box asset symlink escapes assets directory")
}
} Try / catch
if _, err := model.GetAssetAbsPathInBox(ref, box); err != nil && strings.Contains(err.Error(), "resolves outside assets directory") {
// symlink escapes the box assets dir; replace with a copy, do not weaken the guard
} Prevention
- Do not link box assets to other boxes or to non-asset workspace data.
- Copy shared assets into each box that needs them.
- Audit `find <DataDir>/<boxID>/assets -type l` regularly.
When it happens
Trigger: Calling GetAssetAbsPathInBox where the asset file is a symlink to another location inside the workspace but outside `<boxID>/assets/` — e.g. a link from `<boxA>/assets/x` to `<boxB>/data/notes/foo` or to `data/storage/...`. The workspace check (411) passes but this tighter check fails.
Common situations: Symlinking a box asset to a file in another box or to non-asset workspace data; cross-box sharing attempts via links; restoring a backup that recreated links with shifted targets.
Related errors
- notebook asset path resolves outside notebook directory: %s
- asset path resolves outside assets directory: %s
- symlink [%s] resolves outside workspace: [%s]
- symlink [%s] resolves outside data/assets: [%s]
- asset path must be relative to data directory: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/7d44f86a64a71b97.
Report an issue: GitHub.