{"record":{"id":"01c040afafe2af93","repo":"siyuan-note/siyuan","slug":"invalid-notebook-id","errorCode":null,"errorMessage":"invalid notebook ID","messagePattern":"invalid notebook ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1334,"sourceCode":"\t\t\t\tlogging.LogWarnf(\"fix encrypted box conf from backup [%s] failed: %s\", boxID, saveErr)\n\t\t\t}\n\t\t\tif needWriteNotebookCryptBackup(boxID, backup) {\n\t\t\t\tif writeErr := writeNotebookCryptBackup(boxID, backup); writeErr != nil {\n\t\t\t\t\tlogging.LogWarnf(\"refresh notebook crypt backup [%s] failed: %s\", boxID, writeErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn dek, backup, nil\n\t\t}\n\t}\n\treturn nil, nil, fmt.Errorf(\"decrypt box [%s] failed: incorrect key or corrupted data\", boxID)\n}\n\n// UnlockBox 用主密码派生 KEK，解出该笔记本的 DEK 并缓存。KEK 用完即弃，不全局缓存。\n// 每次调用都跑一次 Argon2id（约 1 秒），严格满足\"每笔记本单独解锁\"语义。\nfunc UnlockBox(boxID string, password string, boxEnc *conf.BoxEncryption) (err error) {\n\tinvalidateEncryptedPublishAccessCache()\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn errors.New(\"invalid notebook ID\")\n\t}\n\n\t// 全局配置锁先于笔记本生命周期锁获取（设计 §17 锁顺序约定），避免与持子系统锁后回取配置锁的路径死锁。\n\t// notebookCryptoMu 持锁期间调用的 deriveKEK/conf 修复只申请 Conf.m/cachedDEKsLock，不回取 box 生命周期锁。\n\tnotebookCryptoMu.Lock()\n\tdefer notebookCryptoMu.Unlock()\n\treleaseTransition := holdEncryptedBoxTransition(boxID)\n\tdefer releaseTransition()\n\treturn unlockBoxHeld(boxID, password, boxEnc)\n}\n\n// UnlockAndMountBox 在同一个笔记本转换锁内完成解锁和挂载，挂载失败时回滚本次新建的解锁状态。\nfunc UnlockAndMountBox(boxID, password string, boxEnc *conf.BoxEncryption) (alreadyMount bool, err error) {\n\tinvalidateEncryptedPublishAccessCache()\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn false, errors.New(\"invalid notebook ID\")\n\t}\n","sourceCodeStart":1316,"sourceCodeEnd":1352,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1316-L1352","documentation":"Thrown by UnlockBox (crypto.go:1334) when boxID does not satisfy ast.IsNodeIDPattern. SiYuan notebook IDs are fixed-format node IDs (timestamp-based, alphanumeric); a malformed ID cannot correspond to a real notebook, so unlocking is rejected before any lock is taken or KEK derived. This is an input-validation guard at the public API boundary.","triggerScenarios":"POST /api/notebook/unlockNotebook with a 'notebook' field that is empty, contains non-ID characters, is the wrong length, or is a path/special value. Note: the API handler also calls util.InvalidIDPattern(notebook) earlier, so reaching this model-layer guard means the caller invoked UnlockBox directly with a bad ID.","commonSituations":"Internal caller passing a stale, truncated, or hand-constructed ID. Copy/paste error in an ID. Test calling UnlockBox with a placeholder string.","solutions":["Pass a real notebook ID obtained from Conf.Box / ListNotebooks / the notebook list endpoint.","Validate the ID with ast.IsNodeIDPattern before calling UnlockBox.","If calling via HTTP, the API layer's InvalidIDPattern check returns first; fix the client to send the correct ID."],"exampleFix":"// before\nmodel.UnlockBox(\"my-notebook\", password, boxCrypt)\n\n// after\nid := box.ID // a valid node ID from Conf.Box / ListNotebooks\nmodel.UnlockBox(id, password, boxCrypt)","handlingStrategy":"validation","validationCode":"// Validate the notebook ID before unlocking.\nif !ast.IsNodeIDPattern(boxID) {\n    return fmt.Errorf(\"invalid notebook ID: %q\", boxID)\n}\nmodel.UnlockBox(boxID, password, boxCrypt)","typeGuard":"func isValidNotebookID(id string) bool { return ast.IsNodeIDPattern(id) }","tryCatchPattern":"if err := model.UnlockBox(boxID, password, boxCrypt); err != nil {\n    if err.Error() == \"invalid notebook ID\" {\n        respond(c, \"provide a valid notebook ID\")\n        return\n    }\n    respond(c, err.Error())\n}","preventionTips":["Always source notebook IDs from the list/create endpoints.","Validate with ast.IsNodeIDPattern at the call site.","Never construct IDs by hand."],"tags":["encryption","notebook-crypto","validation","input"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}