siyuan-note/siyuan · error
Conf.Language(314)
Error message
Conf.Language(314)
What it means
When copying an asset out of an encrypted notebook, the code takes the box read lock and asks GetDEKIfUnlocked for the notebook's data-encryption key. If the notebook is still locked (user has not supplied the passphrase, so no DEK is cached in memory), the operation fails closed with the localized message for language key 314 ('Please unlock the encrypted notebook first') rather than copying ciphertext that would be unusable or leak file material.
Source
Thrown at kernel/model/crypto.go:2584
return &crypt, nil
}
// copyAssetDecryptIfEncrypted 把 srcPath 的 asset 复制到 destPath。
// 若 srcPath 在已解锁的加密笔记本下,读密文→解密→写明文到 destPath(导出目录);
// 否则走 filelock.Copy 原路径(字节级复制,密文/明文均可)。
func copyAssetDecryptIfEncrypted(srcPath, destPath string) error {
if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
return err
}
boxID := ExtractBoxIDFromAssetsPath(srcPath)
if boxID != "" && IsEncryptedBox(boxID) {
HoldBoxReadLock(boxID)
defer ReleaseBoxReadLock(boxID)
dek, err := GetDEKIfUnlocked(boxID)
if err != nil {
// 加密笔记本未解锁:fail-closed,拒绝复制(不复制密文,避免泄漏无效文件)
return errors.New(Conf.Language(314))
}
raw, readErr := filelock.ReadFile(srcPath)
if readErr != nil {
return readErr
}
diskName := filepath.Base(srcPath)
plain, decErr := DecryptAsset(boxID, diskName, dek, raw)
if decErr != nil {
return errors.New(Conf.Language(316))
}
if err := filelock.WriteFile(destPath, plain); err != nil {
return err
}
return nil
}
return filelock.Copy(srcPath, destPath)
}
View on GitHub (pinned to 8641553a1f)
Solutions
- Unlock the encrypted notebook first (open it in the UI and enter the passphrase, or call the kernel unlock API with the correct password) and retry the copy
- For automation, call the unlock endpoint as part of the script before any asset operations
- If the notebook cannot be unlocked, the password or key material is wrong — recover from the BoxCrypt backup instead of retrying
Example fix
// before
await fetchPost("/api/filetree/copyAsset", {srcID: assetID, destID: otherBox});
// after
await fetchPost("/api/notebook/unlockEncrypted", {box: encryptedBoxID, password});
await fetchPost("/api/filetree/copyAsset", {srcID: assetID, destID: otherBox}); Defensive patterns
Strategy: try-catch
Validate before calling
// query notebook state first
const nb = await fetchPost("/api/notebook/info", {notebook: boxID});
if (nb.data.box && nb.data.box.encrypted && !unlockedBoxes.has(boxID)) {
await unlockNotebook(boxID, password);
} Try / catch
try {
await copyAsset(srcPath, destPath);
} catch (e) {
if (String(e.message).includes("unlock the encrypted notebook")) {
await unlockNotebook(boxID, password);
return copyAsset(srcPath, destPath);
}
throw e;
} Prevention
- Track unlock state client-side; re-unlock after kernel restarts
- Batch asset operations immediately after unlocking to avoid re-lock timeouts
- Inform users before scripted asset operations on encrypted notebooks
When it happens
Trigger: Invoking asset copy/export APIs (e.g. copying an asset to another notebook or exporting) for a box where IsEncryptedBox(boxID) is true while the notebook has not been unlocked with its password in the current session (or was re-locked after timeout).
Common situations: Scripting/API automation against the kernel without performing the unlock step first; kernel restarted, clearing in-memory DEKs, then an asset operation is attempted; multiple users on one workspace where only one has unlocked the notebook.
Related errors
- Please unlock the encrypted notebook first
- Conf.Language(314)
- Related operations are being processed, please try again lat
- Encrypted notebooks do not support this operation
- encrypted box db not opened for box
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/6c3a8e14e097a99a.
Report an issue: GitHub.