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:
parent
2842a62d35
commit
1e975c0837
@ -207,7 +207,10 @@ where
|
|||||||
let semaphore = worker_semaphore.clone();
|
let semaphore = worker_semaphore.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
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;
|
let result = execute(task_id, task_service.clone()).await;
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
|||||||
import { Search, X, Loader2, ArrowUp } from 'lucide-react';
|
import { Search, X, Loader2, ArrowUp } from 'lucide-react';
|
||||||
import { client } from '@/client/client.gen';
|
import { client } from '@/client/client.gen';
|
||||||
import type { RoomMessageResponse } from '@/client';
|
import type { RoomMessageResponse } from '@/client';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
messages: RoomMessageResponse[];
|
messages: RoomMessageResponse[];
|
||||||
@ -40,13 +41,13 @@ export function RoomMessageSearch({ roomId, onSelectMessage, onClose }: RoomMess
|
|||||||
url: `/api/rooms/${roomId}/messages/search`,
|
url: `/api/rooms/${roomId}/messages/search`,
|
||||||
params: { q: searchQuery, limit: 20, offset: searchOffset },
|
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) {
|
if (data) {
|
||||||
setResults(data);
|
setResults(data);
|
||||||
setOffset(searchOffset);
|
setOffset(searchOffset);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Search failed:', err);
|
toast.error('Search failed. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSearching(false);
|
setIsSearching(false);
|
||||||
}
|
}
|
||||||
@ -66,7 +67,9 @@ export function RoomMessageSearch({ roomId, onSelectMessage, onClose }: RoomMess
|
|||||||
|
|
||||||
const highlightText = (text: string, searchQuery: string) => {
|
const highlightText = (text: string, searchQuery: string) => {
|
||||||
if (!searchQuery.trim()) return text;
|
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);
|
const parts = text.split(regex);
|
||||||
return parts.map((part, i) =>
|
return parts.map((part, i) =>
|
||||||
regex.test(part) ? (
|
regex.test(part) ? (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user