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

  1. Only process bundle files whose export entries are relative names without '..'
  2. Sanitize/reject suspicious encoded segments before calling
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/cac2074119304d3f. Report an issue: GitHub.