microsoft/aspire · error · InvalidOperationException
Aspire skills bundle path
Error message
Aspire skills bundle path '{0}' is not safe. What it means
After checking that a bundle path is relative, NormalizeRelativePath splits it into segments and requires every segment to be portable (no '..', '.', invalid characters, reserved names). Paths that fail this check could traverse outside the bundle or break on other OSes, so Aspire throws this InvalidOperationException.
Solutions
- Remove '..' segments and restate the path from the bundle root, e.g. "skills/my-skill/SKILL.md"
- Use only alphanumerics, '-', '_', '.', and '/' separators in paths
- Repackage the archive so entries are flat relative paths under the bundle root
Example fix
// before (skill-manifest.json)
{ "path": "skills/../shared/SKILL.md" }
// after
{ "path": "shared/SKILL.md" } Defensive patterns
Strategy: validation
Validate before calling
var segments = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
bool safe = segments.Length > 0 && segments.All(s => s != ".." && s != "." && !s.Any(Path.GetInvalidFileNameChars().Contains));
if (!safe) throw new Exception($"Path '{entry.Path}' is not safe."); Try / catch
try { await provider.CreateAsync(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("is not safe")) { /* remove traversal segments */ } Prevention
- Never use '..' in bundle paths; restate paths from the bundle root
- Restrict path characters to letters, digits, '-', '_', '.'
- Scan archives for traversal entries before distribution
When it happens
Trigger: A bundle path contains '..' (or another non-portable segment such as an empty dot segment sequence that yields zero segments, or OS-illegal characters), e.g. "skills/../../etc/passwd", processed via AspireSkillsBundleProvider.
Common situations: Manually crafted or tampered manifests attempting path traversal; zip-slip-style archives; relative paths that assumed a different base directory; Windows reserved names like 'CON' or 'AUX'.
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
- Aspire skills bundle path
- Aspire skills archive entry
- Aspire skills archive entry
- Aspire skills bundle contains an empty relative path.
- Symlink ' ' targets ' ' which resolves outside the…
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/44accd0faf1e4bd0.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs:420
{
if (string.IsNullOrWhiteSpace(relativePath))
{
throw new InvalidOperationException("Aspire skills bundle contains an empty relative path.");
}
var normalizedPath = relativePath
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
if (Path.IsPathRooted(normalizedPath))
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Aspire skills bundle path '{0}' must be relative.", relativePath));
}
var segments = normalizedPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length == 0 || segments.Any(static segment => !IsPortablePathSegment(segment)))
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Aspire skills bundle path '{0}' is not safe.", relativePath));
}
return Path.Combine(segments);
}
private static bool IsPortablePathSegment(string segment)
{
// Bundle paths can be validated on one platform and installed on another. Reject the
// Windows-invalid character set everywhere so ':' cannot create an NTFS alternate data
// stream and other invalid filenames cannot enter a cached bundle.
// See https://learn.microsoft.com/windows/win32/fileio/naming-a-file.
return segment is not "." and not ".." &&
!segment.Any(static character => char.IsControl(character) || character is '<' or '>' or ':' or '"' or '|' or '?' or '*');
}
internal static string NormalizeSha512(string sha512)
{
return sha512.StartsWith("sha512-", StringComparison.OrdinalIgnoreCase) ||View on GitHub (pinned to 25830f84bd)