siyuan-note/siyuan · error
resource escapes the data directory
Error message
resource escapes the data directory: %s
What it means
The asset relink scan verifies that every resolved asset path (after symlink evaluation) still lives inside the workspace data directory. If the resolved real path is not a subpath of DataDir, or symlink evaluation fails, the resource is rejected so relinking never touches or rewrites references to files outside the workspace. This is a safety gate protecting the workspace boundary.
Solutions
- Move or copy the asset into the workspace data directory (e.g. data/assets/) so its real path is a subpath of DataDir
- Remove symlinks pointing outside the workspace and replace them with real files inside data/
- Fix the reference in the document so it uses a valid workspace-relative asset path
- Check DataDir resolution (e.g. wrong workspace opened) so legitimate assets are not falsely seen as escaping
Example fix
// before
relinkOld := "/mnt/shared/photos/logo.png" // outside data dir
// after
oldAbs := filepath.Join(util.DataDir, "assets", "logo.png")
err := filelock.Copy("/mnt/shared/photos/logo.png", oldAbs) // bring asset inside data/
relinkOld := "assets/logo.png" Defensive patterns
Strategy: validation
Validate before calling
func isInsideDataDir(p string) bool {
abs, err := filepath.Abs(p)
if err != nil { return false }
real, err := filepath.EvalSymlinks(abs)
if err != nil { return false }
root, err := filepath.EvalSymlinks(util.DataDir)
return err == nil && gulu.File.IsSubPath(root, real)
} Type guard
if !isInsideDataDir(oldPath) { return errors.New("path must resolve inside the workspace data directory") } Prevention
- Keep assets as real files inside data/assets, avoid symlinks out of the workspace
- Use workspace-relative asset paths in documents
- Check EvalSymlinks resolution when using linked folders or cloud sync
When it happens
Trigger: Calling RelinkAssetWithContext/RelinkAssets or the scan functions (via resolveRelinkAsset, scanViews, scanAnnotation) with an oldPath whose absolute location is a symlink pointing outside data/, an escape like '../../outside.png', or a path whose EvalSymlinks fails.
Common situations: Assets symlinked from a shared folder outside the workspace; manually moved or hand-crafted .sy documents referencing paths with parent-directory escapes; cloud-sync setups where assets are junctions/symlinks to another volume; note-taking content pasted with absolute file paths later interpreted as assets.
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
- invalid history source path
- invalid skill path
- marketplace package contains an unsupported file
- notebook asset path resolves outside notebook directory
- Obsidian Vault path is unsafe
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/56763395daadc206.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink.go:489
for offset := 0; offset < 1000; offset++ {
dir := filepath.Join(util.HistoryDir, time.Now().Add(time.Duration(offset)*time.Second).Format("2006-01-02-150405")+"-"+HistoryOpReplace)
if err := os.Mkdir(dir, 0755); err == nil {
return dir, nil
} else if !os.IsExist(err) {
return "", err
}
}
return "", errors.New("cannot allocate a replacement history directory")
}
func validateRelinkStoragePath(abs string) error {
real, err := filepath.EvalSymlinks(abs)
if err != nil {
return err
}
dataRoot, err := filepath.EvalSymlinks(util.DataDir)
if err != nil || !gulu.File.IsSubPath(dataRoot, real) {
return fmt.Errorf("resource escapes the data directory: %s", abs)
}
rel, err := filepath.Rel(dataRoot, real)
if err != nil {
return err
}
first, _, _ := strings.Cut(filepath.ToSlash(rel), "/")
if ast.IsNodeIDPattern(first) && IsEncryptedBox(first) {
return errors.New("encrypted resources are not supported")
}
return nil
}
View on GitHub (pinned to 9f775e8a12)