Critical Securities (5) fixes

This commit is contained in:
2026-02-25 19:44:38 +00:00
parent 10acd14755
commit 5d4f0114cd
14 changed files with 302 additions and 103 deletions
@@ -2,6 +2,7 @@ import { useState, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import { Volume2, Loader2, Square } from 'lucide-react';
import type { ChatMessage } from '../types';
import { ToolActivity } from './ToolActivity';
@@ -9,6 +10,15 @@ import { QuestionActivity } from './QuestionActivity';
import { getRawUrl } from '../../FileViewer/file-types';
import { useFilesAPI } from '../../../hooks/useFilesAPI';
const sanitizeSchema = {
...defaultSchema,
tagNames: (defaultSchema.tagNames ?? []).filter((tag) => tag !== 'script' && tag !== 'iframe' && tag !== 'object' && tag !== 'embed' && tag !== 'form'),
attributes: {
...defaultSchema.attributes,
'*': (defaultSchema.attributes?.['*'] ?? []).filter((attr) => typeof attr === 'string' && !attr.startsWith('on')),
},
};
// Matches absolute image file paths, e.g. /home/user/pic.png or /tmp/photo.jpg
const IMAGE_PATH_RE = /(\/(?:home\/[^/\s]+\/)?[^\s`"'<>\n\r[\]()]+\.(?:png|jpg|jpeg|gif|webp|svg|bmp|ico))/gi;
@@ -129,7 +139,7 @@ export const MessageBubble = ({ message, onAnswer }: MessageBubbleProps) => {
<div className="max-w-[85%]">
<div className="rounded-2xl rounded-tl-sm bg-muted/50 border border-border/50 px-4 py-2.5">
<div className="chat-md">
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}>
{injectImages(assistantText)}
</ReactMarkdown>
</div>
@@ -178,7 +188,7 @@ export const StreamingBubble = ({ text }: StreamingBubbleProps) => {
<div className="flex justify-start">
<div className="max-w-[85%] rounded-2xl rounded-tl-sm bg-muted/50 border border-border/50 px-4 py-2.5">
<div className="chat-md">
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}>
{injectImages(text)}
</ReactMarkdown>
<span className="inline-block w-2 h-4 bg-duck-teal/60 animate-pulse ml-0.5 align-middle" />
@@ -35,7 +35,8 @@ export const PreviewProvider = ({ children }: PreviewProviderProps) => {
setStopped(false);
try {
const res = await client.post<DevServerResponse>('/dev-server/start', { slug: targetSlug });
setUrl(res.url);
const token = client.token;
setUrl(token ? `${res.url}?token=${encodeURIComponent(token)}` : res.url);
setPort(res.port);
} catch (err: unknown) {
const msg = err && typeof err === 'object' && 'message' in err ? String(err.message) : 'Failed to start dev server';
@@ -81,7 +82,8 @@ export const PreviewProvider = ({ children }: PreviewProviderProps) => {
const status = await client.get<DevServerStatus>(`/dev-server/status?slug=${encodeURIComponent(slug)}`);
if (cancelled) return;
if (status.running && status.url) {
setUrl(status.url);
const token = client.token;
setUrl(token ? `${status.url}?token=${encodeURIComponent(token)}` : status.url);
setPort(status.port ?? null);
} else {
startServer(slug);