{"record":{"id":"137d2d4384429836","repo":"siyuan-note/siyuan","slug":"encrypted-repository-data-is-missing-notebook-cont","errorCode":null,"errorMessage":"encrypted repository data is missing notebook context","messagePattern":"encrypted repository data is missing notebook context","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/repository.go","lineNumber":741,"sourceCode":"\t\t}\n\n\t\ttitle = tree.Root.IALAttr(\"title\")\n\t\trootID = tree.Root.ID\n\t}\n\treturn\n}\n\n// decryptRepoDataIfNeeded 判断仓库数据是否属于加密笔记本，如果是则按路径类型分流解密。\n// file.Path 格式：/<boxID>/...\n// .sy → DecryptFile，assets/* → DecryptAsset，storage/av/*.json → av.DecryptAVData。\n// 密文缺少有效路径上下文、笔记本未解锁或认证失败时返回错误，不允许调用方按明文继续处理。\nfunc decryptRepoDataIfNeeded(data []byte, filePath string) ([]byte, error) {\n\trelPath := strings.TrimPrefix(filePath, \"/\")\n\tparts := strings.SplitN(relPath, \"/\", 2)\n\tencryptedPayload := util.IsCiphertext(data) || bytes.HasPrefix(data, encryptedAssetMagic)\n\tif len(parts) < 2 || !ast.IsNodeIDPattern(parts[0]) {\n\t\tif encryptedPayload {\n\t\t\treturn nil, errors.New(\"encrypted repository data is missing notebook context\")\n\t\t}\n\t\treturn data, nil\n\t}\n\tboxID := parts[0]\n\tif !IsEncryptedBox(boxID) {\n\t\tif encryptedPayload {\n\t\t\treturn nil, fmt.Errorf(\"encrypted repository data has no matching notebook [%s]\", boxID)\n\t\t}\n\t\treturn data, nil\n\t}\n\t// 持读锁，防止 LockBox 在解密期间清 DEK/缓存\n\tHoldBoxReadLock(boxID)\n\tdefer ReleaseBoxReadLock(boxID)\n\tdek, err := GetDEKIfUnlocked(boxID)\n\tif err != nil {\n\t\treturn nil, errors.New(Conf.Language(314))\n\t}\n\tboxRelPath := parts[1]","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/repository.go#L723-L759","documentation":"decryptRepoDataIfNeeded: the data is ciphertext (IsCiphertext or encryptedAssetMagic) but the file path splits into fewer than 2 parts or its first segment is not a valid node ID (20-char boxID). With no valid boxID the code cannot route decryption, and fail-closed semantics forbid treating ciphertext as plaintext. Distinct from 862 (payload-context check in the caller) and 867 (boxID valid but not a registered encrypted box).","triggerScenarios":"A snapshot object whose path is malformed (no /<boxID>/ prefix, or boxID segment not matching ast.IsNodeIDPattern) while its stored bytes are ciphertext; reached during rollback/open/export of such a file.","commonSituations":"Corrupted repo index pointing at a misnamed object; manually relocated/renamed snapshot files; cross-version repo store with a path-format change.","solutions":["Check the logged filePath and confirm it starts with /<20-char-boxID>/.","If the repo index is corrupt, reset the data repo and rebuild from a known-good snapshot set.","Do not strip or rewrite path prefixes to bypass the check — it exists to prevent decrypting ciphertext without a key."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reject ciphertext whose path cannot yield a valid boxID\nrel := strings.TrimPrefix(filePath, \"/\")\nparts := strings.SplitN(rel, \"/\", 2)\nencrypted := util.IsCiphertext(data) || bytes.HasPrefix(data, encryptedAssetMagic)\nif encrypted && (len(parts) < 2 || !ast.IsNodeIDPattern(parts[0])) {\n    return errors.New(\"encrypted repository data is missing notebook context\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Preserve the /<boxID>/ prefix on every repo object path.","Run integrity checks after any manual repo-store manipulation.","Never rewrite this guard to fall back to plaintext for ciphertext."],"tags":["repo","encryption","data-integrity","fail-closed"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}