siyuan-note/siyuan · error
[ ] is not a box id
Error message
[%s] is not a box id
What it means
When a boxID was supplied (from the argument or the ?box= query), GetAssetAbsPathInBox validates it against ast.IsNodeIDPattern, the canonical notebook-ID format (20-char timestamp-random pattern). This error is returned when the box ID does not match that pattern, preventing lookups into a non-existent or malformed notebook directory that could double as a traversal vector.
Solutions
- Pass the notebook's real 20-character ID (available from Conf.Box names/IDs or listNotebooks API), not its name
- Trim whitespace and verify the ID against ast.IsNodeIDPattern before calling
- Strip or fix the ?box= query parameter if the malformed ID comes from an asset URL
Example fix
// before: name instead of ID
model.GetAssetAbsPathInBox("assets/img.png", "My Notebook")
// after: resolve the real box ID first
boxID := getBoxIDByName("My Notebook")
model.GetAssetAbsPathInBox("assets/img.png", boxID) Defensive patterns
Strategy: validation
Validate before calling
import "github.com/88250/lute/ast"
if boxID != "" && !ast.IsNodeIDPattern(boxID) {
return fmt.Errorf("invalid box id %q", boxID)
} Type guard
func validBoxID(id string) bool { return id == "" || ast.IsNodeIDPattern(id) } Try / catch
abs, err := model.GetAssetAbsPathInBox(ref, boxID)
if err != nil && strings.Contains(err.Error(), "is not a box id") {
// resolve the real box ID from the notebook list and retry
for _, b := range model.Conf.Boxes { if b.Name == wantedName { abs, err = model.GetAssetAbsPathInBox(ref, b.ID) } }
} Prevention
- Always obtain box IDs from the notebook list API, never from names or user free-text
- Trim and validate any ?box= query values parsed from stored asset URLs
- Sanitize plugin/config inputs that feed boxID parameters
When it happens
Trigger: Calling GetAssetAbsPathInBox("assets/img.png", boxID) where boxID is a notebook name, a title, an empty-but-non-nil string with whitespace, a truncated ID, or a value like "../x" embedded in the ?box= query parameter.
Common situations: Passing a human-readable notebook name instead of its ID; building the box ID from user input or a plugin config value; a corrupted stored link whose ?box= query was mangled; extracting the wrong capture group from a URL regex.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/6544da5e98e52067.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1230
// 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)
}
// 验证解析后的路径仍在 <boxID>/assets/ 或全局 data/assets/ 下
expectedPrefix := filepath.Join(util.DataDir, "assets")View on GitHub (pinned to 9f775e8a12)