fix: regex injection in message search + semaphore expect panic

- Escape regex special chars in highlightText to prevent ReDoS
- Replace semaphore.acquire().expect() with graceful skip
- Add toast error feedback for search failures
- Remove unsafe (resp.data as any) bypass
This commit is contained in:
ZhenYi 2026-04-27 11:12:26 +08:00
parent 2842a62d35
commit 1e975c0837
2 changed files with 11 additions and 5 deletions

View File

@ -207,7 +207,10 @@ where
let semaphore = worker_semaphore.clone();
tokio::spawn(async move {
let _permit = semaphore.acquire().await.expect("semaphore closed");
let Ok(_permit) = semaphore.acquire().await else {
tracing::warn!(task_id = %task_id, "semaphore closed, skipping task");
return;
};
let result = execute(task_id, task_service.clone()).await;

View File

@ -5,6 +5,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Search, X, Loader2, ArrowUp } from 'lucide-react';
import { client } from '@/client/client.gen';
import type { RoomMessageResponse } from '@/client';
import { toast } from 'sonner';
interface SearchResult {
messages: RoomMessageResponse[];
@ -40,13 +41,13 @@ export function RoomMessageSearch({ roomId, onSelectMessage, onClose }: RoomMess
url: `/api/rooms/${roomId}/messages/search`,
params: { q: searchQuery, limit: 20, offset: searchOffset },
});
const data = (resp.data as any)?.data as SearchResult | undefined;
const data = resp.data?.data as SearchResult | undefined;
if (data) {
setResults(data);
setOffset(searchOffset);
}
} catch (err) {
console.error('Search failed:', err);
toast.error('Search failed. Please try again.');
} finally {
setIsSearching(false);
}
@ -66,9 +67,11 @@ export function RoomMessageSearch({ roomId, onSelectMessage, onClose }: RoomMess
const highlightText = (text: string, searchQuery: string) => {
if (!searchQuery.trim()) return text;
const regex = new RegExp(`(${searchQuery})`, 'gi');
// Escape regex special characters to prevent regex injection / ReDoS.
const escaped = searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escaped})`, 'gi');
const parts = text.split(regex);
return parts.map((part, i) =>
return parts.map((part, i) =>
regex.test(part) ? (
<mark key={i} className="bg-yellow-200 text-inherit">{part}</mark>
) : (