{"record":{"id":"068182d284d9d70f","repo":"siyuan-note/siyuan","slug":"invalid-sy-base-name-s-must-end-with-sy","errorCode":null,"errorMessage":"invalid .sy base name [%s]: must end with .sy","messagePattern":"invalid \\.sy base name \\[(.+?)\\]: must end with \\.sy","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/filesys/crypto_hook.go","lineNumber":112,"sourceCode":"\t}()\n\taad, err := SyAAD(boxID, relativePath)\n\tif err != nil {\n\t\treturn nil, err\n\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","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/filesys/crypto_hook.go#L94-L130","documentation":"SyObjectBase validates that a relative path names a .sy document file: it extracts the base name and requires the \".sy\" suffix (the next check also requires a node-ID stem). This guard feeds SyAAD, which builds the authenticated associated data from the stable file base name, so any non-.sy path is rejected before crypto operations.","triggerScenarios":"Calling SyObjectBase (directly or via SyAAD) with a relativePath whose base name does not end with .sy, e.g. \"20260101120000-abcdefg.json\", \"assets/foo.png\", a directory path, or an empty string.","commonSituations":"Passing asset files or history snapshots to an API expecting .sy document paths; a path separator bug leaving a directory name as the base; constructing paths programmatically with the wrong extension.","solutions":["Pass only document paths whose base name ends in .sy.","Strip or fix a wrong extension before calling; convert the source to a .sy document if needed.","Check the path is a file path, not a directory or asset path."],"exampleFix":"// before\nSyObjectBase(\"20260101120000-abcdefg.json\")\n// after\nSyObjectBase(\"20260101120000-abcdefg.sy\")","handlingStrategy":"validation","validationCode":"function isSyBase(p) {\n  const base = p.split(/[\\\\/]/).pop();\n  return typeof base === \"string\" && base.endsWith(\".sy\");\n}","typeGuard":null,"tryCatchPattern":"if err := filesys.SyObjectBase(relPath); err != nil {\n    // non-.sy path: route to the correct handler or reject\n}","preventionTips":["Only feed .sy document paths into crypto/AAD APIs.","Keep asset and history paths on their own code paths.","Build paths from tree IDs so the extension is always .sy."],"tags":["validation","filesystem","encryption"],"backgroundTag":"invalid-argument-format","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"}