siyuan-note/siyuan · error
source is not an encrypted asset
Error message
source is not an encrypted asset
What it means
Returned by copyDecryptedAsset (file.go:60) when the source path is not recognized as belonging to an encrypted asset. Either ExtractBoxIDFromAssetsPath(src) returned empty (the path is not under a notebook's assets/ folder), or the extracted box is not an encrypted notebook (IsEncryptedBox is false). The decryption flow only operates on genuine encrypted-box assets, so any other source is rejected before acquiring the box read lock.
Source
Thrown at kernel/api/file.go:60
// rejectEncryptedBoxPath 检查 absPath 是否落在加密笔记本目录下(含 symlink 绕过),是则返回 true。
// 原始文件 API(getFile/putFile/copyFile/renameFile/removeFile)是绕过加密层的逃生口,
// 对加密笔记本的任何文件读写都应拒绝——合法读写走专用 API(upload/getBlockKramdown 等,已加密感知),
// 避免密文泄漏给插件或明文破坏加密格式。
// 防止 symlink 绕过:找到最长已存在的父路径,解析 symlink 后拼回剩余路径,再检查是否落入加密 box。
func rejectEncryptedBoxPath(absPath string) bool {
return model.EncryptedRawPathBoxID(absPath) != ""
}
// copyDecryptedAsset 将加密 asset 解密后复制到目标路径(dest 必须在工作区外)。
func copyDecryptedAsset(src, dest string) error {
// 安全守卫:dest 必须在工作区外,防止解密后的明文落入工作区普通目录
if gulu.File.IsSubPath(util.WorkspaceDir, dest) {
return fmt.Errorf("refuse to write decrypted asset inside workspace")
}
boxID := model.ExtractBoxIDFromAssetsPath(src)
if boxID == "" || !model.IsEncryptedBox(boxID) {
return fmt.Errorf("source is not an encrypted asset")
}
model.HoldBoxReadLock(boxID)
defer model.ReleaseBoxReadLock(boxID)
dek, dekErr := model.GetDEKIfUnlocked(boxID)
if dekErr != nil {
return dekErr
}
diskName := filepath.Base(src)
data, readErr := os.ReadFile(src)
if readErr != nil {
return readErr
}
plain, decErr := model.DecryptAsset(boxID, diskName, dek, data)
if decErr != nil {
return decErr
}
if writeErr := os.WriteFile(dest, plain, 0644); writeErr != nil {
return writeErrView on GitHub (pinned to 251596fc0d)
Solutions
- Confirm the source actually lives under an encrypted notebook's assets directory (check IsEncryptedBox on the parsed box ID).
- If the asset is not encrypted, use a plain file copy instead of copyDecryptedAsset.
- Ensure the path is in the canonical absolute assets form expected by ExtractBoxIDFromAssetsPath (model/crypto.go:2137).
Defensive patterns
Strategy: validation
Validate before calling
boxID := model.ExtractBoxIDFromAssetsPath(src)
if boxID == "" || !model.IsEncryptedBox(boxID) {
// not an encrypted asset: use plain copy instead of copyDecryptedAsset
} Prevention
- Branch on IsEncryptedBox: only call copyDecryptedAsset for genuinely encrypted assets.
- Keep source paths in the canonical data/<box>/assets/<diskname> layout.
- Log boxID extraction results to catch path-format regressions early.
When it happens
Trigger: Calling copyDecryptedAsset with a `src` that points to a regular (non-encrypted) notebook asset, a file outside any assets/ folder, or a path whose box ID cannot be parsed by ExtractBoxIDFromAssetsPath. The check at file.go:59 fails the IsEncryptedBox/boxID guards.
Common situations: Passing a plaintext asset path to the decryption helper (the asset isn't encrypted, so no decryption is needed). Path format mismatch (the assets-path parser expects the standard data/<box>/assets/<diskname> layout). Notebook that was formerly encrypted but is no longer, or vice versa. Bug in a caller that didn't distinguish encrypted from plain assets.
Related errors
- refuse to write decrypted asset inside workspace
- encrypted notebook is locked, please unlock it first
- import path is not sub path of import dir
- export source [%s] is not a regular file
- invalid import token
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/590cbef7a47e5087.
Report an issue: GitHub.