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

  1. Move the asset to a non-encrypted notebook or to the workspace-level `assets/` folder and reference it from there
  2. Decrypt/open the notebook as a normal notebook if you legitimately need file-level asset access
  3. Change the calling code to skip encrypted notebooks (check IsEncryptedBox before calling ResolveDataAssetPath)
  4. 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

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


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)