Compare commits

..

4 Commits

Author SHA1 Message Date
Simos Mikelatos
945454444a Merge branch 'main' into fix/plugin-svg-icon-sanitization 2026-06-02 13:24:09 +02:00
Haileyesus
e14a512421 fix: sanitize plugin svg icons 2026-06-02 14:07:41 +03:00
Haile
704ff69b6e Merge branch 'main' into contribai/fix/security/unsanitized-svg-content-injected-via-dan 2026-06-02 13:22:37 +03:00
tuanaiseo
f705f2555e fix(security)(components): unsanitized svg content injected via `dangerouslys
The plugin icon renderer fetches SVG text from `/api/plugins/.../assets/...` and injects it directly into the DOM using `dangerouslySetInnerHTML` after only checking that the payload starts with `<svg`. This does not remove malicious attributes/elements (e.g., event handlers, scriptable SVG payloads), enabling DOM-based XSS if a plugin asset is malicious or compromised.

Affected files: PluginIcon.tsx

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
2026-04-12 06:17:10 +07:00

View File

@@ -143,21 +143,6 @@ const createFakeSubmitEvent = () => {
return { preventDefault: () => undefined } as unknown as FormEvent<HTMLFormElement>; return { preventDefault: () => undefined } as unknown as FormEvent<HTMLFormElement>;
}; };
const THINKING_MODE_STORAGE_KEY = 'chat-thinking-mode';
const getInitialThinkingMode = () => {
if (typeof window === 'undefined') {
return 'none';
}
const savedMode = safeLocalStorage.getItem(THINKING_MODE_STORAGE_KEY);
if (!savedMode) {
return 'none';
}
return thinkingModes.some((mode) => mode.id === savedMode) ? savedMode : 'none';
};
const getNotificationSessionSummary = ( const getNotificationSessionSummary = (
selectedSession: ProjectSession | null, selectedSession: ProjectSession | null,
fallbackInput: string, fallbackInput: string,
@@ -219,7 +204,7 @@ export function useChatComposerState({
const [uploadingImages, setUploadingImages] = useState<Map<string, number>>(new Map()); const [uploadingImages, setUploadingImages] = useState<Map<string, number>>(new Map());
const [imageErrors, setImageErrors] = useState<Map<string, string>>(new Map()); const [imageErrors, setImageErrors] = useState<Map<string, string>>(new Map());
const [isTextareaExpanded, setIsTextareaExpanded] = useState(false); const [isTextareaExpanded, setIsTextareaExpanded] = useState(false);
const [thinkingMode, setThinkingMode] = useState(getInitialThinkingMode); const [thinkingMode, setThinkingMode] = useState('none');
const [commandModalPayload, setCommandModalPayload] = useState<CommandModalPayload | null>(null); const [commandModalPayload, setCommandModalPayload] = useState<CommandModalPayload | null>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -579,7 +564,7 @@ export function useChatComposerState({
let messageContent = currentInput; let messageContent = currentInput;
const selectedThinkingMode = thinkingModes.find((mode: { id: string; prefix?: string }) => mode.id === thinkingMode); const selectedThinkingMode = thinkingModes.find((mode: { id: string; prefix?: string }) => mode.id === thinkingMode);
if (provider === 'claude' && selectedThinkingMode && selectedThinkingMode.prefix) { if (selectedThinkingMode && selectedThinkingMode.prefix) {
messageContent = `${selectedThinkingMode.prefix}: ${currentInput}`; messageContent = `${selectedThinkingMode.prefix}: ${currentInput}`;
} }
@@ -764,6 +749,7 @@ export function useChatComposerState({
setUploadingImages(new Map()); setUploadingImages(new Map());
setImageErrors(new Map()); setImageErrors(new Map());
setIsTextareaExpanded(false); setIsTextareaExpanded(false);
setThinkingMode('none');
if (textareaRef.current) { if (textareaRef.current) {
textareaRef.current.style.height = 'auto'; textareaRef.current.style.height = 'auto';
@@ -809,10 +795,6 @@ export function useChatComposerState({
inputValueRef.current = input; inputValueRef.current = input;
}, [input]); }, [input]);
useEffect(() => {
safeLocalStorage.setItem(THINKING_MODE_STORAGE_KEY, thinkingMode);
}, [thinkingMode]);
useEffect(() => { useEffect(() => {
if (!selectedProjectId) { if (!selectedProjectId) {
return; return;