{"record":{"id":"495255ce509931de","repo":"microsoft/aspire","slug":"aspire-skills-bundle-path-0-must-be-relative","errorCode":null,"errorMessage":"Aspire skills bundle path '{0}' must be relative.","messagePattern":"Aspire skills bundle path '(.+?)' must be relative\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs","lineNumber":414,"sourceCode":"        }\n\n        return null;\n    }\n\n    internal static string NormalizeRelativePath(string? relativePath)\n    {\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 \"..\" &&","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs#L396-L432","documentation":"NormalizeRelativePath rejects bundle paths that are rooted (absolute), such as '/etc/passwd' or 'C:\\Windows\\...'. Bundle contents must be addressed relative to the bundle root so extraction/copying can never target arbitrary filesystem locations. Aspire throws this InvalidOperationException when a rooted path is encountered.","triggerScenarios":"A manifest entry or bundle path in skill-manifest.json is an absolute path (leading '/', drive letter, or UNC path) and is passed through AspireSkillsBundleProvider.CreateAsync.","commonSituations":"Authoring a manifest on Windows with 'C:\\...' paths; copy-pasting absolute file paths from an editor; a packaging tool emitting absolute paths.","solutions":["Change the path to be relative to the bundle root, e.g. \"skills/my-skill/SKILL.md\"","Strip any leading slash, drive letter, or UNC prefix from manifest entries","Repackage the bundle with a tool that emits relative paths"],"exampleFix":"// before (skill-manifest.json)\n{ \"path\": \"/home/user/app/skills/my-skill/SKILL.md\" }\n// after\n{ \"path\": \"skills/my-skill/SKILL.md\" }","handlingStrategy":"validation","validationCode":"if (Path.IsPathRooted(entry.Path) || Path.IsPathFullyQualified(entry.Path))\n    throw new Exception($\"Path '{entry.Path}' must be relative to the bundle root.\");","typeGuard":null,"tryCatchPattern":"try { await provider.CreateAsync(...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"must be relative\")) { /* rewrite manifest paths as relative */ }","preventionTips":["Always author manifest paths relative to the bundle root","Never paste absolute filesystem paths into skill manifests","Test bundles on both Windows and Linux to catch rooted-path mistakes"],"tags":["path","security","validation","skills"],"backgroundTag":"invalid-argument-format","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"}