{"record":{"id":"6f2821c8e352456f","repo":"siyuan-note/siyuan","slug":"invalid-export-path","errorCode":null,"errorMessage":"invalid export path","messagePattern":"invalid export path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/encrypted_export.go","lineNumber":186,"sourceCode":"// normalExportTempName 避免普通导出使用加密导出的第一层归属目录，保留归档内部的原始名称。\nfunc normalExportTempName(name string) string {\n\tparts := strings.SplitN(filepath.ToSlash(filepath.Clean(name)), \"/\", 2)\n\tif ast.IsNodeIDPattern(parts[0]) {\n\t\tparts[0] = \"export-\" + parts[0]\n\t}\n\treturn filepath.FromSlash(strings.Join(parts, \"/\"))\n}\n\n// AcquireExportArtifactLease 为导出产物取得覆盖整个复制过程的生命周期租约。\nfunc AcquireExportArtifactLease(exportPath string) (lease *ExportArtifactLease, err error) {\n\tif after, ok := strings.CutPrefix(exportPath, \"/export/\"); ok {\n\t\tfileName, decodeErr := url.PathUnescape(after)\n\t\tif decodeErr != nil {\n\t\t\treturn nil, decodeErr\n\t\t}\n\t\tfileName = filepath.Clean(fileName)\n\t\tif fileName == \".\" || strings.HasPrefix(fileName, \"..\") || filepath.IsAbs(fileName) {\n\t\t\treturn nil, errors.New(\"invalid export path\")\n\t\t}\n\t\tif IsManagedEncryptedExportPath(fileName) {\n\t\t\tboxID, artifact, resolved := ResolveManagedEncryptedExport(fileName)\n\t\t\tif !resolved {\n\t\t\t\treturn nil, errors.New(\"managed export is unavailable\")\n\t\t\t}\n\t\t\tif err = AcquireEncryptedBoxOperation(boxID); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tHoldBoxReadLock(boxID)\n\t\t\trelease := true\n\t\t\tdefer func() {\n\t\t\t\tif release {\n\t\t\t\t\tReleaseBoxReadLock(boxID)\n\t\t\t\t\tReleaseEncryptedBoxOperation(boxID)\n\t\t\t\t}\n\t\t\t}()\n\t\t\t_, artifact, resolved = ResolveManagedEncryptedExport(fileName)","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/encrypted_export.go#L168-L204","documentation":"`AcquireExportArtifactLease` validates the requested export file name after URL-decoding and cleaning. If the cleaned name is `.`, starts with `..`, or is an absolute path, the request is a path-traversal attempt and is rejected with 'invalid export path'.","triggerScenarios":"Requesting a mobile/managed export artifact lease with a `name`/path parameter that escapes the export directory — e.g. `..%2F..%2Fsecret.txt`, an absolute path like `/etc/passwd`, or a bare `.`.","commonSituations":"Buggy client code passing a full path instead of a file name; stale UI links referencing moved files with relative segments; malicious probing of the export endpoint.","solutions":["Pass only a plain file name (no directories, no `..`, not absolute) to the export lease API.","URL-encode file names correctly on the client; do not pre-decode them before sending.","Use a managed export path (via IsManagedEncryptedExportPath flow) returned by a previous export call instead of constructing paths manually.","If a legitimate file is rejected, check whether the name contains traversal segments after filepath.Clean."],"exampleFix":"// before\nawait fetchPost(\"/api/export/acquireLease\", {name: \"../../tmp/export/report.html\"})\n// after\nawait fetchPost(\"/api/export/acquireLease\", {name: \"report.html\"})","handlingStrategy":"validation","validationCode":"// Go: client-side guard before requesting an export lease\nfunc safeExportName(name string) bool {\n    c := filepath.Clean(name)\n    return c != \".\" && !strings.HasPrefix(c, \"..\") && !filepath.IsAbs(c)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass bare file names, never full or relative paths, to export lease APIs.","Reuse managed export names returned by the export call instead of constructing your own.","URL-encode names once, on the client, and never pre-decode server-side parameters manually."],"tags":["go","security","path-traversal","export"],"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-14T05:17:10.506Z"}