{"record":{"id":"590cbef7a47e5087","repo":"siyuan-note/siyuan","slug":"source-is-not-an-encrypted-asset","errorCode":null,"errorMessage":"source is not an encrypted asset","messagePattern":"source is not an encrypted asset","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/file.go","lineNumber":60,"sourceCode":"\n// rejectEncryptedBoxPath 检查 absPath 是否落在加密笔记本目录下（含 symlink 绕过），是则返回 true。\n// 原始文件 API（getFile/putFile/copyFile/renameFile/removeFile）是绕过加密层的逃生口，\n// 对加密笔记本的任何文件读写都应拒绝——合法读写走专用 API（upload/getBlockKramdown 等，已加密感知），\n// 避免密文泄漏给插件或明文破坏加密格式。\n// 防止 symlink 绕过：找到最长已存在的父路径，解析 symlink 后拼回剩余路径，再检查是否落入加密 box。\nfunc rejectEncryptedBoxPath(absPath string) bool {\n\treturn model.EncryptedRawPathBoxID(absPath) != \"\"\n}\n\n// copyDecryptedAsset 将加密 asset 解密后复制到目标路径（dest 必须在工作区外）。\nfunc copyDecryptedAsset(src, dest string) error {\n\t// 安全守卫：dest 必须在工作区外，防止解密后的明文落入工作区普通目录\n\tif gulu.File.IsSubPath(util.WorkspaceDir, dest) {\n\t\treturn fmt.Errorf(\"refuse to write decrypted asset inside workspace\")\n\t}\n\tboxID := model.ExtractBoxIDFromAssetsPath(src)\n\tif boxID == \"\" || !model.IsEncryptedBox(boxID) {\n\t\treturn fmt.Errorf(\"source is not an encrypted asset\")\n\t}\n\tmodel.HoldBoxReadLock(boxID)\n\tdefer model.ReleaseBoxReadLock(boxID)\n\tdek, dekErr := model.GetDEKIfUnlocked(boxID)\n\tif dekErr != nil {\n\t\treturn dekErr\n\t}\n\tdiskName := filepath.Base(src)\n\tdata, readErr := os.ReadFile(src)\n\tif readErr != nil {\n\t\treturn readErr\n\t}\n\tplain, decErr := model.DecryptAsset(boxID, diskName, dek, data)\n\tif decErr != nil {\n\t\treturn decErr\n\t}\n\tif writeErr := os.WriteFile(dest, plain, 0644); writeErr != nil {\n\t\treturn writeErr","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/api/file.go#L42-L78","documentation":"Returned by copyDecryptedAsset (file.go:60) when the source path is not recognized as belonging to an encrypted asset. Either ExtractBoxIDFromAssetsPath(src) returned empty (the path is not under a notebook's assets/ folder), or the extracted box is not an encrypted notebook (IsEncryptedBox is false). The decryption flow only operates on genuine encrypted-box assets, so any other source is rejected before acquiring the box read lock.","triggerScenarios":"Calling copyDecryptedAsset with a `src` that points to a regular (non-encrypted) notebook asset, a file outside any assets/ folder, or a path whose box ID cannot be parsed by ExtractBoxIDFromAssetsPath. The check at file.go:59 fails the IsEncryptedBox/boxID guards.","commonSituations":"Passing a plaintext asset path to the decryption helper (the asset isn't encrypted, so no decryption is needed). Path format mismatch (the assets-path parser expects the standard data/<box>/assets/<diskname> layout). Notebook that was formerly encrypted but is no longer, or vice versa. Bug in a caller that didn't distinguish encrypted from plain assets.","solutions":["Confirm the source actually lives under an encrypted notebook's assets directory (check IsEncryptedBox on the parsed box ID).","If the asset is not encrypted, use a plain file copy instead of copyDecryptedAsset.","Ensure the path is in the canonical absolute assets form expected by ExtractBoxIDFromAssetsPath (model/crypto.go:2137)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"boxID := model.ExtractBoxIDFromAssetsPath(src)\nif boxID == \"\" || !model.IsEncryptedBox(boxID) {\n  // not an encrypted asset: use plain copy instead of copyDecryptedAsset\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Branch on IsEncryptedBox: only call copyDecryptedAsset for genuinely encrypted assets.","Keep source paths in the canonical data/<box>/assets/<diskname> layout.","Log boxID extraction results to catch path-format regressions early."],"tags":["encryption","security","filesystem","kernel"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}