{"record":{"id":"6c3a8e14e097a99a","repo":"siyuan-note/siyuan","slug":"conf-language-314-6c3a8e","errorCode":null,"errorMessage":"Conf.Language(314)","messagePattern":"Conf\\.Language\\(314\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2584,"sourceCode":"\treturn &crypt, nil\n}\n\n// copyAssetDecryptIfEncrypted 把 srcPath 的 asset 复制到 destPath。\n// 若 srcPath 在已解锁的加密笔记本下，读密文→解密→写明文到 destPath（导出目录）；\n// 否则走 filelock.Copy 原路径（字节级复制，密文/明文均可）。\nfunc copyAssetDecryptIfEncrypted(srcPath, destPath string) error {\n\tif err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {\n\t\treturn err\n\t}\n\n\tboxID := ExtractBoxIDFromAssetsPath(srcPath)\n\tif boxID != \"\" && IsEncryptedBox(boxID) {\n\t\tHoldBoxReadLock(boxID)\n\t\tdefer ReleaseBoxReadLock(boxID)\n\t\tdek, err := GetDEKIfUnlocked(boxID)\n\t\tif err != nil {\n\t\t\t// 加密笔记本未解锁：fail-closed，拒绝复制（不复制密文，避免泄漏无效文件）\n\t\t\treturn errors.New(Conf.Language(314))\n\t\t}\n\t\traw, readErr := filelock.ReadFile(srcPath)\n\t\tif readErr != nil {\n\t\t\treturn readErr\n\t\t}\n\t\tdiskName := filepath.Base(srcPath)\n\t\tplain, decErr := DecryptAsset(boxID, diskName, dek, raw)\n\t\tif decErr != nil {\n\t\t\treturn errors.New(Conf.Language(316))\n\t\t}\n\t\tif err := filelock.WriteFile(destPath, plain); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\treturn filelock.Copy(srcPath, destPath)\n}\n","sourceCodeStart":2566,"sourceCodeEnd":2602,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L2566-L2602","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nawait fetchPost(\"/api/filetree/copyAsset\", {srcID: assetID, destID: otherBox});\n// after\nawait fetchPost(\"/api/notebook/unlockEncrypted\", {box: encryptedBoxID, password});\nawait fetchPost(\"/api/filetree/copyAsset\", {srcID: assetID, destID: otherBox});","handlingStrategy":"try-catch","validationCode":"// query notebook state first\nconst nb = await fetchPost(\"/api/notebook/info\", {notebook: boxID});\nif (nb.data.box && nb.data.box.encrypted && !unlockedBoxes.has(boxID)) {\n    await unlockNotebook(boxID, password);\n}","typeGuard":null,"tryCatchPattern":"try {\n    await copyAsset(srcPath, destPath);\n} catch (e) {\n    if (String(e.message).includes(\"unlock the encrypted notebook\")) {\n        await unlockNotebook(boxID, password);\n        return copyAsset(srcPath, destPath);\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["encryption","locked","notebook"],"backgroundTag":"authentication-required","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}