siyuan-note/siyuan · error

archive entry resolves outside destination [%s]

Error message

archive entry resolves outside destination [%s]

What it means

The final stage of validateArchiveEntryPath resolves both destination and entry path to their real filesystem locations (following symlinks and Windows junctions on the longest existing parent) and requires the resolved entry to stay local to the resolved destination. If a symlink component redirects the entry outside the destination, extraction is blocked. Afterwards the encrypted-notebook check still runs on the entry path.

Source

Thrown at kernel/api/archive.go:287

	return nil
}

func validateArchiveEntryPath(destination, entryPath string) error {
	rel, err := filepath.Rel(destination, entryPath)
	if err != nil || !filepath.IsLocal(rel) {
		return fmt.Errorf("invalid archive entry path [%s]", entryPath)
	}
	resolved, err := resolveArchivePath(entryPath)
	if err != nil {
		return err
	}
	resolvedDestination, err := resolveArchivePath(destination)
	if err != nil {
		return err
	}
	rel, err = filepath.Rel(resolvedDestination, resolved)
	if err != nil || !filepath.IsLocal(rel) {
		return fmt.Errorf("archive entry resolves outside destination [%s]", entryPath)
	}
	return rejectEncryptedArchivePath(entryPath)
}

func extractWorkspaceArchiveEntry(entry *archivezip.File, destination string) error {
	if entry.FileInfo().IsDir() {
		return os.MkdirAll(destination, 0755)
	}
	if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
		return err
	}
	source, err := entry.Open()
	if err != nil {
		return err
	}
	defer source.Close()
	target, err := os.Create(destination)
	if err != nil {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Remove symlinks inside the destination directory before extraction.
  2. Extract into a fresh, link-free directory.
  3. Recreate the archive with only regular files and directories.

Example fix

// before
mkdir dest && ln -s /tmp/leak dest/out  // archive entry "out/x.txt" resolves outside dest

// after
mkdir dest && mkdir dest/out  # real directory, no symlink
Defensive patterns

Strategy: validation

Validate before calling

resolvedEntry, _ := filepath.EvalSymlinks(entryPath)
resolvedDest, _ := filepath.EvalSymlinks(destination)
rel, err := filepath.Rel(resolvedDest, resolvedEntry)
if err != nil || !filepath.IsLocal(rel) {
    return fmt.Errorf("entry would resolve outside destination")
}

Try / catch

if err := unzipArchive(f, dest); err != nil && strings.Contains(err.Error(), "resolves outside destination") {
    log.Warn("symlink component redirected an entry outside the destination; strip symlinks and retry")
}

Prevention

When it happens

Trigger: unzip API on an archive where an entry path contains a symlink directory pointing outside the destination, so resolveArchivePath(entryPath) resolves outside resolveArchivePath(destination). Called for every entry during unzipWorkspaceArchive.

Common situations: Archives paired with pre-planted symlinks in the destination; destination directory itself reached via a link while entries reference sibling links escaping it; extraction tools that resolve parent dirs differently than the kernel.

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/8fe56e15bbde11d8. Report an issue: GitHub.