siyuan-note/siyuan · error
[%s] is not an asset path (must start with assets/)
Error message
[%s] is not an asset path (must start with assets/)
What it means
Thrown by GetAssetAbsPathInBox (kernel/model/assets.go:1084) when the cleaned relativePath does not start with the literal `assets/` prefix. The box-scoped resolver only handles assets, so any other relative path (e.g. `data/2024.../file.sy`, `storage/x`, a bare filename) is rejected up front.
Source
Thrown at kernel/model/assets.go:1084
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)
}
// 解析符号链接/目录联接,防止软链接跳出资产根目录
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)View on GitHub (pinned to 251596fc0d)
Solutions
- Confirm the value is meant to be an asset; if it is a block/snippet file, use the appropriate file API instead.
- Strip a leading slash before calling so the value is `assets/...` not `/assets/...`.
- Reconstruct the reference from the document's asset convention (`assets/<name>` or `<boxID>/assets/<name>`).
- If you genuinely need a non-asset file, do not use GetAssetAbsPathInBox.
Example fix
// before
abs, err := model.GetAssetAbsPathInBox("/assets/img.png", box)
// after
rel := strings.TrimPrefix(userInput, "/")
abs, err := model.GetAssetAbsPathInBox(rel, box) Defensive patterns
Strategy: validation
Validate before calling
// Require the assets/ prefix before calling the box-scoped resolver.
rel := strings.TrimPrefix(filepath.ToSlash(p), "/")
if !strings.HasPrefix(rel, "assets/") {
return fmt.Errorf("not an asset path (must start with assets/): %q", p)
}
return model.GetAssetAbsPathInBox(rel, box) Type guard
func isAssetPrefixed(p string) bool {
return strings.HasPrefix(strings.TrimPrefix(filepath.ToSlash(p), "/"), "assets/")
} Prevention
- Route only asset references through GetAssetAbsPathInBox; use the file/snippet APIs for other paths.
- Strip a leading slash so `/assets/...` becomes `assets/...`.
- Centralize asset-URL building in one helper to keep the prefix consistent.
When it happens
Trigger: Calling GetAssetAbsPathInBox with a relativePath that is not an asset reference — for example a notebook data file path, a `storage/` path, or an unqualified filename. Distinguish from error 407: here the path is well-formed and relative, it just isn't under `assets/`.
Common situations: Passing a block file path or a snippet path to the asset resolver by mistake; a frontend caller routing a non-asset URL through the asset endpoint; URL-encoded leading slash making `/assets/...` not match `assets/`.
Related errors
- asset path must be relative to data directory: %s
- asset path escapes data directory: %s
- path is not a child of assets directory: %s
- [%s] is not an asset path
- path escapes templates dir: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/50779f10dc9f0cad.
Report an issue: GitHub.