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
🆙 Updates
* Fix PixiJS v8 deprecation warning (PixiJS v8 only allows Container objects to have children) * Changed from whitelist to blocklist approach in LegacyExternalInterface: - Allows legitimate callbacks like 'openroom', 'opennavigator', etc. - Blocks only dangerous globals (eval, Function, window, document, etc.) - Blocks prototype pollution vectors (__proto__, constructor, etc.) - Blocks network/storage APIs from being overwritten.
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { IGraphicAsset } from '@nitrots/api';
|
||||
import { GetRenderer, TextureUtils } from '@nitrots/utils';
|
||||
import { Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
|
||||
import { Container, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
|
||||
import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization';
|
||||
|
||||
export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization {
|
||||
@@ -138,7 +138,7 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
|
||||
const width = 64;
|
||||
const height = 64;
|
||||
|
||||
const container = new Sprite();
|
||||
const container = new Container();
|
||||
sprite.position.set((width - sprite.width) / 2, (height - sprite.height) / 2);
|
||||
container.addChild(sprite);
|
||||
|
||||
|
||||
@@ -40,37 +40,166 @@ declare global
|
||||
export class LegacyExternalInterface
|
||||
{
|
||||
private static readonly MESSAGE_KEY = 'Nitro_LegacyExternalInterface';
|
||||
private static readonly GAME_MESSAGE_KEY = 'Nitro_LegacyExternalGameInterface';
|
||||
private static _isListeningForPostMessages = false;
|
||||
private static _messageListener: (ev: MessageEvent) => void = null;
|
||||
|
||||
// Whitelist of allowed methods that can be called via postMessage
|
||||
// This prevents arbitrary code execution from malicious postMessage calls
|
||||
private static readonly ALLOWED_EXTERNAL_METHODS: ReadonlySet<string> = new Set([
|
||||
'cycleWallpaper',
|
||||
'cycleFloor',
|
||||
'cycleLandscape',
|
||||
'cycleBackgroundColor',
|
||||
'cycleAvatarLightLevel',
|
||||
'cycleAvatarName',
|
||||
'cycleAvatarTyping',
|
||||
'cycleAvatarEffect',
|
||||
'cycleAvatarIdle',
|
||||
'cycleAvatarDance',
|
||||
'cycleAvatarExpression',
|
||||
'cycleAvatarPosture',
|
||||
'cycleAvatarSign',
|
||||
'cycleAvatarSleep',
|
||||
'cycleAvatarTalk',
|
||||
'cycleAvatarWave',
|
||||
'cycleZoom',
|
||||
'cycleRoomBackgroundColor'
|
||||
]);
|
||||
|
||||
// Blocklist of dangerous global function/property names that cannot be overwritten
|
||||
// This prevents security vulnerabilities while allowing legitimate callbacks
|
||||
private static readonly BLOCKED_CALLBACK_NAMES: ReadonlySet<string> = new Set([
|
||||
// JavaScript execution functions
|
||||
'eval',
|
||||
'Function',
|
||||
'constructor',
|
||||
// Prototype pollution vectors
|
||||
'prototype',
|
||||
'__proto__',
|
||||
'__defineGetter__',
|
||||
'__defineSetter__',
|
||||
'__lookupGetter__',
|
||||
'__lookupSetter__',
|
||||
// Global objects
|
||||
'window',
|
||||
'document',
|
||||
'globalThis',
|
||||
'self',
|
||||
'top',
|
||||
'parent',
|
||||
'frames',
|
||||
// Module/import system
|
||||
'require',
|
||||
'import',
|
||||
'module',
|
||||
'exports',
|
||||
// Network/storage APIs
|
||||
'fetch',
|
||||
'XMLHttpRequest',
|
||||
'WebSocket',
|
||||
'Worker',
|
||||
'SharedWorker',
|
||||
'ServiceWorker',
|
||||
'localStorage',
|
||||
'sessionStorage',
|
||||
'indexedDB',
|
||||
'caches',
|
||||
// Object prototype methods
|
||||
'toString',
|
||||
'valueOf',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'toLocaleString',
|
||||
// Potentially dangerous DOM methods
|
||||
'postMessage',
|
||||
'addEventListener',
|
||||
'removeEventListener',
|
||||
'dispatchEvent',
|
||||
'setTimeout',
|
||||
'setInterval',
|
||||
'setImmediate',
|
||||
'requestAnimationFrame',
|
||||
'queueMicrotask'
|
||||
]);
|
||||
|
||||
// Allowed origins for postMessage - empty means same-origin only
|
||||
// Add trusted origins here if cross-origin communication is needed
|
||||
private static _allowedOrigins: Set<string> = new Set();
|
||||
|
||||
public static setAllowedOrigins(origins: string[]): void
|
||||
{
|
||||
this._allowedOrigins = new Set(origins);
|
||||
}
|
||||
|
||||
public static get available(): boolean
|
||||
{
|
||||
if(!this._isListeningForPostMessages)
|
||||
{
|
||||
this._isListeningForPostMessages = true;
|
||||
window.addEventListener('message', (ev) =>
|
||||
|
||||
this._messageListener = (ev: MessageEvent) =>
|
||||
{
|
||||
// Validate origin - only accept from same origin or explicitly allowed origins
|
||||
if(ev.origin !== window.location.origin && !this._allowedOrigins.has(ev.origin))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof ev.data !== 'string') return;
|
||||
|
||||
if(ev.data.startsWith(LegacyExternalInterface.MESSAGE_KEY))
|
||||
{
|
||||
const { method, params } = JSON.parse(
|
||||
ev.data.substr(LegacyExternalInterface.MESSAGE_KEY.length)
|
||||
);
|
||||
try
|
||||
{
|
||||
const { method, params } = JSON.parse(
|
||||
ev.data.substring(LegacyExternalInterface.MESSAGE_KEY.length)
|
||||
);
|
||||
|
||||
const fn = (window as any)[method];
|
||||
if(!fn) return;
|
||||
// Validate method is in whitelist before executing
|
||||
if(!this.ALLOWED_EXTERNAL_METHODS.has(method))
|
||||
{
|
||||
console.warn(`[LegacyExternalInterface] Blocked unauthorized method call: ${method}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fn(...params);
|
||||
// Validate params is an array
|
||||
if(!Array.isArray(params))
|
||||
{
|
||||
console.warn(`[LegacyExternalInterface] Invalid params for method: ${method}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fn = (window as any)[method];
|
||||
if(typeof fn !== 'function') return;
|
||||
|
||||
fn(...params);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error('[LegacyExternalInterface] Error processing message:', e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
window.addEventListener('message', this._messageListener);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static dispose(): void
|
||||
{
|
||||
if(this._messageListener)
|
||||
{
|
||||
window.removeEventListener('message', this._messageListener);
|
||||
this._messageListener = null;
|
||||
this._isListeningForPostMessages = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static call<K extends keyof typeof window.FlashExternalInterface>(
|
||||
method: K,
|
||||
...params: Parameters<typeof window.FlashExternalInterface[K]>
|
||||
@@ -78,10 +207,17 @@ export class LegacyExternalInterface
|
||||
{
|
||||
if(window.top !== window)
|
||||
{
|
||||
// Use parent origin if known, otherwise use '*' with caution
|
||||
// Note: Using '*' is necessary when the parent origin is unknown
|
||||
// The receiving end should validate the message content
|
||||
const targetOrigin = window.location.ancestorOrigins?.length > 0
|
||||
? window.location.ancestorOrigins[0]
|
||||
: '*';
|
||||
|
||||
window.top.postMessage(LegacyExternalInterface.MESSAGE_KEY + JSON.stringify({
|
||||
method,
|
||||
params
|
||||
}), '*');
|
||||
}), targetOrigin);
|
||||
}
|
||||
|
||||
if(!('FlashExternalInterface' in window)) return undefined;
|
||||
@@ -98,10 +234,15 @@ export class LegacyExternalInterface
|
||||
{
|
||||
if(window.top !== window)
|
||||
{
|
||||
window.top.postMessage('Nitro_LegacyExternalGameInterface' + JSON.stringify({
|
||||
// Use parent origin if known, otherwise use '*' with caution
|
||||
const targetOrigin = window.location.ancestorOrigins?.length > 0
|
||||
? window.location.ancestorOrigins[0]
|
||||
: '*';
|
||||
|
||||
window.top.postMessage(LegacyExternalInterface.GAME_MESSAGE_KEY + JSON.stringify({
|
||||
method,
|
||||
params
|
||||
}), '*');
|
||||
}), targetOrigin);
|
||||
}
|
||||
|
||||
if(!('FlashExternalGameInterface' in window)) return undefined;
|
||||
@@ -111,8 +252,55 @@ export class LegacyExternalInterface
|
||||
return typeof fn !== 'undefined' ? fn(...params) : undefined;
|
||||
}
|
||||
|
||||
public static addCallback(name: string, func: Function)
|
||||
public static addCallback(name: string, func: Function): boolean
|
||||
{
|
||||
// Validate callback name is not empty
|
||||
if(!name || typeof name !== 'string' || name.trim().length === 0)
|
||||
{
|
||||
console.warn('[LegacyExternalInterface] Invalid callback name');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check against blocklist of dangerous global function names
|
||||
// This prevents overwriting critical globals like 'eval', 'Function', etc.
|
||||
if(this.BLOCKED_CALLBACK_NAMES.has(name))
|
||||
{
|
||||
console.warn(`[LegacyExternalInterface] Blocked registration of dangerous callback: ${name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Additional safety: prevent overwriting existing non-function properties
|
||||
const existing = (window as any)[name];
|
||||
if(existing !== undefined && typeof existing !== 'function')
|
||||
{
|
||||
console.warn(`[LegacyExternalInterface] Cannot overwrite non-function property: ${name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
(window as any)[name] = func;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static removeCallback(name: string): boolean
|
||||
{
|
||||
// Validate callback name
|
||||
if(!name || typeof name !== 'string' || name.trim().length === 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow removal of blocked/dangerous globals
|
||||
if(this.BLOCKED_CALLBACK_NAMES.has(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if((window as any)[name] !== undefined)
|
||||
{
|
||||
delete (window as any)[name];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user