{"record":{"id":"ea3760c3873b634e","repo":"siyuan-note/siyuan","slug":"invalid-attribute-view-id-s","errorCode":null,"errorMessage":"invalid attribute view ID: %s","messagePattern":"invalid attribute view ID: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":57,"sourceCode":"\t\"github.com/88250/lute/ast\"\n\t\"github.com/88250/lute/parse\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/jinzhu/copier\"\n\t\"github.com/siyuan-note/filelock\"\n\t\"github.com/siyuan-note/logging\"\n\t\"github.com/siyuan-note/siyuan/kernel/av\"\n\t\"github.com/siyuan-note/siyuan/kernel/cache\"\n\t\"github.com/siyuan-note/siyuan/kernel/filesys\"\n\t\"github.com/siyuan-note/siyuan/kernel/search\"\n\t\"github.com/siyuan-note/siyuan/kernel/sql\"\n\t\"github.com/siyuan-note/siyuan/kernel/treenode\"\n\t\"github.com/siyuan-note/siyuan/kernel/util\"\n\t\"github.com/xrash/smetrics\"\n)\n\nfunc ValidateUnusedAttributeView(id string) error {\n\tif !ast.IsNodeIDPattern(id) {\n\t\treturn fmt.Errorf(\"invalid attribute view ID: %s\", id)\n\t}\n\tfor _, item := range UnusedAttributeViews(false) {\n\t\tif item.Item == id {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"attribute view is not unused: %s\", id)\n}\n\nfunc RemoveUnusedAttributeView(id string) (err error) {\n\t// 防御性校验：ID 必须是合法的节点 ID 格式，防止通过路径穿越读取或删除任意文件\n\tif err = ValidateUnusedAttributeView(id); err != nil {\n\t\treturn\n\t}\n\n\tbase := filepath.Join(util.DataDir, \"storage\", \"av\")\n\tabsPath := filepath.Join(base, id+\".json\")\n\tif !filelock.IsExist(absPath) {","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view.go#L39-L75","documentation":"ValidateUnusedAttributeView first checks that the supplied ID matches the node-ID pattern (ast.IsNodeIDPattern). A non-conforming ID fails immediately with this error. This is a defensive check so downstream file operations (which build paths from the ID) cannot be abused for path traversal or target arbitrary files.","triggerScenarios":"Calling the RemoveUnusedAttributeView API (or ValidateUnusedAttributeView directly) with an id that is not a 64-bit-hex-style node ID — e.g. containing '/', '..\\\\', empty string, or random text.","commonSituations":"Hand-crafted API calls with guessed or user-supplied IDs; plugins passing a database/AV key instead of a block/AV ID; URL-encoding or truncation mangling the ID; hostile requests probing for path traversal.","solutions":["Pass the exact attribute-view ID as listed by UnusedAttributeViews output (item.Item)","Validate the ID client-side against the node-ID pattern before calling","Do not accept free-text IDs from users in plugins/scripts; select from the unused-AV list instead"],"exampleFix":"// before\nfetchPost('/api/attr/removeUnusedAttributeView', { id: userInput })\n// after\nif (!/^[0-9a-f]{20,}$/.test(id)) throw new Error('bad AV id');\nfetchPost('/api/attr/removeUnusedAttributeView', { id })","handlingStrategy":"validation","validationCode":"// SiYuan node IDs are time-ordered hex strings\nfunction looksLikeNodeID(id) {\n  return typeof id === 'string' && /^[0-9a-f]{14,26}$/.test(id);\n}\nif (!looksLikeNodeID(id)) throw new Error('invalid AV id');","typeGuard":"const isNodeID = (v) => typeof v === 'string' && v.length > 0 && !v.includes('/') && !v.includes('..') && /^[0-9a-f]+$/.test(v);","tryCatchPattern":"try { await removeUnusedAttributeView(id) } catch (e) {\n  if (e.message.includes('invalid attribute view ID')) {\n    // caller supplied a malformed id; surface to user/plugin developer\n  }\n}","preventionTips":["Always source AV IDs from UnusedAttributeViews output, not free text","Reject IDs containing path characters at the plugin boundary","Keep IDs intact through URL encoding/decoding"],"tags":["validation","identifier","attribute-view"],"backgroundTag":"invalid-identifier-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"}