siyuan-note/siyuan · warning

export path is outside export directory

Error message

export path is outside export directory

What it means

For non-managed export names, the artifact path is built as `<TempDir>/export/<fileName>` and then verified with gulu.File.IsSubPath. This error means the resulting absolute path is not inside the export directory — a defensive containment check that also catches symlink/normalization surprises.

Source

Thrown at kernel/model/encrypted_export.go:219

					ReleaseEncryptedBoxOperation(boxID)
				}
			}()
			_, artifact, resolved = ResolveManagedEncryptedExport(fileName)
			if !resolved {
				return nil, errors.New("managed export is unavailable")
			}
			if _, dekErr := GetDEKIfUnlocked(boxID); dekErr != nil {
				return nil, dekErr
			}
			lease, err = registerMobileExportLease(boxID, artifact, filepath.Base(fileName), "")
			if err == nil {
				release = false
			}
			return
		}
		artifact := filepath.Join(util.TempDir, "export", fileName)
		if !gulu.File.IsSubPath(filepath.Join(util.TempDir, "export"), artifact) {
			return nil, errors.New("export path is outside export directory")
		}
		return registerMobileExportLease("", artifact, filepath.Base(fileName), "")
	}

	if !strings.HasPrefix(exportPath, "assets/") {
		return nil, errors.New("unsupported export path")
	}
	relativePath, boxID, parseErr := assetPathAndBox(exportPath, "")
	if parseErr != nil {
		return nil, parseErr
	}
	if boxID == "" || !IsEncryptedBox(boxID) {
		artifact, resolveErr := GetAssetAbsPath(relativePath)
		if resolveErr != nil {
			return nil, resolveErr
		}
		if ensureErr := EnsureAssetLocal(artifact); ensureErr != nil {
			return nil, ensureErr

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Send only simple file names without directory components to the export lease API.
  2. Prefer managed export paths produced by the export flow over hand-built relative paths.
  3. Verify the client is not double-joining paths (e.g. already prefixed with 'export/').
  4. If TempDir is customized/symlinked, confirm the export directory resolves inside the workspace temp tree.

Example fix

// before
{name: "sub\..\..\report.html"} // escapes TempDir/export after join
// after
{name: "report.html"}
Defensive patterns

Strategy: validation

Validate before calling

// Go: verify the joined artifact stays inside the export dir before calling the API
artifact := filepath.Join(util.TempDir, "export", name)
if !gulu.File.IsSubPath(filepath.Join(util.TempDir, "export"), artifact) {
    return errors.New("name escapes export directory")
}

Prevention

When it happens

Trigger: AcquireExportArtifactLease called with a name that, after joining with the temp export dir and cleaning, escapes `util.TempDir/export` (e.g. crafted names with separators, or asset paths outside `assets/` handled by adjacent checks).

Common situations: Client-supplied names containing path segments that survive filepath.Clean; platform-specific separator tricks; exporting on a system where TempDir is symlinked elsewhere.

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/4c91a9733060ae47. Report an issue: GitHub.