AlistGo/alist · critical · ErrArchiveIllegalPath

archive entry has illegal path

Error message

archive entry has illegal path

What it means

ErrArchiveIllegalPath is returned by SecureJoin when an archive entry name is unsafe to extract: it contains NUL bytes, starts with '//' (UNC path), is absolute, escapes the base directory via '..' traversal, or uses Windows drive letters. The offending entry name is attached via fmt.Errorf('%w: %s'). This is the zip-slip / path-traversal defense for extraction.

Source

Thrown at internal/archive/tool/securepath.go:13

package tool

import (
	"errors"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
)

// ErrArchiveIllegalPath indicates an archive entry path is unsafe for extraction.
var ErrArchiveIllegalPath = errors.New("archive entry has illegal path")

// SecureJoin returns a safe extraction path for an archive entry.
// It rejects absolute paths, traversal, Windows drive/UNC paths, and NUL bytes.
func SecureJoin(baseDir, entryName string) (string, error) {
	if strings.Contains(entryName, "\x00") {
		return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
	}

	normalized := strings.ReplaceAll(entryName, "\\", "/")
	if strings.HasPrefix(normalized, "//") {
		return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
	}
	cleaned := path.Clean(normalized)

	if cleaned == "." || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
		return "", fmt.Errorf("%w: %s", ErrArchiveIllegalPath, entryName)
	}
	if strings.HasPrefix(cleaned, "/") {

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Reject the archive if it comes from an untrusted source — illegal paths are a strong maliciousness signal
  2. If the archive is trusted and paths are merely absolute, re-pack it with relative paths before extraction (e.g. on the machine that created it)
  3. Never bypass SecureJoin or strip the check to 'make it work'
  4. Scan archives (e.g. unzip -l, zipinfo) before extracting user-supplied content

Example fix

// before: unsafe join allows escape
full := filepath.Join(destDir, entry.Name)

// after: guarded join
full, err := tool.SecureJoin(destDir, entry.Name)
if err != nil { return err }
Defensive patterns

Strategy: type-guard

Validate before calling

for _, name := range entryNames {
    if strings.Contains(name, "\x00") || strings.HasPrefix(name, "/") || strings.HasPrefix(name, "\\\\") { return tool.ErrArchiveIllegalPath }
}

Type guard

func isIllegalPath(err error) bool { return errors.Is(err, tool.ErrArchiveIllegalPath) }

Try / catch

dest, err := tool.SecureJoin(baseDir, entry.Name)
if errors.Is(err, tool.ErrArchiveIllegalPath) {
    log.Warnf("skipping hostile entry %q", entry.Name)
    continue // skip entry; extraction proceeds
}

Prevention

When it happens

Trigger: Extracting an archive containing entries like '../../etc/passwd', 'C:\Windows\system32\x', '\\server\share\x', '/absolute/path', or names with embedded \x00. Any code path that calls SecureJoin per entry during extraction will refuse these immediately.

Common situations: Malicious uploads crafted for zip-slip; archives created by tools that store absolute paths (some Windows archivers); entries with backslash-separated names on Linux extraction; symlink-heavy archives from unknown sources.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/f12692c33bcdb297. Report an issue: GitHub.