argoproj/argo-workflows · error

illegal file path after symlink resolution: %s resolves outs

Error message

illegal file path after symlink resolution: %s resolves outside destination

What it means

For regular files/dirs in the tar, the executor evaluates any existing symlinked parent directories (filepath.EvalSymlinks on the parent path) and confirms the resolved location is still inside the destination. If the physical parent resolves outside dest — meaning a previously extracted symlink redirects the write — extraction aborts with this error.

Source

Thrown at workflow/executor/executor.go:1186

				// Before writing the file, check if the parent directory resolves outside dest
				parentDir := filepath.Dir(target)

				// Resolve the destination directory
				resolvedDest, err := filepath.EvalSymlinks(dest)
				if err != nil {
					return err
				}

				// Check if parent exists and if so, verify it doesn't resolve outside dest
				if _, lstatErr := os.Lstat(parentDir); lstatErr == nil {
					// Parent exists, resolve it to check for symlink traversal
					resolvedParent, evalErr := filepath.EvalSymlinks(parentDir)
					if evalErr != nil {
						return evalErr
					}
					// Check if resolved parent is outside dest
					if !strings.HasPrefix(resolvedParent+string(os.PathSeparator), resolvedDest+string(os.PathSeparator)) && resolvedParent != resolvedDest {
						return fmt.Errorf("illegal file path after symlink resolution: %s resolves outside destination", header.Name)
					}
				} else if !os.IsNotExist(lstatErr) {
					return lstatErr
				} else {
					// Parent doesn't exist, create it
					if mkdirErr := os.MkdirAll(parentDir, 0o755); mkdirErr != nil {
						return mkdirErr
					}
				}

				f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
				if err != nil {
					return err
				}
				if _, err := io.Copy(f, tr); err != nil {
					return err
				}
				if err := f.Close(); err != nil {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Repackage the artifact without symlinks in directory positions (use real directories)
  2. Inspect the archive (tar -tvf) for symlink members before loading it
  3. Extract to a fresh, empty destination so no pre-existing symlinks can redirect paths
  4. Treat artifacts from untrusted producers as hostile and re-verify contents

Example fix

# before (archive: 'sub' -> /tmp symlink, then 'sub/file')
# after (archive uses real directory)
mkdir sub && cp file sub/ && tar -C root -cf out.tar .
Defensive patterns

Strategy: validation

Validate before calling

// Detect archives where directory-position symlinks could redirect writes:
// tar -tvf artifact.tar | awk '$1 ~ /^l/ {print}'   # review all symlinks
// Prefer archives with no symlinks in directory positions

Try / catch

if err := loadArtifacts(ctx); err != nil {
	if strings.Contains(err.Error(), "resolves outside destination") {
		// symlinked parent redirects writes: reject/repackage the archive
	}
}

Prevention

When it happens

Trigger: Writing a tar member under a path whose parent directory chain traverses a symlink created earlier in the same archive (or pre-existing in dest) that points outside the extraction root, so the final write would land outside dest even though the joined name looked safe.

Common situations: Multi-stage attacks where an archive first extracts a symlink like 'sub -> /tmp' and then writes 'sub/file' (path 'dest/sub/file' passes prefix check but resolves to /tmp/file); archives recombining paths from multiple sources.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/a79efd995acec9f9. Report an issue: GitHub.