Files
Nitro-V3/src/components/room/widgets/furniture/FurnitureExternalImageView.tsx
T
simoleo89 24d10aced1 fix(security): harden external-link opening (protocol allow-list + noopener)
URLs reached window.open from user/server-controlled content without a protocol check or noopener, allowing reverse-tabnabbing and (for the chat link handler) a javascript:/data: href running in our origin.

- add isSafeExternalUrl() (http/https only) + tests; gate the chat link opener (useOnClickChat) and external photo opener with it

- SanitizeHtml: afterSanitizeAttributes hook forces rel="noopener noreferrer" on any target=_blank anchor (overrides attacker-supplied rel)

- add noopener,noreferrer to the remaining window.open(_blank) sites (YouTube share, external photo, guide forum link); drop a stray console.log
2026-06-17 19:12:01 +02:00

37 lines
1.8 KiB
TypeScript

import { FC } from 'react';
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
import { GetConfigurationValue, isSafeExternalUrl, LocalizeText, ReportType } from '../../../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
import { useFurnitureExternalImageWidget, useHelp } from '../../../../hooks';
import { CameraWidgetShowPhotoView } from '../../../camera/views/CameraWidgetShowPhotoView';
export const FurnitureExternalImageView: FC<{}> = props =>
{
const { objectId = -1, currentPhotoIndex = -1, currentPhotos = null, onClose = null } = useFurnitureExternalImageWidget();
const { report = null } = useHelp();
if (objectId === -1 || currentPhotoIndex === -1) return null;
const handleOpenFullPhoto = () =>
{
const photoUrl = currentPhotos[currentPhotoIndex].w.replace('_small.png', '.png');
if (photoUrl && isSafeExternalUrl(photoUrl))
{
window.open(photoUrl, '_blank', 'noopener,noreferrer');
}
};
return (
<NitroCardView className="nitro-external-image-widget no-resize" uniqueKey="photo-viewer" theme="primary-slim">
<NitroCardHeaderView
headerText={ LocalizeText('camera.interface.title') }
isGalleryPhoto={true}
onCloseClick={onClose}
onReportPhoto={() => report(ReportType.PHOTO, { extraData: currentPhotos[currentPhotoIndex].w, roomId: currentPhotos[currentPhotoIndex].s, reportedUserId: GetSessionDataManager().userId, roomObjectId: Number(currentPhotos[currentPhotoIndex].u) })}
/>
<NitroCardContentView>
<CameraWidgetShowPhotoView currentIndex={currentPhotoIndex} currentPhotos={currentPhotos} onClick={handleOpenFullPhoto} />
</NitroCardContentView>
</NitroCardView>
);
};