siyuan-note/siyuan · error
invalid notebook ID
Error message
invalid notebook ID
What it means
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.
Source
Thrown at kernel/model/crypto.go:1334
logging.LogWarnf("fix encrypted box conf from backup [%s] failed: %s", boxID, saveErr)
}
if needWriteNotebookCryptBackup(boxID, backup) {
if writeErr := writeNotebookCryptBackup(boxID, backup); writeErr != nil {
logging.LogWarnf("refresh notebook crypt backup [%s] failed: %s", boxID, writeErr)
}
}
return dek, backup, nil
}
}
return nil, nil, fmt.Errorf("decrypt box [%s] failed: incorrect key or corrupted data", boxID)
}
// UnlockBox 用主密码派生 KEK,解出该笔记本的 DEK 并缓存。KEK 用完即弃,不全局缓存。
// 每次调用都跑一次 Argon2id(约 1 秒),严格满足"每笔记本单独解锁"语义。
func UnlockBox(boxID string, password string, boxEnc *conf.BoxEncryption) (err error) {
invalidateEncryptedPublishAccessCache()
if !ast.IsNodeIDPattern(boxID) {
return errors.New("invalid notebook ID")
}
// 全局配置锁先于笔记本生命周期锁获取(设计 §17 锁顺序约定),避免与持子系统锁后回取配置锁的路径死锁。
// notebookCryptoMu 持锁期间调用的 deriveKEK/conf 修复只申请 Conf.m/cachedDEKsLock,不回取 box 生命周期锁。
notebookCryptoMu.Lock()
defer notebookCryptoMu.Unlock()
releaseTransition := holdEncryptedBoxTransition(boxID)
defer releaseTransition()
return unlockBoxHeld(boxID, password, boxEnc)
}
// UnlockAndMountBox 在同一个笔记本转换锁内完成解锁和挂载,挂载失败时回滚本次新建的解锁状态。
func UnlockAndMountBox(boxID, password string, boxEnc *conf.BoxEncryption) (alreadyMount bool, err error) {
invalidateEncryptedPublishAccessCache()
if !ast.IsNodeIDPattern(boxID) {
return false, errors.New("invalid notebook ID")
}
View on GitHub (pinned to 251596fc0d)
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.
Example fix
// before
model.UnlockBox("my-notebook", password, boxCrypt)
// after
id := box.ID // a valid node ID from Conf.Box / ListNotebooks
model.UnlockBox(id, password, boxCrypt) Defensive patterns
Strategy: validation
Validate before calling
// Validate the notebook ID before unlocking.
if !ast.IsNodeIDPattern(boxID) {
return fmt.Errorf("invalid notebook ID: %q", boxID)
}
model.UnlockBox(boxID, password, boxCrypt) Type guard
func isValidNotebookID(id string) bool { return ast.IsNodeIDPattern(id) } Try / catch
if err := model.UnlockBox(boxID, password, boxCrypt); err != nil {
if err.Error() == "invalid notebook ID" {
respond(c, "provide a valid notebook ID")
return
}
respond(c, err.Error())
} Prevention
- Always source notebook IDs from the list/create endpoints.
- Validate with ast.IsNodeIDPattern at the call site.
- Never construct IDs by hand.
When it happens
Trigger: 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.
Common situations: Internal caller passing a stale, truncated, or hand-constructed ID. Copy/paste error in an ID. Test calling UnlockBox with a placeholder string.
Related errors
- Argon2id KeyLength must be 32
- Argon2id Memory too low (minimum 64 MB)
- Argon2id Memory too high (maximum 256 MB)
- Argon2id Iterations too low (minimum 3)
- Argon2id Iterations too high (maximum 10)
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/01c040afafe2af93.
Report an issue: GitHub.