siyuan-note/siyuan · error

decrypt file annotation

Error message

decrypt file annotation [%s]: %w

What it means

For assets in encrypted notebooks, the .sya annotation data is ciphertext and is decrypted with the notebook's data-encryption key via DecryptAsset. If decryption or authentication fails (wrong key, corrupted or tampered ciphertext, version mismatch), the export aborts with this wrapped error instead of falling back to plaintext, per the encrypted-notebook compatibility policy.

Solutions

  1. Restore the correct notebook encryption keys (do not regenerate MasterSalt); keep keys consistent between devices
  2. Re-sync the notebook from a healthy replica to replace corrupted ciphertext
  3. Verify the .sya resides in the same notebook it was encrypted for; move it back if misplaced
  4. Ensure the kernel version supports the .sya envelope format version, or migrate via the supported recovery path

Example fix

// before: .sya copied into another encrypted notebook -> decrypt fails
assets/report.pdf.sya  (in box-B, encrypted for box-A)
// after: keep sidecar with its owning notebook
assets/report.pdf.sya  (in box-A)
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await exportAnnotation(docID);
} catch (e) {
  if (/decrypt file annotation/.test(e.message)) {
    // do NOT retry with regenerated keys; restore keys from a working replica first
    await restoreKeysFromReplica(boxID);
    return exportAnnotation(docID);
  }
  throw e;
}

Prevention

When it happens

Trigger: Exporting an annotation from a notebook whose MasterSalt/keys were regenerated or lost; .sya ciphertext corrupted by partial sync or manual editing; DecryptAsset rejects the payload because the basename or AAD does not match what was authenticated at write time.

Common situations: Workspace keys folder restored from an old backup while data came from a newer sync; user copied a .sya into a different encrypted notebook; truncated/corrupted file after an interrupted transfer; decrypting an asset that was written under an older envelope format version without its migration path.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/13f814b38047582c. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/export.go:4379

	assetBoxID := ExtractBoxIDFromAssetsPath(absPath)
	var dek []byte
	if IsEncryptedBox(assetBoxID) {
		HoldBoxReadLock(assetBoxID)
		defer ReleaseBoxReadLock(assetBoxID)
		dek, err = GetDEKIfUnlocked(assetBoxID)
		if err != nil {
			return err
		}
		defer clear(dek)
	}
	syaData, readErr := os.ReadFile(sya)
	if readErr != nil {
		return fmt.Errorf("read file annotation [%s]: %w", sya, readErr)
	}
	if nil != dek {
		plain, decErr := DecryptAsset(assetBoxID, filepath.Base(sya), dek, syaData)
		if decErr != nil {
			return fmt.Errorf("decrypt file annotation [%s]: %w", sya, decErr)
		}
		syaData = plain
	}
	syaJSON := map[string]struct {
		Pages []struct {
			Index *int `json:"index"`
		} `json:"pages"`
		Page *int `json:"page"`
	}{}
	if err = gulu.JSON.UnmarshalJSON(syaData, &syaJSON); err != nil {
		return fmt.Errorf("parse file annotation [%s]: %w", sya, err)
	}
	annotationData, found := syaJSON[annotationID]
	pageIndex := annotationData.Page
	if 0 < len(annotationData.Pages) {
		pageIndex = annotationData.Pages[0].Index
	}
	if !found || nil == pageIndex || *pageIndex < 0 {

View on GitHub (pinned to 9f775e8a12)