79
80
/** Splits an `enabledPlugins` id into its `plugin` and `marketplace` parts. */
81
>
function splitPluginId(id: string): { readonly plugin: string; readonly marketplace: string } | undefined {
claudeNativePluginScan.ts
82
>
const at = id.lastIndexOf('@');
83
>
if (at <= 0 || at === id.length - 1) {
84
return undefined;
85
}
87
>
const marketplace = id.slice(at + 1);
88
>
// The segments are joined into filesystem paths below, so reject anything
89
>
// that could escape the plugin roots (path separators or `..`). Plugin and
90
>
// marketplace ids are plain identifiers; a settings file (incl. an
91
>
// untrusted workspace `.claude/settings.local.json`) must not redirect the
92
>
// scan outside `~/.claude/plugins/cache` / `.claude/skills`.
93
>
const isUnsafeSegment = (s: string) => s.includes('/') || s.includes('\\') || s.includes('..');
94
>
if (isUnsafeSegment(plugin) || isUnsafeSegment(marketplace)) {
95
return undefined;
96
}
98
>
}
99
100
/**