siyuan-note/siyuan · error

exporting resources across encrypted notebook boundaries is

Error message

exporting resources across encrypted notebook boundaries is not supported

What it means

Returned by exportResourcesEncryptedBox when the resource list contains assets from two or more *different* encrypted notebooks (encryptedBoxID is already set and a later path yields a different encrypted boxID). Cross-notebook encrypted export is refused because each encrypted box has its own DEK and read-lock scope, and the unified export path can only hold one box's lock.

Source

Thrown at kernel/model/export.go:942

	for _, resourcePath := range resourcePaths {
		resourceFullPath := filepath.Join(util.WorkspaceDir, resourcePath)
		if !util.IsAbsPathInWorkspace(resourceFullPath) {
			return "", errors.New("resource path [" + resourcePath + "] is not in workspace")
		}
		boxID := ExtractBoxIDFromAssetsPath(resourceFullPath)
		if boxID == "" || !IsEncryptedBox(boxID) {
			hasNormalResource = true
			continue
		}

		assetsPath := filepath.Join(util.DataDir, boxID, "assets")
		if !gulu.File.IsSubPath(assetsPath, resourceFullPath) {
			return "", errors.New("exporting non-asset files from encrypted notebooks is not supported")
		}
		if encryptedBoxID == "" {
			encryptedBoxID = boxID
		} else if encryptedBoxID != boxID {
			return "", errors.New("exporting resources across encrypted notebook boundaries is not supported")
		}
	}
	if encryptedBoxID != "" && hasNormalResource {
		return "", errors.New("exporting encrypted and normal notebook resources together is not supported")
	}
	return
}

func ExportPreview(id string, fillCSSVar bool) (retStdHTML string) {
	if exportErr := withExportReadLockByBlockID(id, func() error {
		blockRefMode := Conf.Export.BlockRefMode
		bt := getExportBlockTree(id)
		if nil == bt {
			return nil
		}

		tree := prepareExportTree(bt)
		if numberErr := applyHeadingNumbersForExport(tree, bt, false); nil != numberErr {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Split the export into separate ExportResources calls, one per encrypted notebook.
  2. If the assets must be in one archive, first export each box separately then combine the resulting zips outside SiYuan.
  3. Filter the selection so all encrypted assets come from a single box.

Example fix

// before — mixing two encrypted boxes in one call
model.ExportResources([]string{
    "data/boxA/assets/a.png",
    "data/boxB/assets/b.png",
}, name)
// after — one encrypted box per call
model.ExportResources([]string{"data/boxA/assets/a.png"}, name)
model.ExportResources([]string{"data/boxB/assets/b.png"}, name)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure all encrypted resources belong to a single box
var firstEncBox string
for _, p := range resourcePaths {
    full := filepath.Join(util.WorkspaceDir, p)
    boxID := ExtractBoxIDFromAssetsPath(full)
    if boxID == "" || !IsEncryptedBox(boxID) {
        continue
    }
    if firstEncBox == "" {
        firstEncBox = boxID
    } else if firstEncBox != boxID {
        return errors.New("resources span multiple encrypted notebooks; split the export")
    }
}

Prevention

When it happens

Trigger: POST /api/export/exportResources where resourcePaths mixes assets from data/boxA/assets/ and data/boxB/assets/, both boxA and boxB being encrypted notebooks. Detected when the second distinct encrypted boxID is encountered in the loop.

Common situations: User multi-selects assets across two encrypted notebooks in the UI and chooses 'export resources'. A plugin batches assets from multiple encrypted boxes into one export call.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/0bf99ac08cb91838. Report an issue: GitHub.