{"record":{"id":"6b232af8e87b2e9c","repo":"siyuan-note/siyuan","slug":"invalid-sy-base-name-s-stem-is-not-a-node-id","errorCode":null,"errorMessage":"invalid .sy base name [%s]: stem is not a node ID","messagePattern":"invalid \\.sy base name \\[(.+?)\\]: stem is not a node ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/filesys/crypto_hook.go","lineNumber":116,"sourceCode":"\t}\n\treturn util.DecryptWithAAD(fileKey, data, []byte(aad))\n}\n\n// SyObjectBase 从 box 内相对路径提取稳定文件基名并校验合法性。\n// 接受形如 <rootID>.sy 的基名：扩展名必须是 .sy，且 stem 是合法节点 ID。\n// 非法扩展名或非节点 ID 模式返回错误，避免把任意路径当 AAD 绑定物产生不可解密的数据。\n// 由 filesys、model 历史查看/回滚、import 等所有 .sy 加解密路径共同使用，保证 AAD 一致。\nfunc SyObjectBase(relativePath string) (string, error) {\n\tbase := relativePath\n\tif idx := strings.LastIndexAny(relativePath, \"/\\\\\"); idx >= 0 {\n\t\tbase = relativePath[idx+1:]\n\t}\n\tif !strings.HasSuffix(base, \".sy\") {\n\t\treturn \"\", fmt.Errorf(\"invalid .sy base name [%s]: must end with .sy\", base)\n\t}\n\tstem := strings.TrimSuffix(base, \".sy\")\n\tif !ast.IsNodeIDPattern(stem) {\n\t\treturn \"\", fmt.Errorf(\"invalid .sy base name [%s]: stem is not a node ID\", base)\n\t}\n\treturn base, nil\n}\n\n// SyAAD 构造 .sy 密文的 AAD：siyuan:file:<boxID>:<稳定文件基名>。\n// 父目录不进 AAD——同 box 内文件名不变的移动允许原样 Rename 密文，内容/box/类型/对象 ID 仍受认证。\nfunc SyAAD(boxID, relativePath string) (string, error) {\n\tbase, err := SyObjectBase(relativePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn \"siyuan:file:\" + boxID + \":\" + base, nil\n}\n\n// encryptedBox 判断 boxID 是否为已解锁的加密 box，供 filesys 内部分流（如静默修正禁用）。\n// 通过 DEKProvider 探测：返回非 nil dek 即加密且已解锁。\nfunc encryptedBox(boxID string) bool {\n\tif DEKProvider == nil {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/filesys/crypto_hook.go#L98-L134","documentation":"Returned by `filesys.SyObjectBase` when the `.sy` basename's stem (the filename minus `.sy`) does not match `ast.IsNodeIDPattern`. SiYuan `.sy` files are keyed by node IDs (the standard block-ID format used throughout the kernel), so the stem must be a valid node ID for the AAD to bind to a real object and remain decryptable after moves.","triggerScenarios":"A `.sy` file whose name is not a node ID, e.g. `notes.sy`, `temp.sy`, or `My Document.sy`. The check protects the encryption layer from attaching AAD to arbitrary filenames that would later fail to decrypt.","commonSituations":"Manually created or renamed `.sy` files in an encrypted notebook; import logic that generated a non-ID filename; a corrupted/tampered tree whose root ID was rewritten to an invalid string.","solutions":["Rename the file to `<valid-node-id>.sy`, where the node ID is the document's root block ID.","Regenerate the document so the kernel writes the correctly-named `.sy` file.","If decrypting legacy data, confirm the file was not renamed outside SiYuan; restore from history if so."],"exampleFix":"// before\n// file on disk: data/<box>/draft.sy\n// after\n// file on disk: data/<box>/20240101000000-abcdef1234567.sy","handlingStrategy":"type-guard","validationCode":"// Confirm the stem is a node ID before calling SyObjectBase:\nstem := strings.TrimSuffix(base, \".sy\")\nif !ast.IsNodeIDPattern(stem) {\n    return \"\", fmt.Errorf(\"stem not a node ID: %s\", base)\n}","typeGuard":"func isValidSyBasename(base string) bool {\n    if !strings.HasSuffix(base, \".sy\") {\n        return false\n    }\n    return ast.IsNodeIDPattern(strings.TrimSuffix(base, \".sy\"))\n}","tryCatchPattern":null,"preventionTips":["Always name .sy files by the document root block ID.","Never hand-rename .sy files in an encrypted notebook.","When importing, generate IDs through the kernel, not by string manipulation."],"tags":["encryption","validation","node-id","aad"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}