siyuan-note/siyuan · error

asset path [%s] is outside assets directory [%s]

Error message

asset path [%s] is outside assets directory [%s]

What it means

assetPathMapKey computes an asset's relative path inside an assets directory and rejects any path that is not strictly contained in it: equal to '.', '..', starting with '../', or absolute. This is a path-traversal guard ensuring every asset key maps under 'assets/'. filepath.Rel produced a relative path that escapes the assets directory, so the entry is refused instead of generating a key outside the assets tree.

Source

Thrown at kernel/model/assets.go:2827

		return nil
	})
	if walkErr != nil {
		if os.IsNotExist(walkErr) {
			return
		}
		return nil, fmt.Errorf("walk global assets [%s] failed: %w", dataAssetsAbsPath, walkErr)
	}
	return
}

func assetPathMapKey(assetsDirPath, assetPath string, isDir bool) (ret string, err error) {
	relPath, err := filepath.Rel(assetsDirPath, assetPath)
	if err != nil {
		return
	}
	relPath = filepath.ToSlash(relPath)
	if relPath == "." || relPath == ".." || strings.HasPrefix(relPath, "../") || path.IsAbs(relPath) {
		err = fmt.Errorf("asset path [%s] is outside assets directory [%s]", assetPath, assetsDirPath)
		return
	}

	ret = path.Join("assets", relPath)
	if isDir {
		ret += "/"
	}
	return
}

// copyBoxAssetsToDataAssets 将笔记本路径下所有(包括子文档)的 assets 复制一份到 data/assets 中。
func copyBoxAssetsToDataAssets(boxID string) error {
	boxLocalPath := filepath.Join(util.DataDir, boxID)
	return copyAssetsToDataAssets(boxLocalPath)
}

// copyDocAssetsToDataAssets 将文档路径下所有(包括子文档)的 assets 复制一份到 data/assets 中。
func copyDocAssetsToDataAssets(boxID, parentDocPath string) error {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Locate the offending path reported in the message and check whether it is a symlink escaping the assets directory; remove or relocate the symlink.
  2. Ensure only files physically under the assets directory are referenced; copy external files into data/assets instead of linking them.
  3. If you call this internal pipeline yourself, pass paths that are descendants of assetsDirPath (verify with filepath.Rel == no '../' prefix).
  4. Report it as an internal invariant violation if the path came from the kernel's own walk, since walked entries should always be inside the dir.

Example fix

// before: symlink inside assets escaping the directory
ln -s /home/user/other-files data/assets/linked
// after: copy the content instead of linking
cp -r /home/user/other-files data/assets/other-files
Defensive patterns

Strategy: validation

Validate before calling

function isInsideAssets(assetPath, assetsDir) {
  const rel = path.relative(assetsDir, assetPath);
  return rel !== "" && rel !== ".." && !rel.startsWith(".." + path.sep) && !path.isAbsolute(rel);
}

Prevention

When it happens

Trigger: An asset path passed to assetPathMapKey (during asset map building for cleanup/refresh) resolves to a location outside the assets dir, e.g. a symlink under data/assets pointing elsewhere, or an absolute path supplied that is not a descendant of assetsDirPath.

Common situations: A user placed a symlink inside data/assets pointing to files elsewhere on disk; storage layouts where assets dirs are linked; custom tooling that feeds absolute asset paths into the asset-map pipeline.

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/3cf6837602f87bc2. Report an issue: GitHub.