siyuan-note/siyuan · error

[ ] is not an asset path

Error message

[%s] is not an asset path

What it means

GetAssetAbsPathInBox cleans the input path and rejects anything that is empty, '.', '..', starting with '../', or absolute. Such a path can never denote a notebook asset, so this error is returned before any filesystem access. It is an input-validation guard against path traversal and malformed asset references.

Solutions

  1. Pass a data-relative path that starts with assets/, e.g. "assets/foo.png" (after path.Clean)
  2. Strip any absolute prefix or leading slashes before calling, or use filepath.Rel(util.DataDir, abs) to derive the relative path
  3. Validate the path yourself before calling: reject empty/absolute/'..' segments early to give a clearer error to your users

Example fix

// before: absolute path rejected
model.GetAssetAbsPathInBox("/home/user/siyuan/data/assets/img.png", boxID)
// after: pass data-relative path
model.GetAssetAbsPathInBox("assets/img.png", boxID)
Defensive patterns

Strategy: validation

Validate before calling

p = path.Clean(strings.TrimSpace(p))
if p == "" || p == "." || p == ".." || strings.HasPrefix(p, "../") || path.IsAbs(p) {
	return fmt.Errorf("rejecting non-asset path %q", p)
}

Type guard

func isSafeRelAssetPath(p string) bool {
	p = path.Clean(strings.TrimSpace(p))
	return p != "." && p != ".." && !strings.HasPrefix(p, "../") && !path.IsAbs(p)
}

Try / catch

abs, err := model.GetAssetAbsPathInBox(ref, boxID)
if err != nil && strings.Contains(err.Error(), "is not an asset path") {
	// convert to data-relative path and retry
	rel, relErr := filepath.Rel(util.DataDir, rawInput)
	if relErr == nil { abs, err = model.GetAssetAbsPathInBox(filepath.ToSlash(rel), boxID) }
}

Prevention

When it happens

Trigger: Calling GetAssetAbsPathInBox with an absolute path like "/etc/passwd", a traversal path like "assets/../../secret", an empty string (cleans to "."), or a path that normalizes to ".." after query stripping.

Common situations: Passing a full filesystem path where a data-relative path is expected; user-supplied or plugin-supplied paths not sanitized; constructing paths by string concatenation that leaves '..' segments; passing an empty variable due to an earlier resolution failure.

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/20dbd839ed5f668f. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/assets.go:1224

			}
		}
	}
	cleanPath = filepath.ToSlash(relativePath)
	return
}

// GetAssetAbsPathInBox 在指定 box 内解析资源绝对路径,不进行全局遍历。
// relativePath 必须以 assets/ 前缀开头,boxID 为空且路径没有 box 查询参数时只解析普通/全局资源,不遍历加密 box。
// 加密 box 直接从 <boxID>/assets/ 查找,不依赖后缀匹配。
func GetAssetAbsPathInBox(relativePath, boxID string) (string, error) {
	var err error
	relativePath, boxID, err = assetPathAndBox(relativePath, boxID)
	if err != nil {
		return "", err
	}
	relativePath = path.Clean(relativePath)
	if relativePath == "." || strings.HasPrefix(relativePath, "../") || relativePath == ".." || path.IsAbs(relativePath) {
		return "", fmt.Errorf("[%s] is not an asset path", relativePath)
	}
	if !strings.HasPrefix(relativePath, "assets/") {
		return "", fmt.Errorf("[%s] is not an asset path (must start with assets/)", relativePath)
	}
	if boxID != "" && !ast.IsNodeIDPattern(boxID) {
		return "", fmt.Errorf("[%s] is not a box id", boxID)
	}

	if boxID == "" {
		return GetAssetAbsPathWithOpt(relativePath, false)
	}

	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)
		}
		// 解析符号链接/目录联接,防止软链接跳出资产根目录

View on GitHub (pinned to 9f775e8a12)