siyuan-note/siyuan · error

path is not a child of assets directory

Error message

path is not a child of assets directory: %s

What it means

This is a defense-in-depth check in ResolveDataAssetPath: after locating the `assets` directory segment and joining it to DataDir, the function verifies with gulu.File.IsSubPath that the requested absolute path is actually contained within that assets root. Because earlier cleaning should guarantee containment, hitting this error usually means the computed absPath and the assets root are inconsistent (e.g. edge cases in path separators or the `assets` segment matched at an unexpected index).

Solutions

  1. Verify the input path is a clean slash-relative path ending in a real file name under `assets/`, not the assets directory itself
  2. Pass the path through filepath.ToSlash/clean before calling, e.g. `path.Clean(strings.ReplaceAll(p, "\\", "/"))`
  3. If it occurs on Windows, check path casing/separator normalization in the code that produced the path
  4. Report as a bug with the exact input path if a plainly valid `assets/...` path triggers it

Example fix

// before
rel, abs, err := model.ResolveDataAssetPath("nb\\assets\\sub\\..\\pic.png")
// after
rel, abs, err := model.ResolveDataAssetPath(path.Clean("nb/assets/sub/../pic.png"))
Defensive patterns

Strategy: validation

Validate before calling

p := path.Clean(strings.ReplaceAll(assetPath, "\\", "/"))
if strings.HasSuffix(p, "/assets") {
    return errors.New("path must point to a file inside assets/, not the assets dir itself")
}

Try / catch

rel, abs, err := model.ResolveDataAssetPath(assetPath)
if err != nil {
    if strings.Contains(err.Error(), "not a child of assets directory") {
        return fmt.Errorf("malformed asset path %q: %w", assetPath, err)
    }
    return err
}

Prevention

When it happens

Trigger: ResolveDataAssetPath called such that absPath (DataDir + cleaned relative path) is not a filesystem child of assetRoot — practically rare since assetRoot is a prefix of absPath; can surface on Windows with unusual path casing/separator forms.

Common situations: Non-normalized paths on Windows (mixed separators, differing case); custom/patched callers that mutate absPath between checks; pathological paths like the leaf being the assets directory itself (`nb/assets`).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/22345e85a394a5fd. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/assets.go:1077

			if !filelock.IsExist(boxConfPath) {
				err = fmt.Errorf("asset path does not belong to a notebook: %s", assetPath)
				return
			}
			if IsEncryptedBox(parts[0]) {
				err = fmt.Errorf("accessing assets in encrypted notebook [%s] is not supported", parts[0])
				return
			}
		}
	}
	if assetDirIndex < 0 {
		err = fmt.Errorf("path is not under an assets directory: %s", assetPath)
		return
	}

	assetRootParts := parts[:assetDirIndex+1]
	assetRoot := filepath.Join(util.DataDir, filepath.FromSlash(strings.Join(assetRootParts, "/")))
	if !gulu.File.IsSubPath(assetRoot, absPath) {
		err = fmt.Errorf("path is not a child of assets directory: %s", assetPath)
		return
	}

	resolvedRoot, evalErr := ResolveAssetPathWithMissingLeaf(assetRoot)
	if evalErr != nil {
		err = fmt.Errorf("resolve assets directory [%s] failed: %w", assetRoot, evalErr)
		return
	}
	if assetDirIndex > 0 {
		notebookRoot := filepath.Join(util.DataDir, parts[0])
		resolvedDataDir, dataEvalErr := ResolveRealPath(util.DataDir)
		resolvedNotebookRoot, notebookEvalErr := ResolveRealPath(notebookRoot)
		if dataEvalErr != nil || notebookEvalErr != nil ||
			!gulu.File.IsSubPath(resolvedDataDir, resolvedNotebookRoot) ||
			!gulu.File.IsSubPath(resolvedNotebookRoot, resolvedRoot) {
			err = fmt.Errorf("notebook asset path resolves outside notebook directory: %s", assetPath)
			return
		}

View on GitHub (pinned to 9f775e8a12)