{"record":{"id":"dcb5d4cdc77b5b79","repo":"siyuan-note/siyuan","slug":"invalid-encrypted-asset-original-name","errorCode":null,"errorMessage":"invalid encrypted asset original name","messagePattern":"invalid encrypted asset original name","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2348,"sourceCode":"\tif err := json.Unmarshal(data, metadata); err != nil {\n\t\treturn nil, err\n\t}\n\tvar version struct {\n\t\tSpec        json.RawMessage `json:\"spec\"`\n\t\tContainerID json.RawMessage `json:\"containerID\"`\n\t}\n\tif err := json.Unmarshal(data, &version); err != nil {\n\t\treturn nil, err\n\t}\n\t// 仅认证元数据同时缺少两个版本字段时按旧容器读取，显式空值或不完整的新格式不能降级。\n\tif len(version.Spec) == 0 && len(version.ContainerID) == 0 {\n\t\tmetadata.Spec = encryptedAssetLegacySpec\n\t} else if metadata.Spec != encryptedAssetSpec || len(metadata.ContainerID) != encryptedAssetContainerIDSize {\n\t\treturn nil, errors.New(\"unsupported encrypted asset container version\")\n\t}\n\tif metadata.OriginalName == \"\" || metadata.OriginalName == \".\" ||\n\t\tfilepath.Base(metadata.OriginalName) != metadata.OriginalName || strings.ContainsAny(metadata.OriginalName, `/\\`) {\n\t\treturn nil, errors.New(\"invalid encrypted asset original name\")\n\t}\n\tif metadata.Size < 0 {\n\t\treturn nil, errors.New(\"invalid encrypted asset content metadata\")\n\t}\n\tchunks := uint64(metadata.Size) / encryptedAssetChunkSize\n\tif metadata.Size%encryptedAssetChunkSize != 0 || metadata.Size == 0 {\n\t\tchunks++\n\t}\n\tif metadata.Chunks != chunks {\n\t\treturn nil, errors.New(\"invalid encrypted asset chunk count\")\n\t}\n\treturn metadata, nil\n}\n\n// DecryptAssetWithName 解密资源内容并返回原始名称。\nfunc DecryptAssetWithName(boxID, diskName string, dek, ciphertext []byte) (plaintext []byte, originalName string, err error) {\n\tvar output bytes.Buffer\n\toriginalName, err = DecryptAssetToWriter(boxID, diskName, dek, bytes.NewReader(ciphertext), &output)","sourceCodeStart":2330,"sourceCodeEnd":2366,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L2330-L2366","documentation":"decryptAssetMetadata validates metadata.OriginalName: it must be non-empty, not \".\", equal to its own filepath.Base (no directory components), and contain no '/' or '\\\\'. This prevents path traversal and directory escapes when the original name is restored to disk.","triggerScenarios":"Decrypting an asset whose metadata original name was crafted with '../', absolute paths, embedded separators, or is empty — typically a tampered container or a bug in code that populated metadata before encryption.","commonSituations":"Assets imported by third-party tooling that wrote unsafe names into metadata, malicious containers crafted to overwrite files outside the assets dir, corruption flipping bytes in the name field.","solutions":["Re-encrypt the asset with a sanitized original name (base name only, no separators)","If the container is untrusted, do not decrypt-and-restore it; treat it as hostile input","Check the encryption-side code path that built encryptedAssetMetadata to ensure it passes filepath.Base of the real name","Restore the asset from a trusted backup if metadata was corrupted"],"exampleFix":"// before: encrypt with a user-supplied path as name\nmeta.OriginalName = userPath\n// after: store only the base name\nmeta.OriginalName = filepath.Base(userPath)","handlingStrategy":"validation","validationCode":"func safeAssetName(name string) bool {\n    return name != \"\" && name != \".\" && filepath.Base(name) == name && !strings.ContainsAny(name, `/\\`)\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"original name\") {\n    // reject the container as untrusted/tampered\n}","preventionTips":["Always store filepath.Base of the original name in metadata","Treat containers from untrusted sources as hostile; never restore names verbatim","Sanitize names at import time, before encryption"],"tags":["encryption","assets","path-traversal","security","go-kernel"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}