{"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":72,"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\tif !model.IsBoxUnlocked(boxID) {\n\t\treturn fmt.Errorf(\"%s\", model.Conf.Language(314))\n\t}\n\tif err := model.EnsureAssetLocal(src); err != nil {\n\t\treturn err\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}","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/file.go#L54-L90","documentation":"copyDecryptedAsset extracts the notebook (box) ID from the source assets path via model.ExtractBoxIDFromAssetsPath and requires it to refer to an encrypted notebook. If the path does not resolve to an assets path of an encrypted box (empty box ID, or box not marked encrypted), the function refuses to decrypt-copy with this error.","triggerScenarios":"Calling the decrypt-copy API with src pointing to an asset in a plain (unencrypted) notebook, or a path that is not a valid assets path (e.g. outside data/<boxID>/assets/, or a notebook ID extracted as empty string).","commonSituations":"Caller passes a global /assets/ path or a hand-built path not matching data/<boxID>/assets/; the notebook was converted to unencrypted; the asset belongs to a different feature (e.g. temp or emb folder).","solutions":["Verify the source path is of the form <workspace>/data/<boxID>/assets/... for an encrypted notebook.","Check model.IsEncryptedBox(model.ExtractBoxIDFromAssetsPath(src)) before calling the API and use the normal file-read path for unencrypted assets.","Pass the asset path exactly as returned by kernel asset APIs rather than constructing it manually.","If the notebook should be encrypted, confirm the box's encryption state/config; a recently migrated notebook may no longer be flagged encrypted."],"exampleFix":"// before\nboxID := model.ExtractBoxIDFromAssetsPath(src)\n_ = copyDecryptedAsset(src, dest) // errors for plain notebooks\n\n// after\nboxID := model.ExtractBoxIDFromAssetsPath(src)\nif boxID == \"\" || !model.IsEncryptedBox(boxID) {\n    return model.EnsureAssetLocal(src) // plain asset: use normal copy path\n}\nerr := copyDecryptedAsset(src, dest)","handlingStrategy":"validation","validationCode":"const boxID = extractBoxIDFromAssetsPath(src)\nif (!boxID || !isEncryptedBox(boxID)) usePlainCopyPath(src) else copyDecryptedAsset(src, dest)","typeGuard":"function isEncryptedAssetPath(p) {\n  const m = p.match(/[\\\\/]data[\\\\/]([^\\\\/]+)[\\\\/]assets[\\\\/]/)\n  return !!m && isEncryptedBox(m[1])\n}","tryCatchPattern":"try { await copyDecryptedAsset(src, dest) }\ncatch (e) { if (e.message === \"source is not an encrypted asset\") await plainCopy(src, dest) else throw e }","preventionTips":["Always obtain src from kernel asset APIs instead of string-building paths","Check box encryption state before choosing the decrypt path","Keep notebook id/encryption flags in sync after migrations"],"tags":["encryption","validation","filesystem"],"backgroundTag":"incompatible-source-type","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}