{"record":{"id":"44accd0faf1e4bd0","repo":"microsoft/aspire","slug":"aspire-skills-bundle-path-0-is-not-safe","errorCode":null,"errorMessage":"Aspire skills bundle path '{0}' is not safe.","messagePattern":"Aspire skills bundle path '(.+?)' is not safe\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs","lineNumber":420,"sourceCode":"    {\n        if (string.IsNullOrWhiteSpace(relativePath))\n        {\n            throw new InvalidOperationException(\"Aspire skills bundle contains an empty relative path.\");\n        }\n\n        var normalizedPath = relativePath\n            .Replace('/', Path.DirectorySeparatorChar)\n            .Replace('\\\\', Path.DirectorySeparatorChar);\n\n        if (Path.IsPathRooted(normalizedPath))\n        {\n            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, \"Aspire skills bundle path '{0}' must be relative.\", relativePath));\n        }\n\n        var segments = normalizedPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);\n        if (segments.Length == 0 || segments.Any(static segment => !IsPortablePathSegment(segment)))\n        {\n            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, \"Aspire skills bundle path '{0}' is not safe.\", relativePath));\n        }\n\n        return Path.Combine(segments);\n    }\n\n    private static bool IsPortablePathSegment(string segment)\n    {\n        // Bundle paths can be validated on one platform and installed on another. Reject the\n        // Windows-invalid character set everywhere so ':' cannot create an NTFS alternate data\n        // stream and other invalid filenames cannot enter a cached bundle.\n        // See https://learn.microsoft.com/windows/win32/fileio/naming-a-file.\n        return segment is not \".\" and not \"..\" &&\n            !segment.Any(static character => char.IsControl(character) || character is '<' or '>' or ':' or '\"' or '|' or '?' or '*');\n    }\n\n    internal static string NormalizeSha512(string sha512)\n    {\n        return sha512.StartsWith(\"sha512-\", StringComparison.OrdinalIgnoreCase) ||","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs#L402-L438","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":"// before (skill-manifest.json)\n{ \"path\": \"skills/../shared/SKILL.md\" }\n// after\n{ \"path\": \"shared/SKILL.md\" }","handlingStrategy":"validation","validationCode":"var segments = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);\nbool safe = segments.Length > 0 && segments.All(s => s != \"..\" && s != \".\" && !s.Any(Path.GetInvalidFileNameChars().Contains));\nif (!safe) throw new Exception($\"Path '{entry.Path}' is not safe.\");","typeGuard":null,"tryCatchPattern":"try { await provider.CreateAsync(...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"is not safe\")) { /* remove traversal segments */ }","preventionTips":["Never use '..' in bundle paths; restate paths from the bundle root","Restrict path characters to letters, digits, '-', '_', '.'","Scan archives for traversal entries before distribution"],"tags":["path","security","path-traversal","skills"],"backgroundTag":"path-traversal-blocked","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}