resolveComponentDirs enforces that all component paths (skills, agents, hooks, MCP servers) declared in a plugin manifest must resolve within the plugin's own directory. Any path that traverses outside using ../ is silently dropped by the isEqualOrParentresolved, pluginUri) check.
This is overly restrictive for marketplace repositories that contain multiple plugins. A common repository layout looks like:
my-marketplace/
├── skills/
│ └── shared-skill/
│ └── SKILL.md
├── plugins/
│ ├── plugin-a/
│ │ └── plugin.json ← wants to reference ../../skills/shared-skill
│ └── plugin-b/
│ └── plugin.json ← also wants to reference ../../skills/shared-skill
Today, both plugin-a and plugin-b cannot reference ../../skills/shared-skill because it resolves outside their plugin directories. The only workaround is to duplicate the skill into each plugin's own skills/ folder — which defeats the purpose of a shared repository layout.
This is especially problematic for marketplaces that keep skills in a root-level skills/ directory so they can be discovered by npx skills. Authors are forced to maintain duplicate copies of every skill: one at the repository root for npx skills discovery, and one inside each plugin for VS Code to find. This duplication is error-prone, increases maintenance burden, and diverges from the conventions of the broader skills ecosystem.
Proposed Solution
Completed PR
Relax the containment boundary for marketplace plugins from the plugin directory to the repository root. The repository root is already a known, trusted boundary. IAgentPluginRepositoryService.getRepositoryUri() computes it, and plugin installation already validates that plugin directories stay within it.
Concretely:
Add an optional boundaryUri parameter to resolveComponentDirs (defaulting to pluginUri for backward compatibility)
For marketplace-discovered plugins, pass the repository URI as the boundary
Non-marketplace plugins (configured, extension-contributed) continue using the plugin directory as the boundary
This allows marketplace plugins to declare paths like ../../skills/shared-skill that resolve outside the plugin but stay within the repository — while still preventing traversal to arbitrary filesystem locations.
Benefits
Eliminates forced duplication of shared components across plugins in the same repository
Aligns with the npx skills convention of keeping skills at the repository root
Backward compatible — only marketplace plugins get the relaxed boundary; all other plugin types retain the current behavior
Maintains security — paths that escape the repository root are still rejected
resolveComponentDirsenforces that all component paths (skills, agents, hooks, MCP servers) declared in a plugin manifest must resolve within the plugin's own directory. Any path that traverses outside using ../ is silently dropped by theisEqualOrParentresolved, pluginUri)check.This is overly restrictive for marketplace repositories that contain multiple plugins. A common repository layout looks like:
Today, both plugin-a and plugin-b cannot reference ../../skills/shared-skill because it resolves outside their plugin directories. The only workaround is to duplicate the skill into each plugin's own skills/ folder — which defeats the purpose of a shared repository layout.
This is especially problematic for marketplaces that keep skills in a root-level skills/ directory so they can be discovered by npx skills. Authors are forced to maintain duplicate copies of every skill: one at the repository root for npx skills discovery, and one inside each plugin for VS Code to find. This duplication is error-prone, increases maintenance burden, and diverges from the conventions of the broader skills ecosystem.
Proposed Solution
Completed PR
Relax the containment boundary for marketplace plugins from the plugin directory to the repository root. The repository root is already a known, trusted boundary.
IAgentPluginRepositoryService.getRepositoryUri()computes it, and plugin installation already validates that plugin directories stay within it.Concretely:
Add an optional boundaryUri parameter to resolveComponentDirs (defaulting to pluginUri for backward compatibility)
For marketplace-discovered plugins, pass the repository URI as the boundary
Non-marketplace plugins (configured, extension-contributed) continue using the plugin directory as the boundary
This allows marketplace plugins to declare paths like ../../skills/shared-skill that resolve outside the plugin but stay within the repository — while still preventing traversal to arbitrary filesystem locations.
Benefits
Eliminates forced duplication of shared components across plugins in the same repository
Aligns with the npx skills convention of keeping skills at the repository root
Backward compatible — only marketplace plugins get the relaxed boundary; all other plugin types retain the current behavior
Maintains security — paths that escape the repository root are still rejected