siyuan-note/siyuan · error
accessing assets in encrypted notebook
Error message
accessing assets in encrypted notebook [%s] is not supported
What it means
ResolveDataAssetPath resolves a data-dir-relative asset path (e.g. `20240101120000-abc123/assets/foo.png`) to an absolute path while enforcing security checks. When the path points inside a notebook's own assets directory (not the global `assets/` folder), the notebook must be a normal (unencrypted) notebook; if `IsEncryptedBox(parts[0])` reports the notebook is encrypted, asset access is refused because encrypted notebook contents cannot be read as plaintext files. The notebook ID is interpolated into the message.
Solutions
- Move the asset to a non-encrypted notebook or to the workspace-level `assets/` folder and reference it from there
- Decrypt/open the notebook as a normal notebook if you legitimately need file-level asset access
- Change the calling code to skip encrypted notebooks (check IsEncryptedBox before calling ResolveDataAssetPath)
- If the notebook is not actually meant to be encrypted, inspect its .siyuan/conf.json — the box may have been misconfigured
Example fix
// before
rel, abs, err := model.ResolveDataAssetPath(notebookID + "/assets/pic.png")
// after
if model.IsEncryptedBox(notebookID) {
return fmt.Errorf("skip encrypted notebook %s", notebookID)
}
rel, abs, err := model.ResolveDataAssetPath(notebookID + "/assets/pic.png") Defensive patterns
Strategy: try-catch
Validate before calling
func canAccessAsset(boxID string) bool {
return !model.IsEncryptedBox(boxID)
}
// check before: strings.HasPrefix(assetPath, boxID+"/assets/") && !model.IsEncryptedBox(boxID) Type guard
func isPlainNotebookAsset(assetPath, boxID string) bool {
return strings.HasPrefix(assetPath, boxID+"/assets/") && !model.IsEncryptedBox(boxID)
} Try / catch
rel, abs, err := model.ResolveDataAssetPath(assetPath)
if err != nil {
if strings.Contains(err.Error(), "accessing assets in encrypted notebook") {
// fall back: copy asset to global assets/ dir first, or skip
return handleEncryptedBoxAsset(assetPath)
}
return err
} Prevention
- Check model.IsEncryptedBox(notebookID) before touching any `<notebookID>/assets/...` path
- Prefer the workspace-level `assets/` directory for files that must be file-accessed programmatically
- Enumerate notebooks via conf and filter out encrypted boxes in any batch asset processing
When it happens
Trigger: Calling ResolveDataAssetPath (directly or via assetStat, deferredAssetPathFromFiles, PrepareAgentMessageImage, ResolveUnusedDataAssetPath) with a path of the form `<notebookID>/assets/<file>` where the notebook identified by parts[0] is an encrypted notebook (its .siyuan/conf.json marks it encrypted).
Common situations: Plugins, scripts, or AI/agent integrations referencing assets inside an encrypted notebook; tooling that enumerates notebook assets without filtering out encrypted boxes; moving an asset path between notebooks where the target is encrypted.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Conf.Language(0)
- path is not a child of assets directory
- path is not under an assets directory
- read image failed
- resolve assets directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/f9c390e75f91e57d.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1064
assetDirIndex := -1
switch {
case len(parts) > 1 && parts[0] == "assets":
assetDirIndex = 0
case len(parts) > 2 && ast.IsNodeIDPattern(parts[0]):
for i := 1; i < len(parts)-1; i++ {
if parts[i] == "assets" {
assetDirIndex = i
break
}
}
if assetDirIndex > 0 {
boxConfPath := filepath.Join(util.DataDir, parts[0], ".siyuan", "conf.json")
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 {View on GitHub (pinned to 9f775e8a12)