You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 15:06:20 +00:00
🆙 Camera Security fix / small fix for beds
- Validate data URL format (must start with data:image/png) - Validate PNG magic bytes on binary data before sending - Enforce 2MB size limit matching server - Add try/catch around atob() to handle invalid base64 gracefully - Fix XSS vulnerability in editor download: replace unsafe window.open()+document.write() with safe anchor-based download that also validates data URL scheme
This commit is contained in:
@@ -3,7 +3,7 @@ import { GetAssetManager } from '@nitrots/assets';
|
||||
import { GetConfiguration } from '@nitrots/configuration';
|
||||
import { GetEventDispatcher, RoomCameraWidgetManagerEvent } from '@nitrots/events';
|
||||
import { TextureUtils } from '@nitrots/utils';
|
||||
import { BLEND_MODES, ColorMatrix, ColorMatrixFilter, Container, Filter, Sprite, Texture } from 'pixi.js';
|
||||
import { BLEND_MODES, ColorMatrix, ColorMatrixFilter, Container, Filter, RenderTexture, Sprite, Texture } from 'pixi.js';
|
||||
import { RoomCameraWidgetEffect } from './RoomCameraWidgetEffect';
|
||||
|
||||
const COLOR_MATRIX_OFFSET_INDICES = [4, 9, 14, 19] as const;
|
||||
@@ -112,7 +112,12 @@ export class RoomCameraWidgetManager implements IRoomCameraWidgetManager
|
||||
|
||||
container.filters = filters;
|
||||
|
||||
return await TextureUtils.generateImage(container);
|
||||
const resolution = texture.source.resolution || 1;
|
||||
const renderTexture = RenderTexture.create({ width: texture.width, height: texture.height, resolution });
|
||||
|
||||
TextureUtils.writeToTexture(container, renderTexture);
|
||||
|
||||
return await TextureUtils.generateImage(renderTexture);
|
||||
}
|
||||
|
||||
public get effects(): Map<string, IRoomCameraWidgetEffect>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { IMessageComposer } from '@nitrots/api';
|
||||
import { TextureUtils } from '@nitrots/utils';
|
||||
import { NitroLogger, TextureUtils } from '@nitrots/utils';
|
||||
import { RenderTexture } from 'pixi.js';
|
||||
|
||||
const MAX_IMAGE_BYTES = 2 * 1024 * 1024;
|
||||
const PNG_MAGIC_BYTES = [0x89, 0x50, 0x4E, 0x47];
|
||||
|
||||
export class RenderRoomMessageComposer implements IMessageComposer<ConstructorParameters<typeof RenderRoomMessageComposer>>
|
||||
{
|
||||
private _data: any;
|
||||
@@ -27,16 +30,63 @@ export class RenderRoomMessageComposer implements IMessageComposer<ConstructorPa
|
||||
|
||||
if(!url) return;
|
||||
|
||||
const base64Data = url.split(',')[1];
|
||||
const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||
|
||||
this._data.push(binaryData.byteLength, binaryData.buffer);
|
||||
this.processBase64(url);
|
||||
}
|
||||
|
||||
public assignBase64(base64: string): void
|
||||
{
|
||||
this.processBase64(base64);
|
||||
}
|
||||
|
||||
private processBase64(base64: string): void
|
||||
{
|
||||
if(!base64 || !base64.includes(','))
|
||||
{
|
||||
NitroLogger.error('Camera: invalid base64 data URL');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!base64.startsWith('data:image/png'))
|
||||
{
|
||||
NitroLogger.error('Camera: rejected non-PNG image data');
|
||||
return;
|
||||
}
|
||||
|
||||
const base64Data = base64.split(',')[1];
|
||||
const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||
|
||||
if(!base64Data || !base64Data.length)
|
||||
{
|
||||
NitroLogger.error('Camera: empty base64 payload');
|
||||
return;
|
||||
}
|
||||
|
||||
let binaryData: Uint8Array;
|
||||
|
||||
try
|
||||
{
|
||||
binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
NitroLogger.error('Camera: failed to decode base64 data');
|
||||
return;
|
||||
}
|
||||
|
||||
if(binaryData.byteLength > MAX_IMAGE_BYTES)
|
||||
{
|
||||
NitroLogger.error(`Camera: image too large (${binaryData.byteLength} bytes, max ${MAX_IMAGE_BYTES})`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(binaryData.length < 4
|
||||
|| binaryData[0] !== PNG_MAGIC_BYTES[0]
|
||||
|| binaryData[1] !== PNG_MAGIC_BYTES[1]
|
||||
|| binaryData[2] !== PNG_MAGIC_BYTES[2]
|
||||
|| binaryData[3] !== PNG_MAGIC_BYTES[3])
|
||||
{
|
||||
NitroLogger.error('Camera: binary data does not have valid PNG header');
|
||||
return;
|
||||
}
|
||||
|
||||
this._data.push(binaryData.byteLength, binaryData.buffer);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
private static BASE_Y_SCALE: number = 1000;
|
||||
private static AVATAR_SPRITE_DEFAULT_DEPTH: number = -0.01;
|
||||
private static AVATAR_OWN_DEPTH_ADJUST: number = 0.001;
|
||||
private static AVATAR_SPRITE_LAYING_DEPTH: number = 0.2;
|
||||
private static AVATAR_SPRITE_LAYING_DEPTH: number = 0.002;
|
||||
|
||||
protected _data: AvatarVisualizationData;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user