siyuan-note/siyuan · warning
invalid export path
Error message
invalid export path
What it means
`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'.
Source
Thrown at kernel/model/encrypted_export.go:186
// normalExportTempName 避免普通导出使用加密导出的第一层归属目录,保留归档内部的原始名称。
func normalExportTempName(name string) string {
parts := strings.SplitN(filepath.ToSlash(filepath.Clean(name)), "/", 2)
if ast.IsNodeIDPattern(parts[0]) {
parts[0] = "export-" + parts[0]
}
return filepath.FromSlash(strings.Join(parts, "/"))
}
// AcquireExportArtifactLease 为导出产物取得覆盖整个复制过程的生命周期租约。
func AcquireExportArtifactLease(exportPath string) (lease *ExportArtifactLease, err error) {
if after, ok := strings.CutPrefix(exportPath, "/export/"); ok {
fileName, decodeErr := url.PathUnescape(after)
if decodeErr != nil {
return nil, decodeErr
}
fileName = filepath.Clean(fileName)
if fileName == "." || strings.HasPrefix(fileName, "..") || filepath.IsAbs(fileName) {
return nil, errors.New("invalid export path")
}
if IsManagedEncryptedExportPath(fileName) {
boxID, artifact, resolved := ResolveManagedEncryptedExport(fileName)
if !resolved {
return nil, errors.New("managed export is unavailable")
}
if err = AcquireEncryptedBoxOperation(boxID); err != nil {
return nil, err
}
HoldBoxReadLock(boxID)
release := true
defer func() {
if release {
ReleaseBoxReadLock(boxID)
ReleaseEncryptedBoxOperation(boxID)
}
}()
_, artifact, resolved = ResolveManagedEncryptedExport(fileName)View on GitHub (pinned to 8641553a1f)
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.
Example fix
// before
await fetchPost("/api/export/acquireLease", {name: "../../tmp/export/report.html"})
// after
await fetchPost("/api/export/acquireLease", {name: "report.html"}) Defensive patterns
Strategy: validation
Validate before calling
// Go: client-side guard before requesting an export lease
func safeExportName(name string) bool {
c := filepath.Clean(name)
return c != "." && !strings.HasPrefix(c, "..") && !filepath.IsAbs(c)
} Prevention
- 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.
When it happens
Trigger: 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 `.`.
Common situations: 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.
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
- export path is outside export directory
- resource path [%s] is not in workspace
- export path is outside export directory
- Please unlock the encrypted notebook first
- import path is not sub path of import dir
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/6f2821c8e352456f.
Report an issue: GitHub.