siyuan-note/siyuan · error

exporting symbolic links is not supported

Error message

exporting symbolic links is not supported

What it means

Returned by copyExportResource when os.Lstat on the top-level resource path shows the ModeSymlink bit set. SiYuan refuses to export symlinks because a symlink can point outside the workspace (and outside an encrypted notebook's assets dir), which would leak plaintext paths or escape the workspace sandbox. The check uses Lstat, so the link itself (not its target) is inspected.

Source

Thrown at kernel/model/export.go:863

	}

	exportFilePath = path.Join("temp", "export")
	if encryptedBoxID != "" {
		exportFilePath = path.Join("temp", "export", registerManagedEncryptedExport(encryptedBoxID, "resources", zipFilePath))
	} else {
		exportFilePath = path.Join(exportFilePath, filepath.Base(zipFilePath))
	}
	return
}

// copyExportResource 复制导出资源,目录逐文件处理以避免将加密资源作为普通文件读取。
func copyExportResource(source, destination string) error {
	info, err := os.Lstat(source)
	if err != nil {
		return err
	}
	if info.Mode()&os.ModeSymlink != 0 {
		return errors.New("exporting symbolic links is not supported")
	}
	if !info.IsDir() {
		return copyExportFile(source, destination)
	}

	return filepath.WalkDir(source, func(current string, entry fs.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if entry.Type()&os.ModeSymlink != 0 {
			return errors.New("exporting symbolic links is not supported")
		}
		relativePath, relErr := filepath.Rel(source, current)
		if relErr != nil {
			return relErr
		}
		target := filepath.Join(destination, relativePath)
		if entry.IsDir() {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Replace the symlink with the actual file or a copy of its target inside the workspace, then export.
  2. Resolve the symlink to its real target and ensure that target is a regular file or directory inside the workspace before exporting.
  3. Avoid storing symlinks inside the SiYuan assets folder — copy real files instead.
  4. If using a sync client, configure it to materialize files rather than leave symlink placeholders.

Example fix

# before — assets/myvideo is a symlink to /mnt/external/clip.mp4
ls -la workspace/data/notebook/assets/myvideo
# after — replace symlink with the real file (or a copy)
rm workspace/data/notebook/assets/myvideo
cp /mnt/external/clip.mp4 workspace/data/notebook/assets/myvideo
Defensive patterns

Strategy: validation

Validate before calling

// Reject symlinks before calling ExportResources
for _, p := range resourcePaths {
    full := filepath.Join(util.WorkspaceDir, p)
    if info, err := os.Lstat(full); err == nil && info.Mode()&os.ModeSymlink != 0 {
        return fmt.Errorf("refusing to export symlink: %s", p)
    }
}

Type guard

// isRegularOrRealDir reports whether the path is a real file/dir (not a symlink).
func isRegularOrRealDir(p string) bool {
    info, err := os.Lstat(p)
    if err != nil {
        return false
    }
    return info.Mode()&os.ModeSymlink == 0
}

Prevention

When it happens

Trigger: ExportResources called with a resource path that is itself a symbolic link. Common on Linux/macOS where users symlink asset folders into the workspace, or where a sync tool replaced a file with a symlink.

Common situations: User created a symlink inside the workspace assets folder pointing to an external directory. A cloud-sync client (Dropbox/OneDrive) represented a placeholder file as a symlink. Migrating data via symlinks.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/97253987903676ba. Report an issue: GitHub.