🔒️(frontend) implement strict validation for user-provided metadata
Add comprehensive validation for metadata that can be input by users with LiveKit access tokens. Handle all user-controlled metadata with extra care, implementing strict checks to prevent injection attacks or other security issues from malicious input.
This commit is contained in:
committed by
aleb_the_flash
parent
b73f18419b
commit
6545ecf11a
@@ -18,6 +18,7 @@ import {
|
|||||||
ANIMATION_DURATION,
|
ANIMATION_DURATION,
|
||||||
ReactionPortals,
|
ReactionPortals,
|
||||||
} from '@/features/rooms/livekit/components/ReactionPortal'
|
} from '@/features/rooms/livekit/components/ReactionPortal'
|
||||||
|
import { safeParseMetadata } from '@/features/rooms/utils/safeParseMetadata'
|
||||||
|
|
||||||
export const MainNotificationToast = () => {
|
export const MainNotificationToast = () => {
|
||||||
const room = useRoomContext()
|
const room = useRoomContext()
|
||||||
@@ -145,10 +146,10 @@ export const MainNotificationToast = () => {
|
|||||||
if (isMobileBrowser()) return
|
if (isMobileBrowser()) return
|
||||||
if (participant.isLocal) return
|
if (participant.isLocal) return
|
||||||
|
|
||||||
const prevMetadata = JSON.parse(prevMetadataStr || '{}')
|
const prevMetadata = safeParseMetadata(prevMetadataStr)
|
||||||
const metadata = JSON.parse(participant.metadata || '{}')
|
const metadata = safeParseMetadata(participant.metadata)
|
||||||
|
|
||||||
if (prevMetadata.raised == metadata.raised) return
|
if (prevMetadata?.raised == metadata?.raised) return
|
||||||
|
|
||||||
const existingToast = toastQueue.visibleToasts.find(
|
const existingToast = toastQueue.visibleToasts.find(
|
||||||
(toast) =>
|
(toast) =>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Participant } from 'livekit-client'
|
|||||||
import { fetchServerApi } from './fetchServerApi'
|
import { fetchServerApi } from './fetchServerApi'
|
||||||
import { buildServerApiUrl } from './buildServerApiUrl'
|
import { buildServerApiUrl } from './buildServerApiUrl'
|
||||||
import { useRoomData } from '../hooks/useRoomData'
|
import { useRoomData } from '../hooks/useRoomData'
|
||||||
|
import { safeParseMetadata } from '@/features/rooms/utils/safeParseMetadata'
|
||||||
|
|
||||||
export const useLowerHandParticipant = () => {
|
export const useLowerHandParticipant = () => {
|
||||||
const data = useRoomData()
|
const data = useRoomData()
|
||||||
@@ -10,7 +11,7 @@ export const useLowerHandParticipant = () => {
|
|||||||
if (!data || !data?.livekit) {
|
if (!data || !data?.livekit) {
|
||||||
throw new Error('Room data is not available')
|
throw new Error('Room data is not available')
|
||||||
}
|
}
|
||||||
const newMetadata = JSON.parse(participant.metadata || '{}')
|
const newMetadata = safeParseMetadata(participant.metadata) || {}
|
||||||
newMetadata.raised = !newMetadata.raised
|
newMetadata.raised = !newMetadata.raised
|
||||||
return fetchServerApi(
|
return fetchServerApi(
|
||||||
buildServerApiUrl(
|
buildServerApiUrl(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { WaitingParticipantListItem } from './WaitingParticipantListItem'
|
|||||||
import { useWaitingParticipants } from '@/features/rooms/hooks/useWaitingParticipants'
|
import { useWaitingParticipants } from '@/features/rooms/hooks/useWaitingParticipants'
|
||||||
import { Participant } from 'livekit-client'
|
import { Participant } from 'livekit-client'
|
||||||
import { WaitingParticipant } from '@/features/rooms/api/listWaitingParticipants'
|
import { WaitingParticipant } from '@/features/rooms/api/listWaitingParticipants'
|
||||||
|
import { safeParseMetadata } from '@/features/rooms/utils/safeParseMetadata'
|
||||||
|
|
||||||
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
|
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
|
||||||
export const ParticipantsList = () => {
|
export const ParticipantsList = () => {
|
||||||
@@ -36,7 +37,7 @@ export const ParticipantsList = () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const raisedHandParticipants = participants.filter((participant) => {
|
const raisedHandParticipants = participants.filter((participant) => {
|
||||||
const data = JSON.parse(participant.metadata || '{}')
|
const data = safeParseMetadata(participant.metadata)
|
||||||
return data.raised
|
return data.raised
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
22
src/frontend/src/features/rooms/utils/safeParseMetadata.tsx
Normal file
22
src/frontend/src/features/rooms/utils/safeParseMetadata.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const safeParseMetadata = (
|
||||||
|
metadataStr: string | null | undefined
|
||||||
|
): Record<string, unknown> => {
|
||||||
|
if (!metadataStr) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(metadataStr)
|
||||||
|
|
||||||
|
// Ensure the result is an object
|
||||||
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||||
|
console.warn('Metadata parsed to non-object value:', parsed)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed as Record<string, unknown>
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse metadata:', error)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user