microsoft/aspire · error · InvalidDataException

Aspire skills archive entry

Error message

Aspire skills archive entry '{0}' escapes the extraction directory.

What it means

Even after segment validation, GetSafeArchiveDestinationPath performs a final containment check: the fully-resolved destination must remain under the extraction root. If Path.GetFullPath resolves outside destinationRoot (e.g. via a link or unexpected normalization), this InvalidDataException is thrown as the last line of defense against archive escape.

Solutions

  1. Repackage or re-download the archive so entries resolve inside the extraction directory
  2. Ensure the extraction destination directory itself is a real, non-symlinked path
  3. Inspect entry names with 'tar -tf' and remove entries that resolve outside the root

Example fix

// before (tar entry via symlink)
link -> /etc, link/passwd
// after
skills/my-skill/SKILL.md
Defensive patterns

Strategy: try-catch

Validate before calling

var dest = Path.GetFullPath(Path.Combine(root, entry.Name.Replace('/', Path.DirectorySeparatorChar)));
if (!dest.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.Ordinal))
    throw new Exception($"Entry '{entry.Name}' escapes the extraction directory.");

Try / catch

try { await provider.CreateAsync(...); }
catch (InvalidDataException ex) when (ex.Message.Contains("escapes the extraction directory")) { /* treat archive as malicious; do not extract */ }

Prevention

When it happens

Trigger: An archive entry whose combined path escapes destinationRoot after full resolution, encountered during ExtractArchive of a skills package.

Common situations: Archives abusing symlinks or crafted names to escape the extraction dir; extracting into an unexpected working directory where the root check fails; corrupted archive metadata.

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 microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/043f62102975999d. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs:569

        }
    }

    private static string GetSafeArchiveDestinationPath(string destinationRoot, string entryName)
    {
        var normalizedEntryName = entryName.Replace('\\', '/');
        var segments = normalizedEntryName.Split('/', StringSplitOptions.RemoveEmptyEntries);
        if (Path.IsPathRooted(normalizedEntryName) ||
            segments.Length == 0 ||
            segments.Any(static segment => !IsPortablePathSegment(segment)))
        {
            throw new InvalidDataException(string.Format(CultureInfo.InvariantCulture, "Aspire skills archive entry '{0}' is not safe.", entryName));
        }

        var destinationPath = Path.GetFullPath(Path.Combine(destinationRoot, normalizedEntryName.Replace('/', Path.DirectorySeparatorChar)));
        if (!destinationPath.StartsWith(destinationRoot + Path.DirectorySeparatorChar, StringComparison.Ordinal) &&
            !string.Equals(destinationPath, destinationRoot, StringComparison.Ordinal))
        {
            throw new InvalidDataException(string.Format(CultureInfo.InvariantCulture, "Aspire skills archive entry '{0}' escapes the extraction directory.", entryName));
        }

        return destinationPath;
    }

    private static DirectoryInfo FindBundleRoot(string extractionDirectory)
    {
        var rootManifestPath = Path.Combine(extractionDirectory, ManifestFileName);
        if (File.Exists(rootManifestPath))
        {
            return new DirectoryInfo(extractionDirectory);
        }

        var packageDirectory = Path.Combine(extractionDirectory, "package");
        var packageManifestPath = Path.Combine(packageDirectory, ManifestFileName);
        if (File.Exists(packageManifestPath))
        {
            return new DirectoryInfo(packageDirectory);

View on GitHub (pinned to 25830f84bd)