siyuan-note/siyuan · error

encrypted database history is plaintext

Error message

encrypted database history is plaintext

What it means

While reading a bound attribute view's history snapshot, the engine requires that history data belonging to a notebook (boxID non-empty) is ciphertext under the encrypted-notebook format. Finding plaintext bytes where the box enforces encryption means the history file was written by an older/non-encrypting writer or tampered with, so it is rejected instead of silently accepting unauthenticated data.

Solutions

  1. Do not import plaintext history into an encrypted notebook; regenerate history from the original encrypted workspace
  2. Verify the history file's boxID matches a notebook that was actually encrypted at write time
  3. Re-sync or re-create history by triggering a new snapshot of the database
  4. Restore the original ciphertext file from backup if it was overwritten or corrupted
Defensive patterns

Strategy: validation

Validate before calling

const data = await readFile(filename);
if (boxID && !isCiphertext(data)) {
  throw new Error('history file is plaintext; cannot use with encrypted notebook');
}

Type guard

function isUsableEncryptedHistory(data, boxID) {
  return !boxID || isCiphertext(data);
}

Prevention

When it happens

Trigger: restoreAttributeViewHistory, restoreEmbeddedAttributeViewHistory, or backupBoundAttributeViewHistory reading a history file for an encrypted notebook's database where util.IsCiphertext(data) returns false — e.g. a history file produced before encryption was enabled, copied from a plaintext workspace, or corrupted so the ciphertext header is lost.

Common situations: Restoring database history after migrating notebooks between encrypted and non-encrypted workspaces; mixing history directories across versions; manually editing or syncing history files.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_block_history.go:29

	"github.com/88250/lute/ast"
	"github.com/88250/lute/parse"
	"github.com/siyuan-note/filelock"
	"github.com/siyuan-note/siyuan/kernel/av"
	"github.com/siyuan-note/siyuan/kernel/util"
)

func boundAttributeViewHistoryPath(historyDir, boxID, avID string) string {
	return filepath.Join(historyDir, boxID, "storage", "av", avID+".json")
}

func readBoundAttributeViewHistory(filename, boxID, avID string) (*av.AttributeView, error) {
	data, err := filelock.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if boxID != "" {
		if !util.IsCiphertext(data) {
			return nil, errors.New("encrypted database history is plaintext")
		}
		data, err = av.DecryptAVData(boxID, avID, data)
		if err != nil {
			return nil, err
		}
	} else if util.IsCiphertext(data) {
		return nil, errors.New("encrypted database history has no notebook context")
	}
	view, err := av.ParseAttributeViewData(avID, data)
	if err != nil {
		return nil, err
	}
	if view.ID != avID {
		return nil, errors.New("database history ID does not match its filename")
	}
	for _, kv := range view.KeyValues {
		if kv == nil || kv.Key == nil {
			return nil, errors.New("database history contains an invalid field")

View on GitHub (pinned to 9f775e8a12)