mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-13 09:57:24 +00:00
Use realpathSync to canonicalize paths before the plugin asset boundary check, preventing symlink-based traversal bypasses that could escape the plugin directory. PluginTabContent now guards on plugin.enabled before mounting the plugin module, and re-mounts when the enabled state changes so toggling a plugin takes effect without a page reload. PluginIcon safely handles a missing iconFile prop and skips processing non-OK fetch responses instead of attempting to parse error bodies as SVG. Register 'plugins' as a known main tab so the settings router preserves the tab on navigation.
45 lines
1.2 KiB
TypeScript
45 lines
1.2 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { authenticatedFetch } from '../../utils/api';
|
|
|
|
type Props = {
|
|
pluginName: string;
|
|
iconFile: string;
|
|
className?: string;
|
|
};
|
|
|
|
// Module-level cache so repeated renders don't re-fetch
|
|
const svgCache = new Map<string, string>();
|
|
|
|
export default function PluginIcon({ pluginName, iconFile, className }: Props) {
|
|
const url = iconFile
|
|
? `/api/plugins/${encodeURIComponent(pluginName)}/assets/${encodeURIComponent(iconFile)}`
|
|
: '';
|
|
const [svg, setSvg] = useState<string | null>(url ? (svgCache.get(url) ?? null) : null);
|
|
|
|
useEffect(() => {
|
|
if (!url || svgCache.has(url)) return;
|
|
authenticatedFetch(url)
|
|
.then((r) => {
|
|
if (!r.ok) return;
|
|
return r.text();
|
|
})
|
|
.then((text) => {
|
|
if (text && text.trimStart().startsWith('<svg')) {
|
|
svgCache.set(url, text);
|
|
setSvg(text);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}, [url]);
|
|
|
|
if (!svg) return <span className={className} />;
|
|
|
|
return (
|
|
<span
|
|
className={className}
|
|
// SVG is fetched from the user's own installed plugin — same trust level as the plugin code itself
|
|
dangerouslySetInnerHTML={{ __html: svg }}
|
|
/>
|
|
);
|
|
}
|