siyuan-note/siyuan · error
export path is outside export directory
Error message
export path is outside export directory
What it means
After decoding, exportedFilePath verifies the result stays inside the temp export directory using gulu.File.IsSubPath. If a crafted or malformed path (e.g. '../' segments) escapes that root, it returns 'export path is outside export directory'. This is a path-traversal guard protecting the export bundle reader.
Source
Thrown at kernel/model/notebook_bundle.go:196
if err = os.Rename(partialPath, finalPath); nil != err {
logging.LogErrorf("publish notebook bundle failed: %s", err)
return ""
}
return "/export/" + url.PathEscape(filepath.Base(finalPath))
}
func exportedFilePath(exportPath string) (ret string, err error) {
encoded, ok := strings.CutPrefix(exportPath, "/export/")
if !ok || encoded == "" {
return "", errors.New("invalid export path")
}
decoded, err := url.PathUnescape(encoded)
if nil != err {
return "", err
}
ret = filepath.Join(util.TempDir, "export", filepath.FromSlash(decoded))
if !gulu.File.IsSubPath(filepath.Join(util.TempDir, "export"), ret) {
return "", errors.New("export path is outside export directory")
}
return
}
// ImportSYNotebookBundle 导入批量笔记本包。普通 .sy.zip 返回 bundle=false,由原有导入流程继续处理。
func ImportSYNotebookBundle(zipPath string) (boxIDs []string, bundle bool, err error) {
archive, openErr := zip.OpenReader(zipPath)
if nil != openErr {
err = openErr
return
}
var manifestData []byte
rootName := ""
manifestSuffix := "/" + syNotebookBundleManifestPath
for _, file := range archive.File {
if !strings.HasSuffix(file.Name, manifestSuffix) {
continue
}View on GitHub (pinned to 8641553a1f)
Solutions
- Only process bundle files whose export entries are relative names without '..'
- Sanitize/reject suspicious encoded segments before calling
- Keep the guard intact — treat this error as a sign the bundle is untrusted and skip it
Example fix
// before
p := "/export/" + url.PathEscape("../../../etc/passwd")
// after
if strings.Contains(name, "..") { return errors.New("unsafe entry") }
p := "/export/" + url.PathEscape(name) Defensive patterns
Strategy: validation
Validate before calling
func safeEntry(name string) bool { return name != "" && !strings.Contains(name, "..") && !strings.ContainsAny(name, "\\\"") } Try / catch
if _, err := exportedFilePath(p); err != nil { log.Warnf("unsafe export path %q: %v", p, err); return errBundleUntrusted; } Prevention
- Reject bundle entries containing '..' before import
- Treat traversal errors as untrusted-bundle signals, not retryable failures
- Validate third-party .sy.zip contents before processing
When it happens
Trigger: exportPath contains encoded '../' segments (e.g. '/export/..%2F..%2Fsiyuan.db') or otherwise resolves outside util.TempDir/export when joined.
Common situations: Importing a third-party .sy.zip bundle whose internal export references contain traversal segments; tampered export manifests; passing URLs built by concatenating untrusted names.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- invalid export path
- export path is outside export directory
- resource path [%s] is not in workspace
- import path is not sub path of import dir
- invalid custom emoji name
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/cac2074119304d3f.
Report an issue: GitHub.