{"record":{"id":"f543dd6293461ecb","repo":"siyuan-note/siyuan","slug":"password-must-not-be-empty","errorCode":null,"errorMessage":"password must not be empty","messagePattern":"password must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":974,"sourceCode":"\treturn boxConf.Encrypted, nil\n}\n\n// cachedDEKs 缓存已解锁加密笔记本的 DEK，按 boxID 索引。\n// KEK 不全局缓存（\"严格每笔记本单独解锁\"语义）：UnlockBox 临时派生 KEK 解出 DEK 后即丢弃 KEK，\n// 仅保留 per-box DEK 供后续读写加解密。\nvar (\n\tcachedDEKs     = map[string][]byte{}\n\tcachedDEKsLock sync.RWMutex\n)\n\n// boxLastAccess 记录每个加密笔记本最近一次真实用户交互或显式保活时间（unix 纳秒），供自动锁定 cron 使用。\n// key: boxID, value: *atomic.Int64。UnlockBox 成功时初始化，Unmount 时清理。\nvar boxLastAccess sync.Map\n\n// EnableEncryptedNotebookWithSync 在启用前先完成同步；同步恢复了既有配置时只校验原主密码。\nfunc EnableEncryptedNotebookWithSync(password string) error {\n\tif len(password) == 0 {\n\t\treturn errors.New(\"password must not be empty\")\n\t}\n\tif err := SyncDataBeforeEnableEncryptedNotebook(); err != nil {\n\t\treturn err\n\t}\n\n\t// 同步可能已经从其他设备恢复了完整配置。此时校验用户输入的是原主密码，不能再创建新的密钥体系。\n\tif NotebookCryptoEnabled() {\n\t\tnotebookCryptoMu.Lock()\n\t\tdefer notebookCryptoMu.Unlock()\n\t\tkek, err := deriveKEK(password)\n\t\tif kek != nil {\n\t\t\tzeroAndClear(kek)\n\t\t}\n\t\treturn err\n\t}\n\treturn EnableEncryptedNotebook(password)\n}\n","sourceCodeStart":956,"sourceCodeEnd":992,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L956-L992","documentation":"EnableEncryptedNotebookWithSync guards against an empty master password before doing any work. Enabling the encrypted-notebook feature requires a user-chosen master password from which the KEK and MasterSalt are derived; an empty string cannot be a valid password, so the call is rejected immediately with errors.New(\"password must not be empty\"). This is the same guard as EnableEncryptedNotebook, applied before the pre-enable sync step.","triggerScenarios":"Calling kernel/model.EnableEncryptedNotebookWithSync(\"\") or with a password variable that was never populated (empty form field, missing config value, unset request parameter). Also occurs in tests such as TestRestoreNotebookCryptoConfigFromExistingBackup that invoke the function without supplying a password.","commonSituations":"Frontend sends an empty password field because the user left the dialog blank; a configuration/JSON payload omits the password key so it unmarshals to \"\"; a test helper forgets to set the password argument.","solutions":["Check the password is non-empty at the caller (form validation or API handler) before calling EnableEncryptedNotebookWithSync","If the password comes from config/request JSON, verify the key is present and populated before unmarshalling/marshalling the call","If this happens in tests, pass a real test password string instead of \"\"","If the user intentionally wants no password, note that the encrypted-notebook feature requires one; there is no empty-password mode"],"exampleFix":"// before\nerr := model.EnableEncryptedNotebookWithSync(req.Password) // req.Password may be \"\"\n// after\nif strings.TrimSpace(req.Password) == \"\" {\n    return errors.New(\"master password is required\")\n}\nerr := model.EnableEncryptedNotebookWithSync(req.Password)","handlingStrategy":"validation","validationCode":"if password == \"\" {\n    return errors.New(\"master password is required before enabling encrypted notebooks\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate password fields at the UI/API boundary before calling the kernel","When unmarshalling request JSON, ensure the password key is present, not just non-null","Add a non-empty assertion in tests that exercise the enable path","Never trim-then-pass silently: reject empty/whitespace-only passwords explicitly"],"tags":["validation","empty-password","encrypted-notebook"],"backgroundTag":"empty-required-field","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"}