From 340cf27bd022b11dd66a222001a7741fe6812589 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sat, 25 May 2024 11:46:18 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0sandbox.js=E3=80=81secu?= =?UTF-8?q?rity.js=EF=BC=9B=E5=B9=B6=E7=BB=B4=E6=8A=A4ts=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=5F(:=D0=B7=E3=80=8D=E2=88=A0)=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/util/sandbox.js | 3571 +++++++++++++++++++++++++++++++++++++++ noname/util/security.js | 642 +++++++ 2 files changed, 4213 insertions(+) create mode 100644 noname/util/sandbox.js create mode 100644 noname/util/security.js diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js new file mode 100644 index 000000000..54ecc24ad --- /dev/null +++ b/noname/util/sandbox.js @@ -0,0 +1,3571 @@ +const FILE_URL = import.meta.url; + +/** @typedef {any} Window */ + +// 方便开关确定沙盒的问题喵 +const SANDBOX_ENABLED = true; + +// 暴露方法Symbol +const SandboxExposer = Symbol("Sandbox.Exposer"); // 实例暴露 +const SandboxExposer2 = Symbol("Sandbox.Exposer2"); // 静态暴露 + +// 暴露方法Signal +const SandboxSignal_InitDomain = Symbol("InitDomain"); +const SandboxSignal_GetMarshalledProxy = Symbol("GetMarshalledProxy"); +const SandboxSignal_SetMarshalledProxy = Symbol("SetMarshalledProxy"); +const SandboxSignal_GetWindow = Symbol("GetWindow"); +const SandboxSignal_GetPromise = Symbol("GetPromise"); +const SandboxSignal_IsArray = Symbol("IsArray"); +const SandboxSignal_EnterDomain = Symbol("EnterDomain"); +const SandboxSignal_ExitDomain = Symbol("ExitDomain"); +const SandboxSignal_ListDomain = Symbol("ListDomain"); +const SandboxSignal_UnpackProxy = Symbol("UnpackProxy"); +const SandboxSignal_Marshal = Symbol("Marshal"); +const SandboxSignal_MarshalArray = Symbol("MarshalArray"); +const SandboxSignal_TrapDomain = Symbol("TrapDomain"); +const SandboxSignal_DiapatchMonitor = Symbol("DiapatchMonitor"); +const SandboxSignal_ListMonitor = Symbol("ListMonitor"); +const SandboxSignal_ExposeInfo = Symbol("ExposeInfo"); + +/** + * ```plain + * 判断是否为基元类型 + * ``` + * + * @param {any} obj + * @returns {boolean} + */ +function isPrimitive(obj) { + return Object(obj) !== obj; +} + +/** + * ```plain + * AccessAction枚举 + * 提供给Rule类作为权限ID + * 对应 Proxy 的12种拦截器 + * ``` + */ +class AccessAction { + // static CALL = 0; // apply + // static NEW = 1; // construct + // static READ = 2; // get + // static WRITE = 3; // set + // static DESCRIBE = 4; // getOwnPropertyDescriptor + // static DEFINE = 5; // defineProperty + // static TRACE = 6; // getPrototypeOf + // static META = 7; // setPrototypeOf + // static SEAL = 8; // preventExtensions + // static EXISTS = 9; // has + // static LIST = 10; // ownKeys + // static DELETE = 11; // delete + + /** ```Reflect.apply``` */ + static CALL = 0; + /** ```Reflect.construct``` */ + static NEW = 1; + /** ```Reflect.get``` */ + static READ = 2; + /** ```Reflect.set ``` */ + static WRITE = 3; + /** ```Reflect.getOwnPropertyDescriptor``` */ + static DESCRIBE = 4; + /** ```Reflect.defineProperty``` */ + static DEFINE = 5; + /** ```Reflect.getPrototypeOf``` */ + static TRACE = 6; + /** ```Reflect.setPrototypeOf``` */ + static META = 7; + /** ```Reflect.preventExtensions``` */ + static SEAL = 8; + /** ```Reflect.has``` */ + static EXISTS = 9; + /** ```Reflect.ownKeys``` */ + static LIST = 10; + /** ```Reflect.delete``` */ + static DELETE = 11 + + /** + * 判断给定的action是否是AccessAction + * + * @param {number} action + * @returns + */ + static isAccessAction(action) { + return typeof action == "number" + && action >= 0 && action < 12; + } +} + +/** + * ```plain + * 指定一个对象的封送规则 + * + * 是否允许对象进行封送 + * 是否允许对象封送到某个具体的运行域 + * 是否允许封送的对象进行特定的操作 + * ``` + */ +class Rule { + /** @type {Domain} */ + #domain; + /** @type {boolean} */ + #allowMarshal = true; + + /** @type {WeakSet?} */ + #allowMarshalTo = null; + /** @type {WeakSet?} */ + #disallowMarshalTo = null; + + /** @type {boolean[]} */ + #permissions = new Array(12).fill(true); + + /** @type {((...args: any[]) => boolean)?} */ + #accessControl = null; + + /** + * ```plain + * 创建一个封送规则 + * ``` + * + * @param {Rule?} rule + */ + constructor(rule = null) { + this.#domain = Domain.current; + + if (rule instanceof Rule) { + this.#allowMarshal = rule.#allowMarshal; + this.#allowMarshalTo = rule.#allowMarshalTo; + this.#disallowMarshalTo = rule.#disallowMarshalTo; + this.#permissions = rule.#permissions.slice(); + this.#accessControl = rule.#accessControl; + } + } + + /** + * ```plain + * 检查当前是否是 Monitor 所属的运行域 + * ``` + * + * @param {Rule} thiz + */ + static #assertOperator = function (thiz) { + if (thiz.#domain !== Domain.current) + throw new Error("当前不是 Rule 所属的运行域"); + } + + /** + * ```plain + * 是否允许对象进行封送 + * ``` + * + * @type {boolean} + */ + get canMarshal() { + Rule.#assertOperator(this); + return this.#allowMarshal; + } + + /** + * ```plain + * 是否允许对象进行封送 + * ``` + * + * @type {boolean} + */ + set canMarshal(newValue) { + Rule.#assertOperator(this); + this.#allowMarshal = !!newValue; + } + + /** + * ```plain + * 检查当前的规则是否允许封送到指定的运行域 + * ``` + * + * @param {Domain} domain + * @returns {boolean} + */ + canMarshalTo(domain) { + Rule.#assertOperator(this); + + if (!this.#allowMarshal) + return false; + + if (this.#allowMarshalTo) + return this.#allowMarshalTo.has(domain); + else if (this.#disallowMarshalTo) + return !this.#disallowMarshalTo.has(domain); + + return true; + } + + /** + * ```plain + * 将特定的运行域添加到当前对象的封送白名单 + * + * 请注意,封送白名单与黑名单不能同时指定 + * ``` + * + * @param {Domain} domain + */ + allowMarshalTo(domain) { + Rule.#assertOperator(this); + + if (!this.#allowMarshalTo) { + if (this.#disallowMarshalTo) + throw new TypeError("封送黑名单与封送白名单不能同时存在"); + + this.#allowMarshalTo = new WeakSet(); + } + + this.#allowMarshalTo.add(domain); + } + + /** + * ```plain + * 将特定的运行域添加到当前对象的封送黑名单 + * + * 请注意,封送白名单与黑名单不能同时指定 + * ``` + * + * @param {Domain} domain + */ + disallowMarshalTo(domain) { + Rule.#assertOperator(this); + + if (!this.#disallowMarshalTo) { + if (this.#allowMarshalTo) + throw new TypeError("封送黑名单与封送白名单不能同时存在"); + + this.#disallowMarshalTo = new WeakSet(); + } + + this.#disallowMarshalTo.add(domain); + } + + /** + * ```plain + * 检查给定的AccessAction是否被允许 + * ``` + * + * @param {number} action + * @returns {boolean} + */ + isGranted(action) { + Rule.#assertOperator(this); + + if (!AccessAction.isAccessAction(action)) + throw new TypeError("参数 action 不是一个有效的操作"); + + return this.#permissions[action]; + } + + /** + * ```plain + * 指定给定的AccessAction是否被允许 + * ``` + * + * @param {number} action + * @param {boolean} granted + */ + setGranted(action, granted) { + Rule.#assertOperator(this); + + if (!AccessAction.isAccessAction(action)) + throw new TypeError("参数 action 不是一个有效的操作"); + + this.#permissions[action] = !!granted; + } + + /** + * ```plain + * 判断在给定的AccessAction与指定的参数下是否允许访问 + * ``` + * + * @param {number} action + * @param {...any} args + * @returns {boolean} + */ + canAccess(action, ...args) { + Rule.#assertOperator(this); + + if (!this.isGranted(action)) + return false; + if (this.#accessControl + && !this.#accessControl(action, ...args)) + return false; + + return true; + } + + /** + * ```plain + * 设置当前的权限控制器 + * + * 权限控制器形参是拦截器的对应参数 + * 返回值则控制本次访问是否允许 + * ``` + * + * @param {(...args: any[]) => boolean} accessControl + */ + setAccessControl(accessControl) { + Rule.#assertOperator(this); + + if (typeof accessControl != "function") + throw new TypeError("无效的权限控制器"); + if (this.#accessControl) + throw new TypeError("权限控制器已经被设置"); + + this.#accessControl = accessControl; + } +} + +/** + * ```plain + * 全局变量映射表 + * + * 在下表中标记的全局变量, + * 封送时将不使用代理封送, + * 而是直接映射成另一个运行域对应的全部变量 + * + * 映射表项格式: + * string: 全局变量路径 + * 例如: /Object/assign 指向 window.Object.assign + * 同时路径也是映射的键名 + * array: [全局变量名称, 对应的获取代码] + * 例如: [/AsyncFunction, (async()=>{}).constructor] + * 指向异步函数的构造函数,使用/AsyncFunction作为映射键名 + * + * 请注意,映射键名不得相同,不然会导致相互覆盖 + * 全局变量映射表应该用于JavaScript的内建对象 + * 因为只有内建对象才会在所有运行域同时都有 + * ``` + */ +const GLOBAL_PATHES = Object.freeze([ + "/Object", + "/Array", + "/Promise", + "/Date", + "/String", + "/Number", + "/Boolean", + "/BigInt", + "/RegExp", + "/Symbol", + "/Error", + "/TypeError", + "/SyntaxError", + "/RangeError", + "/EvalError", + "/ReferenceError", + "/Map", + "/Set", + "/WeakRef", + "/WeakMap", + "/WeakSet", + "/Object/prototype", + "/Array/prototype", + "/Function/prototype", + "/Promise/prototype", + "/Date/prototype", + "/String/prototype", + "/Number/prototype", + "/Boolean/prototype", + "/BigInt/prototype", + "/RegExp/prototype", + "/Symbol/prototype", + "/Error/prototype", + "/TypeError/prototype", + "/SyntaxError/prototype", + "/RangeError/prototype", + "/EvalError/prototype", + "/ReferenceError/prototype", + "/Map/prototype", + "/Set/prototype", + "/WeakRef/prototype", + "/WeakMap/prototype", + "/WeakSet/prototype", + ["/Generator", "(function*(){})().constructor"], + ["/AsyncGenerator", "(async function*(){})().constructor"], + ["/Generator/prototype", "(function*(){})().constructor.prototype"], + ["/AsyncGenerator/prototype", "(async function*(){})().constructor.prototype"], + ["/GeneratorFunction/prototype", "(function*(){}).constructor.prototype"], + ["/AsyncFunction/prototype", "(async()=>{}).constructor.prototype"], + ["/AsyncGeneratorFunction/prototype", "(async function*(){}).constructor.prototype"], + "/JSON", + "/Proxy", + "/Math", + "/Reflect", + "/parseInt", + "/parseFloat", + "/isNaN", + "/isFinite", + "/alert", + "/confirm", + "/console", + + // 危险对象不传递 + // "/Function", + // ["/GeneratorFunction", "(function*(){}).constructor"], + // ["/AsyncFunction", "(async()=>{}).constructor"], + // ["/AsyncGeneratorFunction", "(async function*(){}).constructor"], + // "/eval", +]); + +/** + * ```plain + * 初始化内建对象时就需要封送的全局变量 + * ``` + */ +const MARSHALLED_LIST = Object.freeze([ + "/setTimeout", + "/clearTimeout", + "/setInterval", + "/clearInterval", + "/setImmediate", + "/clearImmediate", + "/requestAnimationFrame", + "/cancelAnimationFrame", + "/requestIdleCallback", + "/cancelIdleCallback", + "/queueMicrotask", +]); + +/** + * ```plain + * 为每个运行域的全局对象提供封送映射 + * + * 非暴露类 + * ``` + */ +class Globals { + /** @type {[WeakMap, Object, Object]} */ + static #topGlobals; + /** @type {WeakMap} */ + static #globals = new WeakMap(); + /** @type {Object} */ + static #builtinKeys = {}; + + /** + * ```plain + * 判断是否是顶级域的内建对象 + * ``` + * + * @param {string|symbol} key + * @returns {boolean} + */ + static isBuiltinKey(key) { + return key in Globals.#builtinKeys; // 基于hash的存在性检查效率最高喵 + } + + /** + * ```plain + * 解析映射路径 + * ``` + * + * @param {string|string[]} path + * @param {Window} window + * @returns {[string, any]} [映射键名, 映射值] + */ + static parseFrom(path, window) { + if (typeof path == "string") { + const items = path.split("/").filter(Boolean); + let obj = window; + + for (const item of items) + if (!(obj = obj[item])) + break; + + return [path, obj]; + } else + return [path[0], window.eval(path[1])]; + } + + /** + * ```plain + * 解析映射路径为索引 + * ``` + * + * @param {string} path + * @param {Window} window + * @returns {[Object, string]} [索引对象, 索引键名] + */ + static parseIndex(path, window) { + const items = path.split("/").filter(Boolean); + const last = items.pop(); + let obj = window; + + for (const item of items) + if (!(obj = obj[item])) + break; + + // @ts-ignore + return [obj, last]; + } + + /** + * ```plain + * 初始化运行域的全局对象映射 + * ``` + * + * @param {Domain} domain + */ + static ensureDomainGlobals(domain) { + if (!Globals.#globals.has(domain)) { + const window = domain[SandboxExposer](SandboxSignal_GetWindow); + const globals = [new WeakMap(), {}]; + + if (Globals.#topGlobals) { + const marshalleds = Globals.#topGlobals[2]; + + for (const path of MARSHALLED_LIST) { + const [obj, key] = Globals.parseIndex(path, window); + obj[key] = trapMarshal(Domain.topDomain, domain, marshalleds[path]); + } + } else { + // @ts-ignore + Globals.#topGlobals = globals; + globals.push({}); + + for (const path of MARSHALLED_LIST) { + const [key, obj] = Globals.parseFrom(path, window); + + if (obj == null) + continue; + + globals[2][key] = obj; + } + + for (const key of Reflect.ownKeys(window)) + Globals.#builtinKeys[key] = true; + } + + for (const path of GLOBAL_PATHES) { + const [key, obj] = Globals.parseFrom(path, window); + + if (obj == null) + continue; + + globals[0].set(obj, key); + globals[1][key] = obj; + } + + // @ts-ignore + Globals.#globals.set(domain, globals); + } + } + + /** + * ```plain + * 将一个对象映射为全局键 + * ``` + * + * @param {Domain} domain + * @param {Object} obj + */ + static findGlobalKey(domain, obj) { + Globals.ensureDomainGlobals(domain); + const globals = Globals.#globals.get(domain); + // @ts-ignore + return globals[0].get(obj); + } + + /** + * ```plain + * 将一个全局键映射为对象 + * ``` + * + * @param {Domain} domain + * @param {string} key + */ + static findGlobalObject(domain, key) { + Globals.ensureDomainGlobals(domain); + const globals = Globals.#globals.get(domain); + // @ts-ignore + return globals[1][key]; + } + + /** + * ```plain + * 将一个运行域的全局对象映射为另一个运行域的全局对象 + * ``` + * + * @param {Object} obj + * @param {Domain} sourceDomain + * @param {Domain} targetDomain + */ + static mapTo(obj, sourceDomain, targetDomain) { + const key = Globals.findGlobalKey(sourceDomain, obj); + + if (!key) + return undefined; + + return Globals.findGlobalObject(targetDomain, key); + } +} + +/** + * ```plain + * 需要封装传递ExecuteContext的函数 + * + * 根据HTML现有函数设置 + * 请不要改动下面的列表 + * ``` + */ +const wrappingFunctions = [ + "/setTimeout", + "/setInterval", + "/setImmediate", + "/requestAnimationFrame", + "/requestIdleCallback", + "/queueMicrotask", + "/EventTarget/prototype/addEventListener", + "/EventTarget/prototype/removeEventListener", + [/^HTML\w*?Element$/, "prototype", /^on[a-z0-9]+$/, "*"], + ["IDBRequest", "prototype", /^on[a-z0-9]+$/, "*"], + ["XMLHttpRequestEventTarget", "prototype", /^on[a-z0-9]+$/, "*"], + "/MutationObserver", + + // "/HTMLCanvasElement/prototype/toBlob", + // "/DataTransferItem/prototype/getAsString", + // "/LaunchQueue/prototype/setConsumer", + // "/ResizeObserver", + // "/ReportingObserver", + // "/PerformanceObserver", + // "/IntersectionObserver", +]; + +// 不支持的: +// customElements / CustomElementRegistry, 太抽象了喵,太多东西了喵,太麻烦了喵,太复杂了喵(用这个的要被狠狠的打皮鼓喵! + +/** + * ```plain + * 对于原生函数进行封装 + * + * 非暴露类 + * ``` + */ +class NativeWrapper { + static #unboxedFunction = Symbol("NativeWrapper.unboxedFunction"); + + /** @type {WeakMap} */ + static #boxedMap = new WeakMap(); + /** @type {WeakSet} */ + static #boxedSet = new WeakSet(); + + /** @type {typeof Function?} */ + static #topFunction = null; + /** @type {typeof Function?} */ + static #currentFunction = null; + + /** + * ```plain + * 初始化顶级运行域的Function + * ``` + * + * @param {Window} topGlobal + */ + static initTopDomain(topGlobal) { + if (NativeWrapper.#topFunction) + throw new Error("NativeWrapper 已经初始化过了"); + + NativeWrapper.#topFunction = topGlobal.Function; + } + + /** + * ```plain + * 对某个域的原生函数进行封装 + * ``` + * + * @param {Window} global + */ + static wrapInDomains(global) { + NativeWrapper.#currentFunction = global.Function; + + for (const selector of wrappingFunctions) + NativeWrapper.wrapFunctions(global, selector); + + NativeWrapper.#currentFunction = null; + } + + /** + * ```plain + * 根据选择器对原生函数进行封装 + * ``` + * + * @param {Window} global + * @param {string|Array} selector + */ + static wrapFunctions(global, selector) { + /** @type {Array} */ + const items = Array.isArray(selector) + ? selector : selector.split("/").filter(Boolean); + + let flags = 2; // 默认装箱了喵 + + if (items[items.length - 1] === "*") { + flags |= 1; + items.pop(); + } + + items.unshift(global); + + const pathes = [items]; + const indexes = []; + + while (pathes.length) { + /** @type {Array} */ + // @ts-ignore + const path = pathes.shift(); + + if (path.length == 2) { + if (!(path[1] instanceof RegExp)) { + if (path[1] in path[0]) + indexes.push(path); + + continue; + } + + const root = path[0]; + const pattern = path[1]; + indexes.push(...Reflect.ownKeys(root) + .filter(k => pattern.test( + typeof k == "string" + ? k : `@${k.description}`)) + .filter(k => k in root) + .map(k => [root, k])); + + continue; + } + + if (!(path[1] instanceof RegExp)) { + const root = path.shift(); + + if (path[0] === "__proto__") + path[0] = Reflect.getPrototypeOf(root); + else + path[0] = root[path[0]]; + + if (!path[0]) + continue; + + pathes.push(path); + continue; + } + + const root = path.shift(); + const pattern = path.shift(); + const keys = Reflect.ownKeys(root) + .filter(k => pattern.test( + typeof k == "string" + ? k : `@${k.description}`)) + .filter(k => root[k]); + + if (!keys.length) + continue; + + pathes.push(...keys + .map(k => [root[k], ...path])); + } + + for (const index of indexes) + // @ts-ignore + NativeWrapper.wrapFunction(global, ...index, flags); + } + + /** + * ```plain + * 对于具体的原生函数进行封装 + * ``` + * + * @param {Window} global + * @param {Object} parent + * @param {string|symbol} name + * @param {number} flags + */ + static wrapFunction(global, parent, name, flags) { + if (flags & 1) { + const descriptor = Reflect.getOwnPropertyDescriptor(parent, name); + + if (!descriptor + || typeof descriptor.get != "function" + || typeof descriptor.set != "function") + throw new TypeError("不支持的HTML实现"); + + descriptor.get = NativeWrapper.wrapGetter(descriptor.get); + descriptor.set = NativeWrapper.wrapSetter(descriptor.set); + Reflect.defineProperty(parent, name, descriptor); + } else { + const defaultFunction = parent[name]; + + if (!defaultFunction) + return; + + if (defaultFunction.prototype) { + const wrappedApply = NativeWrapper.wrapApply(defaultFunction, flags); + const wrappedConstruct = NativeWrapper.wrapConstruct(defaultFunction, flags); + + parent[name] = new Proxy(defaultFunction, { + apply(target, thisArg, argArray) { + return Reflect.apply(wrappedApply, thisArg, argArray); + }, + construct(target, argArray, newTarget) { + return Reflect.construct(wrappedConstruct, argArray, newTarget); + }, + }); + } else { + parent[name] = NativeWrapper.wrapApply( + global === parent + ? defaultFunction.bind(null) + : defaultFunction, flags); + } + } + } + + /** + * ```plain + * 将原生函数进行调用封装 + * ``` + * + * @param {Function} func + * @param {number} flags + * @returns {Function} + */ + static wrapApply(func, flags = 0) { + const wrapped = (flags & 2) + ? function (...args) { + const list = args.map(a => NativeWrapper.boxCallback(a)); + // @ts-ignore + return ContextInvoker1(func, this, list); + } + : function (...args) { + // @ts-ignore + return ContextInvoker1(func, this, args); + }; + + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, + // @ts-ignore + NativeWrapper.#currentFunction.prototype); + return wrapped; + } + + /** + * ```plain + * 将原生函数进行构造封装 + * ``` + * + * @param {Function} func + * @param {number} flags + * @returns {Function} + */ + static wrapConstruct(func, flags = 0) { + const wrapped = (flags & 2) + ? function (...args) { + const list = args.map(a => NativeWrapper.boxCallback(a)); + return ContextInvoker2(func, list, new.target); + } + : function (...args) { + return ContextInvoker2(func, args, new.target); + }; + + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, + // @ts-ignore + NativeWrapper.#currentFunction.prototype); + return wrapped; + } + + /** + * ```plain + * 将原生GETTER进行调用封装 + * ``` + * + * @param {Function} func + * @returns {(...args: any[]) => any} + */ + static wrapGetter(func) { + const wrapped = function () { + // @ts-ignore + return NativeWrapper.unboxCallback(ContextInvoker1(func, this, [])); + }; + + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, + // @ts-ignore + NativeWrapper.#currentFunction.prototype); + return wrapped; + } + + /** + * ```plain + * 将原生SETTER进行调用封装 + * ``` + * + * @param {Function} func + * @returns {(...args: any[]) => any} + */ + static wrapSetter(func) { + const wrapped = function (value) { + // @ts-ignore + return ContextInvoker1(func, this, [NativeWrapper.boxCallback(value)]); + }; + + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, + // @ts-ignore + NativeWrapper.#currentFunction.prototype); + return wrapped; + } + + /** + * ```plain + * 将回调函数进行装箱 + * ``` + * + * @param {Proxy} unboxed + * @returns + */ + static boxCallback(unboxed) { + if (typeof unboxed != "function") + return unboxed; + + let wrapped = NativeWrapper.#boxedMap.get(unboxed); + + if (!wrapped) { + wrapped = ContextInvokerCreator({ + unboxed, // 向封装函数提供unboxed函数 + }, function (thiz, args, newTarget) { + return newTarget + // @ts-ignore + ? Reflect.construct(this.unboxed, args, newTarget) + // @ts-ignore + : Reflect.apply(this.unboxed, thiz, args); + }); + + // 设置暴露器 + wrapped[SandboxExposer] = (signal) => { + if (signal === NativeWrapper.#unboxedFunction) + return unboxed; + }; + + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, + // @ts-ignore + NativeWrapper.#currentFunction.prototype); + NativeWrapper.#boxedMap.set(unboxed, wrapped); + } + + NativeWrapper.#boxedSet.add(wrapped); + return wrapped; + } + + /** + * ```plain + * 将回调函数进行拆箱 + * ``` + * + * @param {Function} boxed + * @returns + */ + static unboxCallback(boxed) { + if (!NativeWrapper.#boxedSet.has(boxed)) + return boxed; + + return boxed[SandboxExposer] + (NativeWrapper.#unboxedFunction); + } +} + +// 执行上下文传递函数,请勿动喵 + +/** @type {(target: Function, thiz: Object, args: Array) => any} */ +// @ts-ignore +const ContextInvoker1 = window.replacedCI1 + || (function (apply, target, thiz, args) { + return apply(target, thiz, args); + }).bind(null, Reflect.apply); + +/** @type {(target: Function, args: Array, newTarget: Function) => any} */ +// @ts-ignore +const ContextInvoker2 = window.replacedCI2 + || (function (construct, target, args, newTarget) { + return construct(target, args, newTarget); + }).bind(null, Reflect.construct); + +/** @type {(closure: Object, target: Function) => ((...args: any[]) => any)} */ +// @ts-ignore +const ContextInvokerCreator = window.replacedCIC + || (function (apply, closure, target) { + return function (...args) { + return apply(target, closure, + // @ts-ignore + [this === window ? null : this, args, new.target]); + }; + }).bind(null, Reflect.apply); + +/** + * ```plain + * 管理每个运行域对于其封送对象的 Monitor + * + * 非暴露类 + * ``` + */ +class DomainMonitors { + /** @type {WeakMap} */ + static #domainMonitors = new WeakMap(); + + /** @type {WeakMap>>} */ + #monitorsMap = new WeakMap(); + + /** + * ```plain + * 在当前运行域安装一个 Monitor + * ``` + * + * @param {DomainMonitors} thiz + * @param {Monitor} monitor + */ + static #installMonitor = function (thiz, monitor) { + // @ts-ignore + const [ + actions, + allowDomains, + disallowDomains, + ] = Monitor[SandboxExposer2] + (SandboxSignal_ExposeInfo, monitor); + + function addToActionMap(actionMap) { + for (const action of actions) { + let monitorMap = actionMap[action]; + + if (!monitorMap) + monitorMap = actionMap[action] = new Set(); + + monitorMap.add(monitor); + } + } + + const domainList = []; + + if (!allowDomains) { + // @ts-ignore + const totalDomains = new Set(Domain[SandboxExposer2] + (SandboxSignal_ListDomain)); + totalDomains.delete(monitor.domain); + + if (disallowDomains) + for (const domain of disallowDomains) + totalDomains.delete(domain); + + domainList.push(...totalDomains); + } else + domainList.push(...allowDomains); + + for (const domain of domainList) { + let actionMap = thiz.#monitorsMap.get(domain); + + if (!actionMap) + thiz.#monitorsMap.set(domain, actionMap = {}); + + addToActionMap(actionMap); + } + } + + /** + * ```plain + * 从当前运行域卸载一个 Monitor + * ``` + * + * @param {DomainMonitors} thiz + * @param {Monitor} monitor + */ + static #uninstallMonitor = function (thiz, monitor) { + // @ts-ignore + const [ + actions, + allowDomains, + disallowDomains, + ] = Monitor[SandboxExposer2] + (SandboxSignal_ExposeInfo, monitor); + + function removeFromActionMap(actionMap) { + for (const action of actions) { + const monitorMap = actionMap[action]; + + if (!monitorMap) + continue; + + monitorMap.delete(monitor); + } + } + + const domainList = []; + + if (!allowDomains) { + // @ts-ignore + const totalDomains = new Set(Domain[SandboxExposer2] + (SandboxSignal_ListDomain)); + + if (disallowDomains) + for (const domain of disallowDomains) + totalDomains.delete(domain); + + domainList.push(...totalDomains); + } else + domainList.push(...allowDomains); + + for (const domain of domainList) { + const actionMap = thiz.#monitorsMap.get(domain); + + if (!actionMap) + continue; + + removeFromActionMap(actionMap); + } + } + + /** + * ```plain + * 获取当前运行域封送到目标运行域的所有符合条件的 Monitor + * ``` + * + * @param {Domain} sourceDomain + * @param {Domain} targetDomain + * @param {number} action + * @returns {Set?} + */ + static #getMonitorsBy = function (sourceDomain, targetDomain, action) { + const instance = DomainMonitors.#domainMonitors.get(sourceDomain); + + if (!instance) + return null; + + const actionMap = instance.#monitorsMap.get(targetDomain); + + if (!actionMap || !(action in actionMap)) + return null; + + return actionMap[action]; + } + + /** + * ```plain + * 对新的运行域进行 Monitor 安装 + * ``` + * + * @param {DomainMonitors} thiz + * @param {Domain} domain + */ + static #handleNewDomain = function (thiz, domain) { + let actionMap = thiz.#monitorsMap.get(domain); + + for (const monitor of + // @ts-ignore + Monitor[SandboxExposer2](SandboxSignal_ListMonitor)) { + if (monitor.domain === domain) + continue; + + const [ + actions, + allowDomains, + disallowDomains, + ] = Monitor[SandboxExposer2] + (SandboxSignal_ExposeInfo, monitor); + + if (allowDomains + && !allowDomains.has(domain)) + continue; + if (disallowDomains + && disallowDomains.has(domain)) + continue; + + if (!actionMap) + thiz.#monitorsMap.set(domain, actionMap = {}); + + for (const action of actions) { + let monitors = actionMap[action]; + + if (!monitors) + monitors = actionMap[action] = new Set(); + + monitors.add(monitor); + } + } + } + + /** + * ```plain + * 处理新的运行域 + * ``` + * + * @param {Domain} newDomain + */ + static handleNewDomain(newDomain) { + // @ts-ignore + const totalDomains = new Set(Domain[SandboxExposer2] + (SandboxSignal_ListDomain)); + + for (const domain of totalDomains) { + const instance = DomainMonitors.#domainMonitors.get(domain); + + if (!instance) + continue; + + DomainMonitors.#handleNewDomain(instance, newDomain); + } + } + + /** + * ```plain + * 分发 Monitor 监听事件 + * ``` + * + * @param {Domain} sourceDomain + * @param {Domain} targetDomain + * @param {number} action + * @param {Array} args + * @returns + */ + static dispatch(sourceDomain, targetDomain, action, args) { + const nameds = {}; + let indexMap; + + switch (action) { + case AccessAction.CALL: + indexMap = { + target: 0, + thisArg: 1, + arguments: 2, + }; + break; + case AccessAction.NEW: + indexMap = { + target: 0, + arguments: 1, + newTarget: 2, + }; + break; + case AccessAction.DEFINE: + indexMap = { + target: 0, + property: 1, + descriptor: 2, + }; + break; + case AccessAction.DELETE: + case AccessAction.DESCRIBE: + case AccessAction.EXISTS: + indexMap = { + target: 0, + property: 1, + }; + break; + case AccessAction.READ: + indexMap = { + target: 0, + property: 1, + receiver: 2, + }; + break; + case AccessAction.TRACE: + case AccessAction.LIST: + case AccessAction.SEAL: + indexMap = { + target: 0, + }; + break; + case AccessAction.WRITE: + indexMap = { + target: 0, + property: 1, + value: 2, + receiver: 3, + }; + break; + case AccessAction.META: + indexMap = { + target: 0, + prototype: 1, + }; + break; + default: + throw new TypeError("不支持的访问操作"); + } + + for (const key in indexMap) + nameds[key] = args[indexMap[key]]; + + Object.freeze(indexMap); + Object.freeze(nameds); + + const monitorMap = DomainMonitors.#getMonitorsBy(sourceDomain, targetDomain, action); + const result = { + preventDefault: false, + stopPropagation: false, + returnValue: undefined, + }; + + if (!monitorMap || monitorMap.size == 0) + return result; + + const access = { + domain: targetDomain, + action, + }; + + const control = Object.freeze({ + preventDefault() { + result.preventDefault = true; + }, + stopPropagation() { + result.stopPropagation = true; + }, + overrideParameter(name, value) { + if (!(name in indexMap)) + throw new TypeError(`参数 ${name} 没有找到`); + + args[indexMap[name]] = value; + }, + setReturnValue(value) { + result.returnValue = value; + }, + }); + + for (const monitor of monitorMap) { + Monitor[SandboxExposer2] + (SandboxSignal_DiapatchMonitor, monitor, access, nameds, control); + + if (result.stopPropagation) + break; + } + + return result; + } + + /** + * ```plain + * 安装一个 Monitor 监控 + * ``` + * + * @param {Monitor} monitor + */ + static installMonitor(monitor) { + const domain = monitor.domain; + let instance = DomainMonitors.#domainMonitors.get(domain); + + if (!instance) + DomainMonitors.#domainMonitors + .set(domain, instance = new DomainMonitors()); + + DomainMonitors.#installMonitor(instance, monitor); + } + + /** + * ```plain + * 卸载一个 Monitor 监控 + * ``` + * + * @param {Monitor} monitor + */ + static uninstallMonitor(monitor) { + const domain = monitor.domain; + const instance = DomainMonitors.#domainMonitors.get(domain); + + if (instance) + DomainMonitors.#uninstallMonitor(instance, monitor); + } +} + +/** + * ```plain + * 提供封送对象的行为监控 + * + * 可以对具体的行为、访问的属性进行监控并更改行为 + * + * 例如监听 dummy 这个对象的 value 属性在运行域 domain 的修改行为: + * ``` + * ```javascript + * const monitor = new Monitor(); + * monitor.allow(domain); // 指定监听 domain 运行域 + * monitor.action(AccessAction.WRITE); // 指定监听 Reflect.set 行为 + * monitor.require("target", dummy); // 指定监听 dummy 对象 + * monitor.require("property", "value"); // 指定监听 value 属性 + * monitor.filter((access, nameds) => nameds.value >= 0); // 过滤掉大于等于 0 的修改 + * monitor.then((access, nameds, control) => { + * control.overrideParameter("value", 0); // 将要修改的新值改回 0 + * }); + * monitor.start(); // 启动Monitor + * ``` + */ +class Monitor { + /** @type {Set} */ + static #monitorSet = new Set(); + + /** @type {Domain} */ + #domain; + /** @type {Set?} */ + #allowDomains = null; + /** @type {Set?} */ + #disallowDomains = null; + /** @type {Set} */ + #actions = new Set(); + /** @type {Object} */ + #checkInfo = {}; + /** @type {Function?} */ + #filter = null; + /** @type {Function?} */ + #handler = null; + + constructor() { + this.#domain = Domain.current; + } + + /** + * ```plain + * 检查当前是否是 Monitor 所属的运行域 + * ``` + * + * @param {Monitor} thiz + */ + static #assertOperator = function (thiz) { + if (thiz.#domain !== Domain.current) + throw new Error("当前不是 Monitor 所属的运行域"); + } + + /** + * ```plain + * 获取 Monitor 所属的运行域 + * ``` + */ + get domain() { + return this.#domain; + } + + /** + * ```plain + * 指定 Monitor 可以监听的运行域 + * 默认监听封送到的所有运行域 + * ``` + * + * @param {...Domain} domains + * @returns {this} + */ + allow(...domains) { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (!domains.length) + throw new TypeError("运行域至少要有一个"); + + for (const domain of domains) { + if (!(domain instanceof Domain)) + throw new TypeError("无效的运行域"); + if (domain === this.#domain) + throw new TypeError("Monitor 不能监听自己"); + } + + if (this.#allowDomains) { + for (const domain of domains) + this.#allowDomains.add(domain); + } else if (this.#disallowDomains) { + for (const domain of domains) + this.#disallowDomains.delete(domain); + } else + this.#allowDomains = new Set(domains); + + return this; + } + + /** + * ```plain + * 指定 Monitor 不可监听的运行域 + * 默认监听封送到的所有运行域 + * ``` + * + * @param {...Domain} domains + * @returns {this} + */ + disallow(...domains) { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (!domains.length) + throw new TypeError("运行域至少要有一个"); + + for (const domain of domains) + if (!(domain instanceof Domain)) + throw new TypeError("无效的运行域"); + + if (this.#disallowDomains) { + for (const domain of domains) + this.#disallowDomains.add(domain); + } else if (this.#allowDomains) { + for (const domain of domains) + this.#allowDomains.delete(domain); + } else + this.#disallowDomains = new Set(domains); + + return this; + } + + /** + * ```plain + * 指定 Monitor 监听的访问动作 + * ``` + * + * @param {...number} action + * @returns {this} + */ + action(...action) { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (action.length == 0 + || !action.every(AccessAction.isAccessAction)) + throw new TypeError("无效的访问动作"); + + for (const item of action) + this.#actions.add(item); + + return this; + } + + /** + * + * @typedef {"target" | "thisArg" | "arguments" | "newTarget" | "property" | "descriptor" | "receiver" | "prototype" | "value"} PropertyKey + * + * @typedef {{ + * domain: Domain, + * action: number, + * }} Access + * + * @typedef {{ + * target: Object, + * thisArg?: Object, + * arguments?: Array, + * newTarget?: Function, + * property?: string | symbol, + * descriptor?: { + * value?: any, + * writable?: boolean, + * get?: () => any, + * set?: (value: any) => void, + * enumerable?: boolean, + * configurable?: boolean, + * }, + * receiver?: Object, + * prototype?: Object, + * value?: any, + * }} Nameds + * + * @typedef {{ + * preventDefault: () => void, + * stopPropagation: () => void, + * overrideParameter: (name: PropertyKey, value: any) => void, + * setReturnValue: (value: any) => void, + * }} Control + * + */ + + /** + * ```plain + * 指定 Monitor 监听的命名参数 + * + * 命名参数可能如下: + * target: 监听的对象,访问动作:所有 + * thisArg: 调用的this对象,访问动作:CALL + * arguments: 调用的参数,访问动作:CALL, NEW + * newTarget: 构造的new.target,访问动作:NEW + * property: 访问的属性,访问动作:DEFINE, DELETE, DESCRIBE, EXISTS, READ, WRITE + * descriptor: 定义的属性描述符,访问动作:DEFINE + * receiver: 设置或读取的this对象,访问动作:READ, WRITE + * prototype: 定义的原型,访问动作:META + * value: 设置的新值,访问动作:WRITE + * ``` + * + * @param {PropertyKey} name 命名参数名称 + * @param {...any} values 命名参数可能的值 + * @returns {this} + */ + require(name, ...values) { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (typeof name != "string") + throw new TypeError("无效的检查名称"); + if (!values.length) + return this; + + let info = this.#checkInfo[name]; + + if (!info) + info = this.#checkInfo[name] = new Set(); + + for (const value of values) + info.add(value); + + return this; + } + + /** + * ```plain + * 指定 Monitor 监听的过滤器 + * + * 回调参数 nameds 是一个对象,包含了 Monitor 监听的命名参数 + * ``` + * + * @param {(access: Access, nameds: Nameds) => boolean} filter 要指定的过滤器 + * @returns {this} + */ + filter(filter) { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (typeof filter != "function") + throw new TypeError("无效的过滤器"); + + this.#filter = filter; + return this; + } + + /** + * ```plain + * 指定 Monitor 监听的回调函数 + * + * 回调参数 nameds 是一个对象,包含了 Monitor 监听的命名参数 + * 回调参数 control 是一个对象,提供本次监听的控制函数 + * control.preventDefault(value) 阻止默认的行为,并将设定的返回值作为本次代理访问的返回值 + * control.stopPropagation() 阻断后续的监听器,但不会阻止默认行为 + * control.overrideParameter(name, value) 覆盖本次监听的命名参数 + * control.setReturnValue(value) 设置本次代理访问的返回值,可以覆盖之前监听器设置的返回值 + * ``` + * + * @param {(access: Access, nameds: Nameds, control: Control) => void} handler + * @returns {this} + */ + then(handler) { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (typeof handler != "function") + throw new TypeError("无效的回调"); + + this.#handler = handler; + return this; + } + + /** + * ```plain + * 判断 Monitor 是否已经启动 + * ``` + * + * @type {boolean} + */ + get isStarted() { + return Monitor.#monitorSet.has(this); + } + + /** + * ```plain + * 启动 Monitor + * ``` + */ + start() { + Monitor.#assertOperator(this); + + if (this.isStarted) + throw new Error("Monitor 已经启动"); + if (typeof this.#handler != "function") + throw new Error("Monitor 未指定回调函数"); + + Monitor.#monitorSet.add(this); + DomainMonitors.installMonitor(this); + } + + /** + * ```plain + * 停止 Monitor + * ``` + */ + stop() { + Monitor.#assertOperator(this); + + if (!this.isStarted) + throw new Error("Monitor 还未启动"); + + DomainMonitors.uninstallMonitor(this); + Monitor.#monitorSet.delete(this); + } + + /** + * ```plain + * 向外暴露 Monitor 监听的相关数据 + * ``` + * + * @param {Monitor} thiz + */ + static #exposeInfo = function (thiz) { + return [ + thiz.#actions, + thiz.#allowDomains, + thiz.#disallowDomains + ]; + } + + /** + * ```plain + * 检查 Monitor 监听的命名参数是否符合要求 + * ``` + * + * @param {Object} nameds + * @param {Object} checkInfo + */ + static #check = function (nameds, checkInfo) { + for (const [key, value] of Object.entries(nameds)) { + if (key in checkInfo) { + if (!checkInfo[key].has(value)) + return false; + } + } + + return true; + } + + /** + * ```plain + * 处理 Monitor 监听事件 + * ``` + * + * @param {Monitor} thiz + * @param {number} access + * @param {Nameds} nameds + * @param {Control} control + */ + static #handle = function (thiz, access, nameds, control) { + if (!Monitor.#check(nameds, thiz.#checkInfo)) + return; + + const filter = thiz.#filter; + if (typeof filter === 'function' && !filter(access, nameds)) + return; + + if (typeof thiz.#handler !== 'function') + throw new TypeError("Monitor 未指定回调函数"); + + thiz.#handler(access, nameds, control); + } + + /** + * @param {Symbol} signal + * @param {...any} args + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_DiapatchMonitor: + // @ts-ignore + return Monitor.#handle(...args); + case SandboxSignal_ListMonitor: + return Monitor.#monitorSet; + case SandboxSignal_ExposeInfo: + // @ts-ignore + return Monitor.#exposeInfo(...args); + } + } +} + +/** + * ```plain + * 提供运行域之间的对象封送 + * ``` + */ +class Marshal { + static #revertTarget = Symbol("Marshal.revertTarget"); + static #sourceDomain = Symbol("Marshal.sourceDomain"); + + static #marshalRules = new WeakMap(); + static #marshalledProxies = new WeakSet(); + + constructor() { + throw new TypeError("Marshal 类无法被构造"); + } + + /** + * ```plain + * 判断是否应该封送 + * ``` + * + * @param {any} obj + * @returns {boolean} + */ + static #shouldMarshal = function (obj) { + if (obj === Marshal + || obj === Rule + || obj === AccessAction + || obj === Domain + || obj === Sandbox + || obj instanceof Domain) + return false; + + return true; + } + + /** + * ```plain + * 判断是否禁止封送 + * ``` + * + * @param {any} obj + * @returns {boolean} + */ + static #strictMarshal = function (obj) { + return obj instanceof Sandbox + || obj instanceof Rule + || obj instanceof Monitor; + } + + /** + * ```plain + * 拆除封送代理 + * ``` + * + * @typedef {[ + * Domain, + * Object, + * ]} Reverted + * + * @param {any} proxy + * @returns {Reverted} + */ + static #revertProxy = function (proxy) { + return [ + proxy[Marshal.#sourceDomain], + proxy[Marshal.#revertTarget], + ]; + } + + /** + * ```plain + * 检查封送缓存 + * ``` + * + * @param {Object} obj + * @param {Domain} domain + * @returns {Object?} + */ + static #cacheProxy = function (obj, domain) { + return domain[SandboxExposer] + (SandboxSignal_GetMarshalledProxy, obj); + } + + /** + * ```plain + * 获取指定对象的封送规则引用 + * ``` + * + * @param {Object} obj + * @returns {{rule: Rule}} + */ + static #ensureRuleRef = function (obj) { + let rule = Marshal.#marshalRules.get(obj); + + if (!rule) + Marshal.#marshalRules.set(obj, rule = { rule: null }); + + return rule; + } + + /** + * ```plain + * 判断某个对象是否指定了封送规则 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + static hasRule(obj) { + return Marshal.#marshalRules.has(obj); + } + + /** + * ```plain + * 指定某个对象的封送规则 + * ``` + * + * @param {Object} obj + * @param {Rule} rule + */ + static setRule(obj, rule) { + if (Marshal.#marshalledProxies.has(obj)) + throw new ReferenceError("无法为封送对象设置封送规则"); + + const ref = Marshal.#ensureRuleRef(obj); + + if (ref.rule) + throw new ReferenceError("对象的封送规则已经被设置"); + + ref.rule = rule; + } + + /** + * ```plain + * 判断某个对象是否是其他运行域被封送的对象 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + static isMarshalled(obj) { + return Marshal.#marshalledProxies.has(obj); + } + + /** + * ```plain + * 获取封送对象的源运行域 + * ``` + * + * @param {Object} obj + * @returns {Domain?} + */ + static getMarshalledDomain(obj) { + if (!Marshal.#marshalledProxies.has(obj)) + return null; + + const [domain,] = Marshal.#revertProxy(obj); + return domain; + } + + /** + * ```plain + * 陷入某个运行域并执行代码 + * ``` + * + * @param {Domain} domain + * @param {() => any} action + */ + static #trapDomain = function (domain, action) { + const prevDomain = Domain.current; + + // 如果可能,应该尽量避免陷入相同运行域 + if (prevDomain === domain) + return console.warn("trapDomain 处于相同 domain"), action(); + + Domain[SandboxExposer2](SandboxSignal_EnterDomain, domain); + + try { + return action(); + } catch (e) { + throw Marshal.#marshal(e, prevDomain); + } finally { + Domain[SandboxExposer2](SandboxSignal_ExitDomain); + } + } + + /** + * ```plain + * 封送数组 + * ``` + * + * @param {Array} array + * @param {Domain} targetDomain + * @returns {Array} + */ + static #marshalArray = function (array, targetDomain) { + if (isPrimitive(array)) + return array; + + const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); + const newArray = new window.Array(array.length); + + for (let i = 0; i < newArray.length; i++) + newArray[i] = Marshal.#marshal(array[i], targetDomain); + + return newArray; + } + + /** + * ```plain + * 封送对象 + * ``` + * + * @param {Object} object + * @param {Domain} targetDomain + * @returns {Object} + */ + static #marshalObject = function (object, targetDomain) { + if (isPrimitive(object)) + return object; + + const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); + const newObject = new window.Object(); + + for (const key of Reflect.ownKeys(object)) + newObject[key] = Marshal.#marshal(object[key], targetDomain); + + return newObject; + } + + /** + * @param {Object} obj + * @param {Domain} targetDomain + * @returns {Object} + */ + static #marshal = function (obj, targetDomain) { + // 基元封送 + if (isPrimitive(obj)) + return obj; + + // 尝试拆除代理 + let [sourceDomain, target] = + Marshal.#marshalledProxies.has(obj) + ? Marshal.#revertProxy(obj) + : [Domain.current, obj]; + + // target: 确保拆除了封送代理的对象 + // sourceDomain: target所属的运行域 + // targetDomain: 要封送到的运行域 + + if (sourceDomain === targetDomain) + return target; + + if (Marshal.#strictMarshal(target) + || sourceDomain.isUnsafe(target)) + throw new TypeError("对象无法封送"); + if (!Marshal.#shouldMarshal(target)) + return target; + + // 全局变量封送 + const mapped = Globals.mapTo(target, sourceDomain, targetDomain); + + if (mapped != null) + return mapped; + + // 错误封送 + if (sourceDomain.isError(target)) { + const errorCtor = target.constructor; + const mappedCtor = Globals.mapTo(errorCtor, sourceDomain, targetDomain); + + if (mappedCtor) { + const newError = new mappedCtor(); + Object.defineProperties(newError, + Object.getOwnPropertyDescriptors(target)); + return newError; + } + } + + // 检查封送权限 + const ruleRef = Marshal.#ensureRuleRef(target); // 为加快访问速度使用了引用 + const rule = ruleRef.rule; + + if (rule && !rule.canMarshalTo(targetDomain)) + throw new TypeError("无法将对象封送到目标运行域"); + + // 检查封送缓存 + const cached = Marshal.#cacheProxy(target, targetDomain); + + if (cached) + return cached; + + // 创建封送代理 + const proxy = new Proxy(target, { + apply(target, thisArg, argArray) { + const defaultApply = () => { + const marshalledThis = Marshal.#marshal(thisArg, sourceDomain); + const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.CALL, + target, marshalledThis, marshalledArgs)) + throw new ReferenceError("Access denied"); + + const args = [target, marshalledThis, marshalledArgs]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.CALL, args); + + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // @ts-ignore + const result = Reflect.apply(...args); + return Marshal.#marshal(result, targetDomain); + }); + }; + + // 此处处理异步封送 + // 如果没有逃逸情况,此处代表着当前是异步调用 + if (Domain.current !== targetDomain) + return Marshal.#trapDomain(targetDomain, defaultApply); + + return defaultApply(); + }, + construct(target, argArray, newTarget) { + const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); + const marshalledNewTarget = Marshal.#marshal(newTarget, sourceDomain); + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.NEW, + target, argArray, newTarget)) + throw new ReferenceError("Access denied"); + + const args = [target, marshalledArgs, marshalledNewTarget]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.NEW, args); + + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // @ts-ignore + const result = Reflect.construct(...args); + return Marshal.#marshal(result, targetDomain); + }); + }, + defineProperty(target, property, attributes) { + let getter = attributes.get; + let setter = attributes.set; + + if (typeof getter == "function") + getter = Marshal.#marshal(getter, sourceDomain); + if (typeof setter == "function") + setter = Marshal.#marshal(setter, sourceDomain); + + const window = sourceDomain[SandboxExposer](SandboxSignal_GetWindow); + const descriptor = new window.Object(); + + if ("value" in attributes) + descriptor.value = Marshal.#marshal(attributes.value, sourceDomain); + if ("get" in attributes) + descriptor.get = getter; + if ("set" in attributes) + descriptor.set = setter; + if ("writable" in attributes) + descriptor.writable = !!attributes.writable; + if ("enumerable" in attributes) + descriptor.enumerable = !!attributes.enumerable; + if ("configurable" in attributes) + descriptor.configurable = !!attributes.configurable; + + const isSourceDomain = sourceDomain === Domain.current; + const domainTrapAction = () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.DEFINE, + target, property, descriptor)) + throw new ReferenceError("Access denied"); + + const args = [target, property, descriptor]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.DEFINE, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.defineProperty(...args); + }; + + return isSourceDomain + ? domainTrapAction() + : Marshal.#trapDomain(sourceDomain, domainTrapAction); + }, + deleteProperty(target, p) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.DELETE, target, p)) + throw new ReferenceError("Access denied"); + + const args = [target, p]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.DELETE, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.deleteProperty(...args); + }); + }, + get(target, p, receiver) { + // 因为 get 的东西最多,所以对此追加注释 + // 其他的拦截器都是与 get 类似 + + // 向外暴露封送 + switch (p) { + case Marshal.#revertTarget: + return target; + case Marshal.#sourceDomain: + return sourceDomain; + } + + // 默认封送 + const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); + + // 陷入源运行域执行 + return Marshal.#trapDomain(sourceDomain, () => { + // 获取封送规则并检查 + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.READ, + target, p, marshalledReceiver)) + throw new ReferenceError("Access denied"); + + // 通知 Monitor + const args = [target, p, marshalledReceiver]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.READ, args); + + // 处理 Monitor 的结果 + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // 执行默认流程 + // @ts-ignore + const result = Reflect.get(...args); + return Marshal.#marshal(result, targetDomain); + }); + }, + getOwnPropertyDescriptor(target, p) { + const isSourceDomain = Domain.current === sourceDomain; + + const domainTrapAction = () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.DESCRIBE, target, p)) + throw new ReferenceError("Access denied"); + + const args = [target, p]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.DESCRIBE, args); + + if (dispatched.preventDefault) + return dispatched.returnValue; + + // @ts-ignore + return Reflect.getOwnPropertyDescriptor(...args); + }; + + if (isSourceDomain) + return domainTrapAction(); + + const descriptor = Marshal.#trapDomain(sourceDomain, domainTrapAction); + return Marshal.#marshalObject(descriptor, targetDomain); + }, + getPrototypeOf(target) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.TRACE, target)) + throw new ReferenceError("Access denied"); + + const args = [target]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.TRACE, args); + + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // @ts-ignore + const result = Reflect.getPrototypeOf(...args); + const marshalledResult = Marshal.#marshal(result, targetDomain); + + if (Marshal.#marshalledProxies.has(marshalledResult)) + return null; // 没有实装hasInstance喵,只能折中处理喵 + + return marshalledResult; + }); + }, + has(target, p) { + const isSourceDomain = Domain.current === sourceDomain; + const domainTrapAction = () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.EXISTS, target, p)) + throw new ReferenceError("Access denied"); + + const args = [target, p]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.EXISTS, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.has(...args); + }; + + if (isSourceDomain) + return domainTrapAction(); + + return Marshal.#trapDomain(sourceDomain, domainTrapAction); + }, + isExtensible(target) { + return Reflect.isExtensible(target); + }, + ownKeys(target) { + const keys = Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.LIST, target)) + throw new ReferenceError("Access denied"); + + const args = [target]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.LIST, args); + + if (dispatched.preventDefault) + return dispatched.returnValue; + + // @ts-ignore + return Reflect.ownKeys(...args); + }); + + return Marshal.#marshalArray(keys, targetDomain); + }, + preventExtensions(target) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.SEAL, target)) + throw new ReferenceError("Access denied"); + + const args = [target]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.SEAL, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.preventExtensions(...args); + }); + }, + set(target, p, newValue, receiver) { + const marshalledNewValue = Marshal.#marshal(newValue, sourceDomain); + const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.WRITE, + target, p, marshalledNewValue, marshalledReceiver)) + throw new ReferenceError("Access denied"); + + const args = [target, p, marshalledNewValue, marshalledReceiver]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.WRITE, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.set(...args); + }); + }, + setPrototypeOf(target, v) { + const marshalledV = Marshal.#marshal(v, sourceDomain); + + if (Marshal.#marshalledProxies.has(marshalledV)) + return false; // 没有实装hasInstance喵,只能折中处理喵 + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.META, target, marshalledV)) + throw new ReferenceError("Access denied"); + + const args = [target, marshalledV]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.META, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.setPrototypeOf(...args); + }); + }, + }); + + Marshal.#marshalledProxies.add(proxy); + targetDomain[SandboxExposer] + (SandboxSignal_SetMarshalledProxy, target, proxy); + return proxy; + } + + /** + * @param {Symbol} signal + * @param {...any} args + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_Marshal: + // @ts-ignore + return Marshal.#marshal(...args); + case SandboxSignal_MarshalArray: + // @ts-ignore + return Marshal.#marshalArray(...args); + case SandboxSignal_UnpackProxy: + // @ts-ignore + return Marshal.#revertProxy(...args); + case SandboxSignal_TrapDomain: + // @ts-ignore + return Marshal.#trapDomain(...args); + } + } +} + +/** + * ```plain + * 运行域对象 + * + * 提供运行域的创建以及周期管理 + * ``` + */ +class Domain { + static #hasInstance = Object[Symbol.hasInstance]; + + /** @type {Array} */ + static #domainStack = []; + /** @type {Domain} */ + static #currentDomain; + /** @type {Domain} */ + static #topDomain; + + /** @type {Array>} */ + static #domainLinks = []; + + #domainName = "top"; + + /** @type {typeof Object} */ + #domainObject; + /** @type {typeof Error} */ + #domainError; + /** @type {typeof Promise} */ + #domainPromise; + /** @type {typeof HTMLIFrameElement} */ + #domainIFrame; + /** @type {Navigator} */ + #domainNavigator; + /** @type {ServiceWorker} */ + #domainServiceWorker; + /** @type {Location} */ + #domainLocation; + /** @type {Function} */ + #domainOpen; + /** @type {Function} */ + #domainClose; + /** @type {typeof Worker} */ + #domainWorker; + /** @type {Object} */ + #domainCSS; + /** @type {typeof SharedWorker} */ + #domainSharedWorker; + /** @type {Window} */ + #domainRoot; + /** @type {WeakMap} */ + #marshalledCached = new WeakMap(); + + /** @type {(array: any) => boolean} */ + #domainIsArray; + + /** + * ```plain + * 创建运行域 + * + * 一般不直接使用, + * 请考虑使用直接创建沙盒 + * ``` + */ + constructor() { + // @ts-ignore + let global = window.replacedGlobal || window; + + if (Domain.#currentDomain) { + // @ts-ignore + if (!window.createRealms) + throw new ReferenceError("Sandbox 载入时处于不安全运行域"); + + // @ts-ignore + global = createRealms(); + this.#domainName = Math.random().toString(36).slice(2); + } else + NativeWrapper.initTopDomain(global); + + this.#domainRoot = global; + this.#domainObject = global.Object; + this.#domainError = global.Error; + this.#domainPromise = global.Promise; + this.#domainIFrame = global.HTMLIFrameElement; + this.#domainNavigator = global.navigator; + this.#domainServiceWorker = global.navigator.serviceWorker; + this.#domainLocation = global.location; + this.#domainOpen = global.open; + this.#domainClose = global.close; + this.#domainWorker = global.Worker; + this.#domainCSS = global.CSS; + this.#domainSharedWorker = global.SharedWorker; + + this.#domainIsArray = global.Array.isArray; + + NativeWrapper.wrapInDomains(global); + Globals.ensureDomainGlobals(this); + DomainMonitors.handleNewDomain(this); + Domain.#domainLinks.push(new WeakRef(this)); + sealObjectTree(this); + + global.Array.isArray = new global + .Function("domain", "array", "return this(domain, array)") + .bind(Domain.#isArray, this); + } + + // 实装这个要代理Object喵 + // static #hasInstanceMarshalled = function (obj) { + // if (Marshal.isMarshalled(obj)) + // [, obj] = Marshal[SandboxExposer2] + // (SandboxSignal_UnpackProxy, obj); + + // return Domain.#hasInstance.call(this, obj); + // } + + // 效率影响不确定,暂不实装 + // static #marshalledThen = function (onfulfilled, onrejected) { + // if (Marshal.isMarshalled(this)) { + // const [domain, promise] = Marshal[SandboxExposer2] + // (SandboxSignal_UnpackProxy, this); + + // const marshaller = value => { + // return this(trapMarshal(domain, Domain.current, value)); + // }; + + // return trapMarshal(domain, Domain.current, promise.then( + // marshaller.bind(onfulfilled), + // marshaller.bind(onrejected) + // )); + // } + + // return [[DefaultThen]].call(this, onfulfilled, onrejected); + // } + + /** + * ```plain + * 检查对象是否来自于当前的运行域 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + isFrom(obj) { + if (Marshal.isMarshalled(obj)) { + const [domain,] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, obj); + return domain === this; + } + + return Domain.#hasInstance + .call(this.#domainObject, obj); + } + + /** + * ```plain + * 检查对象是否来自于当前的运行域的Promise + * ``` + * + * @param {Promise} promise + * @returns {boolean} + */ + isPromise(promise) { + if (Marshal.isMarshalled(promise)) + [, promise] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, promise); + + return Domain.#hasInstance + .call(this.#domainPromise, promise); + } + + /** + * ```plain + * 检查对象是否来自于当前的运行域的Error + * ``` + * + * @param {Error} error + * @returns {boolean} + */ + isError(error) { + if (Marshal.isMarshalled(error)) + [, error] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, error); + + return Domain.#hasInstance + .call(this.#domainError, error); + } + + /** + * ```plain + * 检查对象是否来自于当前的运行域的危险对象 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + isUnsafe(obj) { + if (Marshal.isMarshalled(obj)) + [, obj] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, obj); + + if (obj === this.#domainRoot) + return true; + if (Domain.#hasInstance.call(this.#domainIFrame, obj)) + return true; + if (obj === this.#domainNavigator) + return true; + if (obj === this.#domainServiceWorker) + return true; + if (obj === this.#domainLocation) + return true; + if (obj === this.#domainOpen) + return true; + if (obj === this.#domainClose) + return true; + if (Domain.#hasInstance.call(this.#domainWorker, obj)) + return true; + if (obj === this.#domainCSS) + return true; + if (Domain.#hasInstance.call(this.#domainSharedWorker, obj)) + return true; + + return false; + } + + toString() { + return `[Domain ${this.#domainName}]`; + } + + /** + * ```plain + * 替代 Array.isArray 并暴露给外部 + * 确保 Array.isArray 对代理数组放宽 + * ``` + * + * @param {any} array + * @returns {boolean} + */ + static #isArray = function (domain, array) { + if (Marshal.isMarshalled(array)) + [domain, array] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, array); + + return domain.#domainIsArray(array); + }; + + /** + * @param {Domain} domain + */ + static #enterDomain = function (domain) { + Domain.#domainStack.push(Domain.#currentDomain); + Domain.#currentDomain = domain; + } + + static #exitDomain = function () { + if (Domain.#domainStack.length < 1) + throw new ReferenceError("无法弹出更多的运行域"); + + // @ts-ignore + Domain.#currentDomain = Domain.#domainStack.pop(); + } + + /** + * @returns {Array} + */ + static #listDomain = function () { + const links = Domain.#domainLinks; + const list = []; + + for (let i = links.length - 1; i >= 0; i--) { + const link = links[i].deref(); + + if (!link) + links.splice(i, 1); + + list.push(link); + } + + // @ts-ignore + return list; + } + + /** + * ```plain + * 获取当前运行域 + * ``` + * + * @type {Domain} + */ + static get current() { + return Domain.#currentDomain; + } + + /** + * ```plain + * 获取调用链中上一个运行域 + * ``` + * + * @type {Domain?} + */ + static get caller() { + for (let i = Domain.#domainStack.length; i >= 0; i--) { + const domain = Domain.#domainStack[i]; + + if (domain !== Domain.#currentDomain) + return domain; + } + + return null; + } + + /** + * ```plain + * 获取顶级运行域 + * ``` + * + * @type {Domain} + */ + static get topDomain() { + return Domain.#topDomain; + } + + /** + * ```plain + * 检查当前的调用是否来自可信的运行域 + * + * 如果检查顶级运行域,则要求没有进行任何其他运行域的陷入 + * 如果检查非顶级运行域,则要求只有顶级运行域与给定运行域的陷入 + * ``` + * + * @param {Domain} domain + */ + static isBelievable(domain) { + if (domain === Domain.#topDomain) + return !Domain.#domainStack.length; + + return Domain.#domainStack.concat([Domain.#currentDomain]) + .every(d => d === Domain.#topDomain || d === domain); + } + + /** + * @param {Symbol} signal + * @param {...any} args + */ + [SandboxExposer](signal, ...args) { + switch (signal) { + case SandboxSignal_GetMarshalledProxy: + // @ts-ignore + return this.#marshalledCached.get(...args); + case SandboxSignal_SetMarshalledProxy: + // @ts-ignore + return void this.#marshalledCached.set(...args); + case SandboxSignal_GetWindow: + return this.#domainRoot; + case SandboxSignal_GetPromise: + return this.#domainPromise; + } + } + + /** + * @param {Symbol} signal + * @param {...any} args + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_InitDomain: + if (Domain.#currentDomain) + throw new TypeError("顶级运行域已经被初始化"); + + Domain.#currentDomain = new Domain(); + Domain.#topDomain = Domain.#currentDomain; + return; + case SandboxSignal_EnterDomain: + // @ts-ignore + return Domain.#enterDomain(...args); + case SandboxSignal_ExitDomain: + return Domain.#exitDomain(); + case SandboxSignal_ListDomain: + return Domain.#listDomain(); + case SandboxSignal_IsArray: + // @ts-ignore + return Domain.#isArray(...args); + } + } +} + +/** + * ```plain + * 将对象从源运行域封送到目标运行域 + * ``` + * + * @param {Domain} srcDomain + * @param {Domain} dstDomain + * @param {any} obj + * @returns + */ +function trapMarshal(srcDomain, dstDomain, obj) { + if (srcDomain === dstDomain) + return obj; + + const domain = Domain.current; + + if (domain === srcDomain) + return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); + + return Marshal[SandboxExposer2](SandboxSignal_TrapDomain, srcDomain, () => { + return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); + }); +} + +/** + * ```plain + * 向JavaScript提供类似于Python的exec的自带上下文的eval功能 + * 同时自动排除原有作用域以沙盒方式来执行部分代码 + * ``` + */ +class Sandbox { + /** @type {WeakMap} */ + static #domainMap = new WeakMap(); + /** @type {Array} */ + static #executingScope = []; + + /** @type {Object} */ + #scope; + /** @type {Array} */ + #scopeStack = []; + + /** @type {Domain} */ + #sourceDomain; + /** @type {Domain} */ + #domain; + /** @type {Window} */ + #domainWindow; + /** @type {Document?} */ + #domainDocument; + /** @type {typeof Object} */ + #domainObject = Object; + /** @type {typeof Function} */ + #domainFunction = Function; + + /** + * ```plain + * 当在当前scope中访问不到变量时, + * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 + * 去读取部分非内建的全局变量(仅读取) + * + * 此开关有风险,请谨慎使用 + * ``` + * + * @type {boolean} + */ + #freeAccess = false; + + /** + * 创建一个新的沙盒 + */ + constructor() { + this.#sourceDomain = Domain.current; + this.#domain = new Domain(); + this.#domainWindow = this.#domain[SandboxExposer](SandboxSignal_GetWindow); + this.#domainDocument = null; // 默认不开放DOM,而且我们也缺少BrowserContext + this.#domainObject = this.#domainWindow.Object; + this.#domainFunction = this.#domainWindow.Function; + Sandbox.#domainMap.set(this.#domain, this); + Sandbox.#createScope(this); + Sandbox.#initDomainFunctions(this, this.#domainWindow); + } + + /** + * ```plain + * 检查沙盒操作运行域 + * ``` + * + * @param {Sandbox} thiz + */ + static #assertOperator = function (thiz) { + if (thiz.#sourceDomain !== Domain.current) + throw new TypeError("当前运行域不是沙盒的所有运行域"); + } + + /** + * ```plain + * 封装沙盒的 Function 函数 + * ``` + * + * @param {Sandbox} thiz + * @param {Window} global + */ + static #initDomainFunctions = function (thiz, global) { + /** @type {typeof Function} */ + const defaultFunction = global.Function; + /** @type {typeof Function} */ + const defaultGeneratorFunction = global.eval("(function*(){}).constructor"); + /** @type {typeof Function} */ + const defaultAsyncFunction = global.eval("(async function(){}).constructor"); + /** @type {typeof Function} */ + const defaultAsyncGeneratorFunction = global.eval("(async function*(){}).constructor"); + + /** + * @param {typeof Function} target + * @param {Array} argArray + * @returns + */ + function functionCtor(target, argArray) { + if (!argArray.length) + return new target(); + + argArray = Array.from(argArray); + + const code = argArray.slice(-1)[0]; + const params = argArray.slice(0, -1); + new target(code); // 防止注入 + + const compiled = Sandbox.#compileCore(thiz, code, null, params, true); + compiled[Symbol.toStringTag] = `function (${params.join(", ")}) {\n${code}\n}`; + return compiled; + } + + const handler = { + apply(target, thisArg, argArray) { + return functionCtor(target, argArray); + }, + construct(target, argArray, newTarget) { + return functionCtor(target, argArray); + }, + }; + + function rewriteCtor(prototype, newCtor) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') + || { configurable: true, writable: true, enumerable: false }; + if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); + descriptor.value = newCtor; + Reflect.defineProperty(prototype, 'constructor', descriptor) + } + + rewriteCtor(defaultFunction.prototype, global.Function = new Proxy(defaultFunction, handler)); + rewriteCtor(defaultGeneratorFunction.prototype, new Proxy(defaultGeneratorFunction, handler)); + rewriteCtor(defaultAsyncFunction.prototype, new Proxy(defaultAsyncFunction, handler)); + rewriteCtor(defaultAsyncGeneratorFunction.prototype, new Proxy(defaultAsyncGeneratorFunction, handler)); + } + + /** + * ```plain + * 获取当前的scope + * ``` + * + * @type {Object} + */ + get scope() { + Sandbox.#assertOperator(this); + return trapMarshal(this.#domain, Domain.current, this.#scope); + } + + /** + * ```plain + * 获取当前沙盒内的运行域 + * ``` + * + * @type {Domain} + */ + get domain() { + return this.#domain; + } + + /** + * ```plain + * 获取当前沙盒内的document对象 + * ``` + * + * @type {Document} + */ + get document() { + Sandbox.#assertOperator(this); + return trapMarshal(this.#domain, Domain.current, this.#domainDocument); + } + + /** + * ```plain + * 设置当前沙盒内的document对象 + * ``` + * + * @type {Document} + */ + set document(value) { + Sandbox.#assertOperator(this); + this.#domainDocument = Marshal[SandboxExposer2] + (SandboxSignal_Marshal, value, this.#domain); + } + + /** + * ```plain + * 当在当前scope中访问不到变量时, + * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 + * 去读取部分非内建的全局变量(仅读取) + * + * 此开关有风险,请谨慎使用 + * ``` + * + * @type {boolean} + */ + get freeAccess() { + Sandbox.#assertOperator(this); + return this.#freeAccess; + } + + /** + * ```plain + * 当在当前scope中访问不到变量时, + * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 + * 去读取部分非内建的全局变量(仅读取) + * + * 此开关有风险,请谨慎使用 + * ``` + * + * @type {boolean} + */ + set freeAccess(value) { + Sandbox.#assertOperator(this); + this.#freeAccess = !!value; + } + + /** + * ```plain + * 向当前域注入内建对象 + * + * 如果使用在使用了 `initBuiltins` 之后进行 `pushScope`, + * 则将自动继承前面的内建对象,无需再次调用 `initBuiltins` + * ``` + */ + initBuiltins() { + Sandbox.#assertOperator(this); + + /** + * ```plain + * 如果要扩充沙盒的内建函数或类,请在此增加喵 + * ``` + */ + const builtins = { + Object: this.#domainObject, + Function: this.#domainFunction, + Array: this.#domainWindow.Array, + Math: this.#domainWindow.Math, + Date: this.#domainWindow.Date, + String: this.#domainWindow.String, + Number: this.#domainWindow.Number, + Boolean: this.#domainWindow.Boolean, + RegExp: this.#domainWindow.RegExp, + Error: this.#domainWindow.Error, + TypeError: this.#domainWindow.TypeError, + RangeError: this.#domainWindow.RangeError, + SyntaxError: this.#domainWindow.RangeError, + EvalError: this.#domainWindow.EvalError, + ReferenceError: this.#domainWindow.ReferenceError, + Promise: this.#domainWindow.Promise, + Map: this.#domainWindow.Map, + Set: this.#domainWindow.Set, + WeakMap: this.#domainWindow.WeakMap, + WeakSet: this.#domainWindow.WeakSet, + WeakRef: this.#domainWindow.WeakRef, + Symbol: this.#domainWindow.Symbol, + Proxy: this.#domainWindow.Proxy, + Reflect: this.#domainWindow.Reflect, + BigInt: this.#domainWindow.BigInt, + JSON: this.#domainWindow.JSON, + eval: this.#domainWindow.eval, + setTimeout: this.#domainWindow.setTimeout, + clearTimeout: this.#domainWindow.clearTimeout, + setInterval: this.#domainWindow.setInterval, + clearInterval: this.#domainWindow.clearInterval, + setImmediate: this.#domainWindow.setImmediate, + clearImmediate: this.#domainWindow.clearImmediate, + requestAnimationFrame: this.#domainWindow.requestAnimationFrame, + cancelAnimationFrame: this.#domainWindow.cancelAnimationFrame, + requestIdleCallback: this.#domainWindow.requestIdleCallback, + cancelIdleCallback: this.#domainWindow.cancelIdleCallback, + queueMicrotask: this.#domainWindow.queueMicrotask, + MutationObserver: this.#domainWindow.MutationObserver, + alert: this.#domainWindow.alert, + confirm: this.#domainWindow.confirm, + console: this.#domainWindow.console, + parseInt: this.#domainWindow.parseInt, + parseFloat: this.#domainWindow.parseFloat, + isFinite: this.#domainWindow.isFinite, + isNaN: this.#domainWindow.isNaN, + }; + + const hardBuiltins = { + NaN: NaN, + Infinity: Infinity, + undefined: undefined, + }; + + Marshal[SandboxExposer2](SandboxSignal_TrapDomain, this.#domain, () => { + for (const [k, v] of Object.entries(builtins)) { + if (!v) + delete builtins[k]; + if (typeof v == "function" && !("prototype" in v)) + builtins[k] = v.bind(null); + } + }); + + Object.assign(this.#scope, builtins); + + for (const [k, v] of Object.entries(hardBuiltins)) { + Reflect.defineProperty(this.#scope, k, { + value: v, + writable: false, + enumerable: false, + configurable: false, + }); + } + + Reflect.defineProperty(this.#scope, "document", { + get: (function () { + // @ts-ignore + return this.#domainDocument; + }).bind(this), + enumerable: false, + configurable: false, + }); + } + + /** + * ```plain + * 基于当前的scope克隆一个新的scope + * 然后将原本的scope压入栈中 + * ``` + */ + pushScope() { + Sandbox.#assertOperator(this); + this.#scopeStack.push(this.#scope); + Sandbox.#createScope(this); + } + + /** + * ```plain + * 丢弃当前的scope并从栈中弹出原本的scope + * ``` + */ + popScope() { + Sandbox.#assertOperator(this); + + if (!this.#scopeStack) + throw new ReferenceError("没有更多的scope可以弹出"); + + this.#scope = this.#scopeStack.pop(); + } + + /** + * ```plain + * 核心编译函数 + * ``` + * + * @param {Sandbox} thiz 当前沙盒实例 + * @param {string} code 代码字符串 + * @param {Object?} context 额外的执行上下文 + * @param {Array?} paramList 参数名列表,以此来创建可以传递参数的函数 + * @param {boolean?} inheritScope 是否继承当前正在执行的scope而不是当前沙盒的scope + * @param {"exists"|"extend"|"all"} writeContext 当执行的代码尝试为未声明的变量赋值时,应该 根据context与window的变量写入(默认行为)|默认行为并且新的变量写入context|全部写入context + * @returns + */ + static #compileCore = function (thiz, code, context = null, + paramList = null, inheritScope = false, writeContext = 'exists') { + if (typeof code != "string") + throw new TypeError("代码需要是一个字符串"); + + if (isPrimitive(context)) + context = {}; + + // 进行语法检查,防止注入 + new thiz.#domainFunction(code); + + const executingScope = Sandbox.#executingScope[Sandbox.#executingScope.length - 1]; + const scope = inheritScope && executingScope || thiz.#scope; + const contextName = Sandbox.#makeName("__context_", scope); + const argsName = Sandbox.#makeName("__args_", scope); + const parameters = paramList + ? paramList.join(", ") : ""; + const writeContextAction = { exists: 0, extend: 1, all: 2 }[writeContext] || 0; + + let argumentList; + + const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(()=>{"use strict";return(function(${parameters}){\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n}).call(${contextName}.this,...${argsName})})()}}}`); + + const domain = thiz.#domain; + const domainWindow = thiz.#domainWindow; + const marshalledContext = Marshal[SandboxExposer2] + (SandboxSignal_Marshal, context, domain); + + // 构建上下文拦截器 + const intercepter = new Proxy(scope, { + has() { + return true; + }, + get(target, p) { + switch (p) { + case contextName: + return marshalledContext; + case argsName: + return argumentList; + } + + if (p === Symbol.unscopables) + return undefined; + + if (!(p in target)) { + if (thiz.#freeAccess + && !Globals.isBuiltinKey(p)) { + const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); + + if (p in topWindow) + return trapMarshal(Domain.topDomain, domain, topWindow[p]); + } + + throw new domainWindow.ReferenceError(`${String(p)} is not defined`); + } + + return target[p]; + }, + set(target, p, v) { + if (writeContextAction == 2 + || (writeContextAction == 1 && !(p in target))) + return Reflect.set(marshalledContext, p, v); + + return Reflect.set(target, p, v); + }, + }); + + // 构建陷入的沙盒闭包 + // 同时对返回值进行封送 + return ((...args) => { + const prevDomain = Domain.current; + const domainAction = () => { + Sandbox.#executingScope.push(scope); + + try { + argumentList = Marshal[SandboxExposer2] + (SandboxSignal_MarshalArray, args, domain); + const result = raw.call(null, intercepter); + return Marshal[SandboxExposer2] + (SandboxSignal_Marshal, result, prevDomain); + } finally { + Sandbox.#executingScope.pop(); + } + }; + + if (prevDomain === domain) + return domainAction(); + + return Marshal[SandboxExposer2] + (SandboxSignal_TrapDomain, domain, domainAction); + }).bind(null); // 编译函数不应该发送 + } + + /** + * ```plain + * 基于给定的代码与当前的scope来构造一个闭包函数 + * + * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope + * 另外可以通过context.this属性来指定函数的this + * + * 请注意,当沙盒闭包函数构造后,scope将被闭包固定 + * 这意味着pushScope与popScope不会影响到构造好的函数 + * ``` + * + * @param {string} code 沙盒闭包函数的代码 + * @param {Object?} context 临时上下文 + * @returns {(...args: any[]) => any} 构造的沙盒闭包函数 + */ + compile(code, context = null) { + return Sandbox.#compileCore(this, code, context); + } + + /** + * ```plain + * 基于当前的scope在沙盒环境下执行给定的代码 + * + * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope + * 另外可以通过context.this属性来指定函数的this + * ``` + * + * @param {string} code 沙盒闭包函数的代码 + * @param {Object?} context 临时上下文 + * @returns 执行代码的返回值 + */ + exec(code, context = null) { + return this.compile(code, context)(); + } + + /** + * ```plain + * 基于当前的scope在沙盒环境下执行给定的代码 + * + * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope + * 另外可以通过context.this属性来指定函数的this + * + * 与exec的区别在于,此函数可以指定未定义变量赋值行为 + * 当 `readonly` 为false时,不存在的全局变量的赋值行为将被转移到context里面 + * 当 `readonly` 为true(默认)时,任何全局变量的赋值行为将被转移到context里面 + * ``` + * + * @param {string} code 沙盒闭包函数的代码 + * @param {Object?} context 临时上下文(没有给出将自动创建) + * @param {boolean} readonly 是否拦截所有全局变量的赋值 + * @returns {[any, Object]} [执行代码的返回值, 参数context] + */ + exec2(code, context = null, readonly = true) { + if (isPrimitive(context)) + context = {}; + + const compiled = Sandbox.#compileCore(this, code, context, + null, false, readonly ? "all" : "extend"); + return [compiled(), context]; + } + + /** + * ```plain + * 根据运行域获取沙盒对象 + * ``` + * + * @param {Domain} domain + * @returns {Sandbox?} + */ + static from(domain) { + const sandbox = Sandbox.#domainMap.get(domain); + + if (!sandbox) + return null; + + if (sandbox.#sourceDomain !== Domain.current) + throw new TypeError("当前运行域不是沙盒的所有运行域"); + + return sandbox; + } + + static #createScope = function (thiz) { + let baseScope = thiz.#scope; + thiz.#scope = new thiz.#domainObject(); + + Reflect.defineProperty(thiz.#scope, "window", { + get: (function () { + // @ts-ignore + return this; + }).bind(thiz.#scope), + enumerable: false, + configurable: false, + }); + + if (!baseScope) + return; + + const descriptors = Object.getOwnPropertyDescriptors(baseScope); + delete descriptors.window; + Object.defineProperties(thiz.#scope, descriptors); + } + + static #makeName = function (prefix, conflict) { + let builtName; + + do { + builtName = prefix + Math.random().toString(36).slice(2); + } while (builtName in conflict); + + return builtName; + } +} + +function sealClass(clazz) { + sealObjectTree(clazz); + + if (typeof clazz == "function") + sealObjectTree(clazz.prototype); + else if (clazz.constructor) + sealObjectTree(clazz.constructor); +} + +// FREEZE FROM SOUL! +function sealObjectTree(obj) { + // @ts-ignore + sealObject(obj, o => { + if (!Reflect.isExtensible(o)) // 防止1103 + return; + if (o === obj) + return void Object.freeze(o); + + sealObjectTree(o); + }); +} + +function sealObject(obj, freeze = Object.freeze) { + if (isPrimitive(obj)) + return; + + const descriptors = Object.getOwnPropertyDescriptors(obj); + + freeze(obj); + + // 防止通过函数属性传值 + for (const [key, descriptor] of Object.entries(descriptors)) { + if (descriptor.get) + freeze(descriptor.get); + if (descriptor.set) + freeze(descriptor.set); + if (!isPrimitive(descriptor.value)) + freeze(descriptor.value); + } +} + +const SANDBOX_EXPORT = { + AccessAction, + Rule, + Monitor, + Marshal, + Domain, + Sandbox, +}; + +// TODO: 其他扩展的全局变量 + +if (SANDBOX_ENABLED) { + // 确保顶级运行域的原型链不暴露 + if (window.top === window) { + const document = window.document; + const createElement = document.createElement.bind(document); + const appendChild = document.body.appendChild.bind(document.body); + const iframe = createElement("iframe"); + iframe.style.display = "none"; + appendChild(iframe); + + if (!iframe.contentWindow) + throw new ReferenceError("无法载入运行域"); + + Reflect.defineProperty(iframe.contentWindow, "createRealms", { + value() { + const iframe = createElement("iframe"); + iframe.style.display = "none"; + appendChild(iframe); + + const window = iframe.contentWindow; + if (!window) + throw new ReferenceError("顶级域已经被卸载"); + + iframe.remove(); + return window; + }, + }); + + // @ts-ignore + iframe.contentWindow.replacedGlobal = window; + // @ts-ignore + iframe.contentWindow.replacedCI1 = ContextInvoker1; + // @ts-ignore + iframe.contentWindow.replacedCI2 = ContextInvoker2; + // @ts-ignore + iframe.contentWindow.replacedCIC = ContextInvokerCreator; + + const script = iframe.contentWindow.document.createElement("script"); + script.src = FILE_URL; + script.type = "module"; + + const promise = new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = reject; + }); + iframe.contentWindow.document.head.appendChild(script); + await promise; + + // @ts-ignore + delete iframe.contentWindow.replacedGlobal; + // @ts-ignore + delete iframe.contentWindow.replacedCI1; + // @ts-ignore + delete iframe.contentWindow.replacedCI2; + // @ts-ignore + delete iframe.contentWindow.replacedCIC; + // @ts-ignore + Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT); + iframe.remove(); + + + ({ + // @ts-ignore + AccessAction, + // @ts-ignore + Rule, + // @ts-ignore + Monitor, + // @ts-ignore + Marshal, + // @ts-ignore + Domain, + // @ts-ignore + Sandbox, + } = SANDBOX_EXPORT); + } else { + // 防止被不信任代码更改 + sealClass(AccessAction); + sealClass(Rule); + sealClass(Globals); + sealClass(DomainMonitors); + sealClass(Monitor); + sealClass(Marshal); + sealClass(Domain); + sealClass(Sandbox); + + sealClass(Object); + sealClass(Array); + sealClass(Function); + sealClass(Promise); + sealClass(RegExp); + sealClass(String); + sealClass(Number); + sealClass(Boolean); + sealClass(Symbol); + sealClass(Reflect); + sealClass(Proxy); + sealClass(Date); + sealClass(Math); + sealClass(Error); + sealClass(TypeError); + sealClass(ReferenceError); + sealClass(RangeError); + sealClass(EvalError); + sealClass(SyntaxError); + + sealClass(function* () { }.constructor); + sealClass(async function () { }.constructor); + sealClass(async function* () { }.constructor); + + // 改为此处初始化,防止多次初始化 + Domain[SandboxExposer2](SandboxSignal_InitDomain); + + // @ts-ignore + window.SANDBOX_EXPORT = + Object.assign({}, SANDBOX_EXPORT); + } +} + +export { + AccessAction, + Rule, + Monitor, + Marshal, + Domain, + Sandbox, + SANDBOX_ENABLED, +}; \ No newline at end of file diff --git a/noname/util/security.js b/noname/util/security.js new file mode 100644 index 000000000..7ed970c2f --- /dev/null +++ b/noname/util/security.js @@ -0,0 +1,642 @@ +import { Sandbox, Domain, Marshal, Monitor, AccessAction, Rule, SANDBOX_ENABLED } from "./sandbox.js"; + +// 是否强制所有模式下使用沙盒 +const SANDBOX_FORCED = true; + +let initialized = false; + +/** @type {Array} */ +const sandboxStack = []; + +/** @type {WeakMap>} */ +const isolatedsMap = new WeakMap(); + +const topVariables = { + lib: null, + game: null, + ui: null, + get: null, + ai: null, + _status: null, + gnc: null, +}; +const defaultEval = window.eval; + +let sandBoxRequired = SANDBOX_FORCED; + +// 可能的垫片函数 +const pfPrototypes = ["Object", "Array", "String", "Map"]; // 传递的实例垫片 +const pfNamespaces = ["Object", "Array", "Reflect", "Math", "Promise"]; // 传递的静态垫片 +// 可能还要补充喵? +const nativePattern = /^function \w*\(\) \{ \[native code\] \}$/; + +// 垫片备份 +const polyfills = { + prototypes: {}, + namespaces: {}, +}; + +/** + * ```plain + * 将一个沙盒作为当前联网传输的运行沙盒 + * ``` + * + * @param {Sandbox} box + */ +function enterSandbox(box) { + if (!SANDBOX_ENABLED) + return; + + if (!Domain.isBelievable(Domain.topDomain)) + throw "无法在沙盒里面访问"; + + sandboxStack.push(box); +} + +/** + * ```plain + * 退出当前联网传输的运行沙盒 + * ``` + */ +function exitSandbox() { + if (!SANDBOX_ENABLED) + return; + + if (!Domain.isBelievable(Domain.topDomain)) + throw "无法在沙盒里面访问"; + if (!sandboxStack.length) + return; + + sandboxStack.pop(); +} + +/** + * ```plain + * 判断对象是否是安全对象 + * ``` + * + * @param {Object?} obj 要检查的对象 + * @param {string?} prop 指定要检查的属性描述符 + */ +function isUnsafeObject(obj, prop = null) { + if (!SANDBOX_ENABLED) + return true; + + if (prop != null) { + const descriptor = Object.getOwnPropertyDescriptor(obj, prop); + + if (descriptor) { + if (descriptor.get + && isUnsafeObject(descriptor.get)) + return true; + if (descriptor.set + && isUnsafeObject(descriptor.set)) + return true; + if (isUnsafeObject(descriptor.value)) + return true; + } + } + + if (isPrimitive(obj)) + return false; + + return !Domain.topDomain.isFrom(obj); +} + +/** + * ```plain + * 确保对象是安全对象 + * ``` + * + * @param {Object?} obj 要检查的对象 + * @param {string?} prop 指定要检查的属性描述符 + */ +function assertSafeObject(obj, prop = null) { + if (isUnsafeObject(obj, prop)) + throw "unsafe object denied"; +} + +/** + * @param {Object?} obj + */ +function isPrimitive(obj) { + return Object(obj) !== obj; +} + +/** + * ```plain + * 获取当前指定的联网传输运行沙盒 + * ``` + * + * @returns {Sandbox?} + */ +function currentSandbox() { + if (!SANDBOX_ENABLED) + return null; + + return sandboxStack[sandboxStack.length - 1] || defaultSandbox; +} + +/** + * ```plain + * 进入沙盒运行模式 + * ``` + */ +function requireSandbox() { + sandBoxRequired = true; +} + +/** + * ```plain + * 判断是否是沙盒运行模式 + * ``` + * + * @returns {boolean} + */ +function isSandboxRequired() { + return SANDBOX_ENABLED && sandBoxRequired; +} + +/** + * ```plain + * 简单的、不带上下文的模拟eval函数 + * + * 自动根据沙盒的启用状态使用不同的实现 + * ``` + * + * @param {any} x + * @returns {any} + */ +function _eval(x) { + if (!SANDBOX_ENABLED || !sandBoxRequired) + return new Function(x)(); + + // @ts-ignore + return defaultSandbox.exec(x); +} + +/** + * ```plain + * 携带简单上下文的eval函数 + * + * 自动根据沙盒的启用状态使用不同的实现 + * ``` + * + * @param {any} x + * @param {Object} scope + * @returns {any} + */ +function _exec(x, scope) { + if (isPrimitive(scope)) + scope = {}; + + if (!SANDBOX_ENABLED || !sandBoxRequired) { + new Function(x); + const name = "_" + Math.random().toString(36).slice(2); + return new Function(name, `with(${name}){return(()=>{"use strict";${x}})()}`)(scope); + } + + // @ts-ignore + return defaultSandbox.exec(x, scope); +} + +/** + * ```plain + * 携带简单上下文的eval函数,并返回scope + * eval代码的返回值将覆盖 `scope.return` 这个属性 + * 另外任意因对未定义变量赋值导致全局变量赋值的行为将被转移到scope里面 + * + * 自动根据沙盒的启用状态使用不同的实现 + * ``` + * + * @param {any} x + * @param {Object|"window"} scope 传入一个对象作为上下文,或者传入 "window" 来生成一个包含指向自身的 `window` 属性的对象 + * @returns {Object} + */ +function _exec2(x, scope = {}) { + if (scope == "window") { + scope = {}; + scope.window = scope; + } else if (isPrimitive(scope)) + scope = {}; + + if (!SANDBOX_ENABLED || !sandBoxRequired) { + new Function(x); + + const intercepter = new Proxy(scope, { + get(target, prop, receiver) { + if (prop === Symbol.unscopables) + return undefined; + + if (!Reflect.has(target, prop) + && !Reflect.has(window, prop)) + throw new ReferenceError(`"${String(prop)}" is not defined`); + + return Reflect.get(target, prop, receiver) || window[prop]; + }, + has(target, prop) { + return true; + }, + }); + + const result = new Function("_", `with(_){return(()=>{"use strict";\n${x}})()}`)(intercepter); + scope.return = result; + return scope; + } + + // @ts-ignore + const [result] = defaultSandbox.exec2(x, scope); + scope.return = result; + return scope; +} + +function initSecurity({ + lib, + game, + ui, + get, + ai, + _status, + gnc, +}) { + if (initialized) + throw "security 已经被初始化过了"; + if (!SANDBOX_ENABLED) + return; + + topVariables.lib = lib; + topVariables.game = game; + topVariables.ui = ui; + topVariables.get = get; + topVariables.ai = ai; + topVariables._status = _status; + topVariables.gnc = gnc; + + loadPolyfills(); + + // @ts-ignore + Object.assign(defaultSandbox.scope, topVariables); + // @ts-ignore + setupPolyfills(defaultSandbox); + + // 不允许被远程代码访问的game函数 + const ioFuncs = [ + "download", + "readFile", + "readFileAsText", + "writeFile", + "removeFile", + "getFileList", + "ensureDirectory", + "createDir", + "removeDir", + "checkForUpdate", + "checkForAssetUpdate", + "importExtension", + "export", + "multiDownload2", + "multiDownload", + "fetch", + ]; + + const accessDenieds = [ + ...ioFuncs.map(n => game[n]).filter(Boolean), + ...Object.values(game.promises), + defaultEval, + window.require, + window, + ]; + + const callRule = new Rule(); + callRule.canMarshal = false; // 禁止获取函数 + callRule.setGranted(AccessAction.CALL, false); // 禁止函数调用 + callRule.setGranted(AccessAction.NEW, false); // 禁止函数new调用 + + accessDenieds.filter(Boolean).forEach(o => { + Marshal.setRule(o, callRule); + }); + + const bannedRule = new Rule(); + bannedRule.canMarshal = false; // 禁止获取 + bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性 + bannedRule.setGranted(AccessAction.WRITE, false); // 禁止读取属性 + + // 禁止访问关键对象 + [ + lib.cheat, + lib.node, + lib.message, + ] + .filter(Boolean) + .forEach(o => Marshal.setRule(o, bannedRule)); + + const writeRule = new Rule(); + writeRule.setGranted(AccessAction.WRITE, false); // 禁止写入属性 + writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性 + Marshal.setRule(game.promises, writeRule); + + new Monitor() + .action(AccessAction.WRITE) + .action(AccessAction.DEFINE) + .require("target", game) + .require("property", ...ioFuncs) + .then(() => { + throw "禁止修改关键函数"; + }) + .start(); // 差点忘记启动了喵 + + // 监听原型、toStringTag的更改 + const toStringTag = Symbol.toStringTag; + new Monitor() + .action(AccessAction.WRITE) + .action(AccessAction.DEFINE) + .action(AccessAction.META) + .require("property", toStringTag) + .then((access, nameds, control) => { + // 阻止原型、toStringTag的更改 + control.preventDefault(); + control.stopPropagation(); + control.setReturnValue(false); + }) + .start(); + + initialized = true; +} + +/** + * ```plain + * 创建一个新的沙盒 + * ``` + * + * @returns {Sandbox?} + */ +function createSandbox() { + if (!SANDBOX_ENABLED) + return null; + + const box = new Sandbox(); + box.freeAccess = true; + box.initBuiltins(); + + // 向沙盒提供顶级运行域的文档对象 + // TODO: 仅提供必要的document函数(?) + box.document = document; + + // 传递七个变量 + Object.assign(box.scope, topVariables); + // 复制垫片函数 + setupPolyfills(box); + + box.pushScope(); + return box; +} + +/** + * ```plain + * 导出当前沙盒的Function类型 + * ``` + * + * @param {Sandbox} sandbox + * @returns {Array} + */ +function getIsolateds(sandbox) { + let isolateds = isolatedsMap.get(sandbox); + + if (isolateds) + return isolateds.slice(); + + isolateds = Array.from(sandbox.exec(` + return [ + (function(){}).constructor, + (function*(){}).constructor, + (async function(){}).constructor, + (async function*(){}).constructor, + ]; + `)); + + isolatedsMap.set(sandbox, isolateds); + return isolateds.slice(); +} + +/** + * ```plain + * 加载当前的垫片函数 + * ``` + */ +function loadPolyfills() { + function copyDescriptors(top, box) { + for (const key of Reflect.ownKeys(top)) { + const descriptor = Reflect.getOwnPropertyDescriptor(top, key); + + if (!descriptor + || typeof descriptor.value !== "function") + continue; + + const body = descriptor.value.toString(); + + if (nativePattern.test(body)) + continue; + + box[key] = descriptor; + } + } + + for (const key of pfPrototypes) { + const top = window[key]; + + if (!top || !top.prototype) + continue; + + copyDescriptors(top.prototype, polyfills.prototypes[key] = {}); + } + + for (const key of pfNamespaces) { + const top = window[key]; + + if (!top) + continue; + + copyDescriptors(top, polyfills.namespaces[key] = {}); + } +} + +/** + * ```plain + * 初始化沙盒的垫片 + * ``` + * + * @param {Sandbox} sandbox + */ +function setupPolyfills(sandbox) { + const context = { + pfPrototypes, + pfNamespaces, + prototypes: polyfills.prototypes, + namespaces: polyfills.namespaces, + }; + + sandbox.exec(` + function definePolyfills(top, box) { + for (const key in top) + Reflect.defineProperty(box, key, top[key]); + } + + for (const key of pfPrototypes) { + if (key in prototypes) + definePolyfills( + prototypes[key], + window[key].prototype + ); + } + + for (const key of pfNamespaces) { + if (key in namespaces) + definePolyfills( + namespaces[key], + window[key] + ); + } + `, context); +} + +/** @type {typeof Function} */ +// @ts-ignore +const defaultFunction = function () { }.constructor; +/** @type {typeof Function} */ +// @ts-ignore +const defaultGeneratorFunction = function* () { }.constructor; +/** @type {typeof Function} */ +// @ts-ignore +const defaultAsyncFunction = async function () { }.constructor; +/** @type {typeof Function} */ +// @ts-ignore +const defaultAsyncGeneratorFunction = async function* () { }.constructor; + +const defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵 + +if (SANDBOX_ENABLED) { + // @ts-ignore + // 对于 defaultSandbox 我们要补充一些东西喵 + defaultSandbox.scope.localStorage = localStorage; + + // 对Function类型进行包裹 + /** @type {Array} */ + const [ + IsolatedFunction, + IsolatedGeneratorFunction, + IsolatedAsyncFunction, + IsolatedAsyncGeneratorFunction, + ] + // @ts-ignore + = getIsolateds(defaultSandbox); + + /** @type {typeof Function} */ + let ModFunction; + /** @type {typeof Function} */ + let ModGeneratorFunction; + /** @type {typeof Function} */ + let ModAsyncFunction; + /** @type {typeof Function} */ + let ModAsyncGeneratorFunction; + + ModFunction = new Proxy(defaultFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedFunction(...argumentsList); + }, + }); + + /** @type {typeof Function} */ + ModGeneratorFunction = new Proxy(defaultGeneratorFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedGeneratorFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedGeneratorFunction(...argumentsList); + }, + }); + + /** @type {typeof Function} */ + ModAsyncFunction = new Proxy(defaultAsyncFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncFunction(...argumentsList); + }, + }); + + /** @type {typeof Function} */ + ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncGeneratorFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncGeneratorFunction(...argumentsList); + }, + }); + + function rewriteCtor(prototype, newCtor) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') + || { configurable: true, writable: true, enumerable: false }; + if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); + descriptor.value = newCtor; + Reflect.defineProperty(prototype, 'constructor', descriptor); + } + + // 覆盖所有的Function类型构造函数 + window.Function = ModFunction; + rewriteCtor(defaultFunction.prototype, ModFunction); + rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction); + rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction); + rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction); +} + +// 测试暴露喵 +// window.sandbox = defaultSandbox; + +const exports = { + enterSandbox, + exitSandbox, + currentSandbox, + createSandbox, + isUnsafeObject, + assertSafeObject, + getIsolateds, + requireSandbox, + isSandboxRequired, + initSecurity, + eval: _eval, + exec: _exec, + exec2: _exec2, + SANDBOX_ENABLED, +}; + +Object.freeze(exports); +export default exports; \ No newline at end of file From 3dbd145f79e97cf29de0ebc223f7f01fd5698868 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sat, 25 May 2024 11:58:23 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E5=A4=84=E7=90=86get/index.js=EF=BC=9B?= =?UTF-8?q?=E5=BE=AE=E8=B0=83=E4=BB=A3=E7=A0=81=E4=BB=A5=E7=AC=A6=E5=90=88?= =?UTF-8?q?=E8=AF=AD=E6=B3=95=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/get/index.js | 117 +++++++++++++++++++++++++++++++--------- noname/util/security.js | 2 +- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/noname/get/index.js b/noname/get/index.js index 7688381eb..98021f71f 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -9,6 +9,7 @@ import { Promises } from "./promises.js"; import { rootURL } from "../../noname.js"; import * as pinyinPro from "./pinyins/index.js"; import { Audio } from "./audio.js"; +import security from "../util/security.js"; export class Get { is = new Is(); @@ -927,10 +928,10 @@ export class Get { const target = constructor ? Array.isArray(obj) || obj instanceof Map || obj instanceof Set || constructor === Object ? // @ts-ignore - new constructor() + new constructor() : constructor.name in window && /\[native code\]/.test(constructor.toString()) ? // @ts-ignore - new constructor(obj) + new constructor(obj) : obj : Object.create(null); if (target === obj) return target; @@ -1488,6 +1489,61 @@ export class Get { infoPlayersOL(infos) { return Array.from(infos || []).map(get.infoPlayerOL); } + /** 箭头函数头 */ + static #arrowPattern = /^(?:async\b\s*)?(?:([\w$]+)|\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\))\s*=>/; + /** 标准函数头 */ + static #fullPattern = /^([\w\s*]+)\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\)\s*\{/; + /** + * ```plain + * 测试一段代码是否为函数体 + * ``` + * + * @param {string} code + * @returns {boolean} + */ + static isFunctionBody(code) { + try { + new Function(code); + } catch (e) { + return false; + } + return true; + } + /** + * ```plain + * 清洗函数体代码 + * ``` + * + * @param {string} str + * @returns + */ + static pureFunctionStr(str) { + str = str.trim(); + const arrowMatch = Get.#arrowPattern.exec(str); + if (arrowMatch) { + const body = `return ${str.slice(arrowMatch[0].length)}`; + if (!Get.isFunctionBody(body)) { + console.error("发现疑似恶意的远程代码:", str); + return `()=>console.error("尝试执行疑似恶意的远程代码")`; + } + return `${arrowMatch[0]}{${body}}`; + } + if (!str.endsWith("}")) return '()=>console.warn("无法识别的远程代码")'; + const fullMatch = Get.#fullPattern.exec(str); + if (!fullMatch) return '()=>console.warn("无法识别的远程代码")'; + const head = fullMatch[1]; + const args = fullMatch[2] || ''; + const body = str.slice(fullMatch[0].length).slice(0, -1); + if (!Get.isFunctionBody(body)) { + console.error("发现疑似恶意的远程代码:", str); + return `()=>console.error("尝试执行疑似恶意的远程代码")`; + } + str = `(${args}){${body}}`; + if (head.includes("*")) str = "*" + str; + str = "function" + str; + if (/\basync\b/.test(head)) str = "async " + str; + return str; + } funcInfoOL(func) { if (typeof func == "function") { if (func._filter_args) { @@ -1496,29 +1552,25 @@ export class Get { const str = func.toString(); // js内置的函数 if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}"; - return "_noname_func:" + str; + return "_noname_func:" + Get.pureFunctionStr(str); } return ""; } infoFuncOL(info) { let func; - const str = info.slice(13).trim(); + const str = Get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入 try { // js内置的函数 - if (/\{\s*\[native code\]\s*\}/.test(str)) return function () {}; - // 一般fun和数组形式 - if (str.startsWith("function") || str.startsWith("(")) eval(`func=(${str});`); - // 其他奇形怪状的fun - else { - try { - eval(`func = ${str}`); - } catch { - eval(`let obj = {${str}}; func = obj[Object.keys(obj)[0]]`); - } - } + if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { }; + if (security.isSandboxRequired()) { + const loadStr = `return (${str});`; + const box = security.currentSandbox(); + if (!box) throw new ReferenceError("没有找到当前沙盒"); + func = box.exec(loadStr); + } else func = security.exec(`return (${str});`); } catch (e) { console.error(`${e} in \n${str}`); - return function () {}; + return function () { }; } if (Array.isArray(func)) { func = get.filter.apply(this, get.parsedResult(func)); @@ -1528,14 +1580,14 @@ export class Get { eventInfoOL(item, level, noMore) { return get.itemtype(item) == "event" ? `_noname_event:${JSON.stringify( - Object.entries(item).reduce((stringifying, entry) => { - const key = entry[0]; - if (key == "_trigger") { - if (noMore !== false) stringifying[key] = get.eventInfoOL(entry[1], null, false); - } else if (!lib.element.GameEvent.prototype[key] && key != "content" && get.itemtype(entry[1]) != "event") stringifying[key] = get.stringifiedResult(entry[1], null, false); - return stringifying; - }, {}) - )}` + Object.entries(item).reduce((stringifying, entry) => { + const key = entry[0]; + if (key == "_trigger") { + if (noMore !== false) stringifying[key] = get.eventInfoOL(entry[1], null, false); + } else if (!lib.element.GameEvent.prototype[key] && key != "content" && get.itemtype(entry[1]) != "event") stringifying[key] = get.stringifiedResult(entry[1], null, false); + return stringifying; + }, {}) + )}` : ""; } /** @@ -2074,8 +2126,8 @@ export class Get { n = game.checkMod(from, to, n, "globalFrom", from); n = game.checkMod(from, to, n, "globalTo", to); const equips1 = from.getCards("e", function (card) { - return !ui.selected.cards || !ui.selected.cards.includes(card); - }), + return !ui.selected.cards || !ui.selected.cards.includes(card); + }), equips2 = to.getCards("e", function (card) { return !ui.selected.cards || !ui.selected.cards.includes(card); }); @@ -4896,6 +4948,19 @@ export class Get { } } +function freezeSlot(obj, key) { + const descriptor = Reflect.getOwnPropertyDescriptor(obj, key); + if (!descriptor) return; + descriptor.writable = false; + descriptor.configurable = false; + Reflect.defineProperty(obj, key, descriptor); +} + +freezeSlot(Get, "isFunctionBody"); +freezeSlot(Get, "pureFunctionStr"); +freezeSlot(Get, "funcInfoOL"); +freezeSlot(Get, "infoFuncOL"); + export let get = new Get(); /** * @param { InstanceType } [instance] diff --git a/noname/util/security.js b/noname/util/security.js index 7ed970c2f..63ba5da3c 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -186,7 +186,7 @@ function _eval(x) { * @param {Object} scope * @returns {any} */ -function _exec(x, scope) { +function _exec(x, scope = {}) { if (isPrimitive(scope)) scope = {}; From 6ffd29048970788e9391bcd443f3a48ac9a9b6ca Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sat, 25 May 2024 13:21:00 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E5=BE=AE=E8=B0=83=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=9B=E5=A4=84=E7=90=86init/index.js=E3=80=81client.js?= =?UTF-8?q?=E3=80=81gameEventPromise.js=E3=80=81player.js=E3=80=81util/ind?= =?UTF-8?q?ex.js=E3=80=81otherMenu.js=E3=80=81extensionMenu.js=E3=80=81gam?= =?UTF-8?q?e/index.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/game/index.js | 12 +- noname/get/index.js | 20 +-- noname/init/index.js | 9 +- noname/library/element/client.js | 2 + noname/library/element/gameEventPromise.js | 2 + noname/library/element/player.js | 37 +++-- noname/ui/create/menu/pages/exetensionMenu.js | 7 +- noname/ui/create/menu/pages/otherMenu.js | 139 ++++++++++-------- noname/util/index.js | 11 +- 9 files changed, 143 insertions(+), 96 deletions(-) diff --git a/noname/game/index.js b/noname/game/index.js index 5a7cf3dac..597507be1 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -22,6 +22,8 @@ import { DynamicStyle } from "./dynamic-style/index.js"; import { GamePromises } from "./promises.js"; import { Check } from "./check.js"; +import security from "../util/security.js"; + export class Game { online = false; onlineID = null; @@ -1312,6 +1314,7 @@ export class Game { if (callback) callback(false); return; } + game.sandbox = security.createSandbox(); game.ws.onopen = lib.element.ws.onopen; game.ws.onmessage = lib.element.ws.onmessage; game.ws.onerror = lib.element.ws.onerror; @@ -2135,8 +2138,8 @@ export class Game { _status.importingExtension = true; try { // 导入普通扩展 - eval(str); - // esm扩展可以不写game.impoprt或许会导致_status.extensionLoading不存在 + security.eval(str); + // esm扩展可以不写game.import或许会导致_status.extensionLoading不存在 if (Array.isArray(_status.extensionLoading)) { await Promise.allSettled(_status.extensionLoading); delete _status.extensionLoading; @@ -5453,6 +5456,8 @@ export class Game { newvid.name1 = newvid.name2.slice(10, newvid.name1.lastIndexOf("_")); } lib.videos.unshift(newvid); + // 清洗代理对象 + newvid.video = structuredClone(newvid.video); store.put(newvid); ui.create.videoNode(newvid, true); } @@ -7695,7 +7700,8 @@ export class Game { ); lib.status.reload++; return new Promise((resolve, reject) => { - const record = lib.db.transaction([storeName], "readwrite").objectStore(storeName).put(value, idbValidKey); + const record = lib.db.transaction([storeName], "readwrite") + .objectStore(storeName).put(structuredClone(value), idbValidKey); record.onerror = event => { if (typeof onError == "function") { onError(event); diff --git a/noname/get/index.js b/noname/get/index.js index 98021f71f..f461cad68 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -1490,9 +1490,9 @@ export class Get { return Array.from(infos || []).map(get.infoPlayerOL); } /** 箭头函数头 */ - static #arrowPattern = /^(?:async\b\s*)?(?:([\w$]+)|\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\))\s*=>/; + #arrowPattern = /^(?:async\b\s*)?(?:([\w$]+)|\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\))\s*=>/; /** 标准函数头 */ - static #fullPattern = /^([\w\s*]+)\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\)\s*\{/; + #fullPattern = /^([\w\s*]+)\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\)\s*\{/; /** * ```plain * 测试一段代码是否为函数体 @@ -1501,7 +1501,7 @@ export class Get { * @param {string} code * @returns {boolean} */ - static isFunctionBody(code) { + isFunctionBody(code) { try { new Function(code); } catch (e) { @@ -1517,24 +1517,24 @@ export class Get { * @param {string} str * @returns */ - static pureFunctionStr(str) { + pureFunctionStr(str) { str = str.trim(); - const arrowMatch = Get.#arrowPattern.exec(str); + const arrowMatch = get.#arrowPattern.exec(str); if (arrowMatch) { const body = `return ${str.slice(arrowMatch[0].length)}`; - if (!Get.isFunctionBody(body)) { + if (!get.isFunctionBody(body)) { console.error("发现疑似恶意的远程代码:", str); return `()=>console.error("尝试执行疑似恶意的远程代码")`; } return `${arrowMatch[0]}{${body}}`; } if (!str.endsWith("}")) return '()=>console.warn("无法识别的远程代码")'; - const fullMatch = Get.#fullPattern.exec(str); + const fullMatch = get.#fullPattern.exec(str); if (!fullMatch) return '()=>console.warn("无法识别的远程代码")'; const head = fullMatch[1]; const args = fullMatch[2] || ''; const body = str.slice(fullMatch[0].length).slice(0, -1); - if (!Get.isFunctionBody(body)) { + if (!get.isFunctionBody(body)) { console.error("发现疑似恶意的远程代码:", str); return `()=>console.error("尝试执行疑似恶意的远程代码")`; } @@ -1552,13 +1552,13 @@ export class Get { const str = func.toString(); // js内置的函数 if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}"; - return "_noname_func:" + Get.pureFunctionStr(str); + return "_noname_func:" + get.pureFunctionStr(str); } return ""; } infoFuncOL(info) { let func; - const str = Get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入 + const str = get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入 try { // js内置的函数 if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { }; diff --git a/noname/init/index.js b/noname/init/index.js index ca396db2b..6c39036d3 100644 --- a/noname/init/index.js +++ b/noname/init/index.js @@ -10,6 +10,7 @@ import * as config from "../util/config.js"; import { promiseErrorHandlerMap } from "../util/browser.js"; import { importCardPack, importCharacterPack, importExtension, importMode } from "./import.js"; import { onload } from "./onload.js"; +import security from "../util/security.js"; // 判断是否从file协议切换到http/s协议 export function canUseHttpProtocol() { @@ -124,6 +125,11 @@ export async function boot() { // 加载polyfill内容 await import("./polyfill.js"); + // 初始化security + security.initSecurity({ + lib, game, ui, get, ai, _status, gnc, + }); + // 设定游戏加载时间,超过时间未加载就提醒 const configLoadTime = localStorage.getItem(lib.configprefix + "loadtime"); // 现在不暴露到全局变量里了,直接传给onload @@ -478,7 +484,8 @@ export async function boot() { //var backup_onload=lib.init.onload; _status.evaluatingExtension = true; try { - eval(extcontent); + debugger; // NEED TO VIEW DATA + security.eval(extcontent); } catch (e) { console.log(e); } diff --git a/noname/library/element/client.js b/noname/library/element/client.js index dadcc611f..279fa579b 100644 --- a/noname/library/element/client.js +++ b/noname/library/element/client.js @@ -3,6 +3,7 @@ import { game } from "../../game/index.js"; import { lib } from "../index.js"; import { _status } from "../../status/index.js"; import { ui } from "../../ui/index.js"; +import security from "../../util/security.js"; export class Client { /** @@ -17,6 +18,7 @@ export class Client { // @ts-ignore this.id = ws.wsid || get.id(); this.closed = false; + this.sandbox = security.createSandbox(); } send() { if (this.closed) return this; diff --git a/noname/library/element/gameEventPromise.js b/noname/library/element/gameEventPromise.js index 95089b042..b210c2021 100644 --- a/noname/library/element/gameEventPromise.js +++ b/noname/library/element/gameEventPromise.js @@ -3,6 +3,7 @@ import { game } from "../../game/index.js"; import { lib } from "../index.js"; import { _status } from "../../status/index.js"; import { AsyncFunction } from "../../util/index.js"; +import security from '../../util/security.js'; /** * 将事件Promise化以使用async异步函数来执行事件。 @@ -220,6 +221,7 @@ export class GameEventPromise extends Promise { * ``` */ async debugger() { + if (security.isSandboxRequired()) throw new Error("当前模式下禁止调试"); return new Promise((resolve) => { const runCode = function (event, code) { try { diff --git a/noname/library/element/player.js b/noname/library/element/player.js index ecd28d7b7..4ce81a277 100644 --- a/noname/library/element/player.js +++ b/noname/library/element/player.js @@ -20,6 +20,7 @@ import { _status } from "../../status/index.js"; import { ui } from "../../ui/index.js"; import { CacheContext } from "../cache/cacheContext.js"; import { ChildNodesWatcher } from "../cache/childNodesWatcher.js"; +import security from "../../util/security.js"; export class Player extends HTMLDivElement { /** @@ -454,7 +455,7 @@ export class Player extends HTMLDivElement { const vars = {}; /** * 作用域 - * @type { (code: string) => any } + * @type { ((code: string) => any)? } */ let scope; /** @type { Skill } */ @@ -500,14 +501,13 @@ export class Player extends HTMLDivElement { varstr += `var ${key}=lib.skill['${skillName}'].vars['${key}'];\n`; } let str = ` - function content(){ - ${varstr}if(event.triggername=='${skillName}After'){ - player.removeSkill('${skillName}'); - delete lib.skill['${skillName}']; - delete lib.translate['${skillName}']; - return event.finish(); - } - `; + ${varstr}if(event.triggername=='${skillName}After'){ + player.removeSkill('${skillName}'); + delete lib.skill['${skillName}']; + delete lib.translate['${skillName}']; + return event.finish(); + } + `; for (let i = 0; i < skill.contentFuns.length; i++) { const fun2 = skill.contentFuns[i]; const a = fun2.toString(); @@ -516,7 +516,15 @@ export class Player extends HTMLDivElement { const str2 = a.slice(begin, a.lastIndexOf("}") != -1 ? a.lastIndexOf("}") : undefined).trim(); str += `'step ${i}'\n\t${str2}\n\t`; } - skill.content = lib.init.parsex((scope || eval)(str + `\n};content;`), scope); + // 防止注入喵 + if (!get.isFunctionBody(str)) throw new Error("无效的content函数代码"); + let recompiledScope; + if (security.isSandboxRequired()) { + recompiledScope = scope ? security.eval(`return (${scope.toString()})`) : code => security.eval(`return (${code.toString()})`); + } else { + recompiledScope = scope || eval; + } + skill.content = lib.init.parsex(recompiledScope(`(function(){\n${str}\n})`), scope && recompiledScope); // @ts-ignore skill.content._parsed = true; }; @@ -632,6 +640,7 @@ export class Player extends HTMLDivElement { */ apply(_scope) { if (lib.skill[skillName] != skill) throw `This skill has been destroyed`; + if (security.isSandboxRequired()) console.warn("`player.when().apply()` 在沙盒模式下不推荐使用"); // @ts-ignore scope = _scope; if (skill.contentFuns.length > 0) createContent(); @@ -3654,9 +3663,9 @@ export class Player extends HTMLDivElement { return Array.from(this.iterableGetCards(arg1, arg2)); } /** - * @param { Player } player - * @param { string } [arg1] - * @param { string } [arg2] + * @param { Player } player + * @param { string } [arg1] + * @param { string } [arg2] * @returns { Generator } */ *iterableGetDiscardableCards(player, arg1, arg2) { @@ -5861,7 +5870,7 @@ export class Player extends HTMLDivElement { return get.is.sameNature(natures, naturesx); }; if (next.hasNature("poison")) delete next._triggered; - else if(next.unreal) next._triggered = 2; + else if (next.unreal) next._triggered = 2; next.setContent("damage"); next.filterStop = function () { if (this.source && this.source.isDead()) delete this.source; diff --git a/noname/ui/create/menu/pages/exetensionMenu.js b/noname/ui/create/menu/pages/exetensionMenu.js index d0b8fce07..d23b7b0a5 100644 --- a/noname/ui/create/menu/pages/exetensionMenu.js +++ b/noname/ui/create/menu/pages/exetensionMenu.js @@ -18,6 +18,7 @@ import { } from "../index.js"; import { ui, game, get, ai, lib, _status } from "../../../../../noname.js"; import { nonameInitialized } from "../../../../util/index.js"; +import security from "../../../../util/security.js"; export const extensionMenu = function (connectMenu) { if (connectMenu) return; @@ -334,18 +335,18 @@ export const extensionMenu = function (connectMenu) { var ext = {}; var config = null, help = null; + debugger; // NEED TO VIEW DATA for (var i in dash4.content) { try { if (i == "content" || i == "precontent") { - eval("ext[i]=" + dash4.content[i]); + ({ config, help, return: ext[i] } = security.exec2(`return (${dash4.content[i]});`)); if (typeof ext[i] != "function") { throw "err"; } else { ext[i] = ext[i].toString(); } } else { - eval(dash4.content[i]); - eval("ext[i]=" + i); + ({ config, help, return: ext[i] } = security.exec2(`${dash4.content[i]}; return (${i});`)); if (ext[i] == null || typeof ext[i] != "object") { throw "err"; } else { diff --git a/noname/ui/create/menu/pages/otherMenu.js b/noname/ui/create/menu/pages/otherMenu.js index 83ff92ce0..8c0758c40 100644 --- a/noname/ui/create/menu/pages/otherMenu.js +++ b/noname/ui/create/menu/pages/otherMenu.js @@ -20,6 +20,8 @@ import { getLatestVersionFromGitHub, getTreesFromGithub, } from "../../../../library/update.js"; +import security from "../../../../util/security.js"; +import { AccessAction, Marshal, Monitor } from "../../../../util/sandbox.js"; export const otherMenu = function (/** @type { boolean | undefined } */ connectMenu) { if (connectMenu) return; @@ -1212,73 +1214,84 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM ai: ai, cheat: lib.cheat, }); - Object.defineProperties(proxyWindow, { - _status: { - configurable: false, - enumerable: true, - writable: false, - }, - lib: { - configurable: false, - enumerable: true, - writable: false, - }, - game: { - configurable: false, - enumerable: true, - writable: false, - }, - ui: { - configurable: false, - enumerable: true, - writable: false, - }, - get: { - configurable: false, - enumerable: true, - writable: false, - }, - ai: { - configurable: false, - enumerable: true, - writable: false, - }, - cheat: { - configurable: false, - enumerable: true, - writable: false, - }, - }); - proxyWindow = new Proxy(proxyWindow, { - set(target, prop, newValue) { - if (!["_status", "lib", "game", "ui", "get", "ai", "cheat"].includes(prop)) { - Reflect.set(window, prop, newValue); - } - return Reflect.set(target, prop, newValue); - }, - }); + if (security.isSandboxRequired()) { + new Monitor() + .action(AccessAction.DEFINE) + .action(AccessAction.WRITE) + .action(AccessAction.DELETE) + .require("target", proxyWindow) + .require("property", "_status", "lib", "game", "ui", "get", "ai", "cheat") + .then((access, nameds, control) => { + if (access.action == AccessAction.DEFINE) { + control.preventDefault(); + control.stopPropagation(); + control.setReturnValue(false); + return; + } + + control.overrideParameter("target", window); + }) + .start(); + } else { + const keys = ["_status", "lib", "game", "ui", "get", "ai", "cheat"]; + + for (const key of keys) { + const descriptor = Reflect.getOwnPropertyDescriptor(proxyWindow, key); + if (!descriptor) continue; + descriptor.writable = false; + descriptor.enumerable = true; + descriptor.configurable = false; + Reflect.defineProperty(proxyWindow, key, descriptor); + } + + proxyWindow = new Proxy(proxyWindow, { + set(target, propertyKey, value, receiver) { + if (typeof propertyKey == "string" && keys.includes(propertyKey)) { + return Reflect.set(target, propertyKey, value, receiver); + } + + return Reflect.set(window, propertyKey, value); + }, + }); + } //使用new Function隔绝作用域,避免在控制台可以直接访问到runCommand等变量 /** * @type { (value:string)=>any } */ - const fun = new Function( - "window", - ` - const _status=window._status; - const lib=window.lib; - const game=window.game; - const ui=window.ui; - const get=window.get; - const ai=window.ai; - const cheat=window.lib.cheat; - //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。 - const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/}; - return function(value){ - "use strict"; - return eval(reg.test(value)?('('+value+')'):value); - } - ` - )(proxyWindow); + let fun + if (security.isSandboxRequired()) { + fun = security.eval(` + const _status=window._status; + const lib=window.lib; + const game=window.game; + const ui=window.ui; + const get=window.get; + const ai=window.ai; + const cheat=window.lib.cheat; + //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。 + const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/}; + return function(value){ + "use strict"; + return eval(reg.test(value)?('('+value+')'):value); + }; + `); + } else { + fun = (new Function('window', ` + const _status=window._status; + const lib=window.lib; + const game=window.game; + const ui=window.ui; + const get=window.get; + const ai=window.ai; + const cheat=window.lib.cheat; + //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。 + const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/}; + return function(value){ + "use strict"; + return eval(reg.test(value)?('('+value+')'):value); + } + `))(proxyWindow); + } const runCommand = () => { if (text2.value && !["up", "down"].includes(text2.value)) { logindex = -1; diff --git a/noname/util/index.js b/noname/util/index.js index 8cc922af0..ea7c21647 100644 --- a/noname/util/index.js +++ b/noname/util/index.js @@ -7,8 +7,15 @@ export const assetURL = nonameInitialized == "nodejs" ? "" : nonameInitialized; -export const GeneratorFunction = function* () {}.constructor; -export const AsyncFunction = async function () {}.constructor; +/** @type {typeof Function} */ +// @ts-ignore +export const GeneratorFunction = (function* () {}).constructor; +/** @type {typeof Function} */ +// @ts-ignore +export const AsyncFunction = (async function () {}).constructor; +/** @type {typeof Function} */ +// @ts-ignore +export const AsyncGeneratorFunction = (async function* () {}).constructor; export const userAgent = navigator.userAgent.toLowerCase(); export { Mutex } from "./mutex.js"; export const characterDefaultPicturePath = "image/character/default_silhouette_"; From 2e6502c6395a608c5152d8e1433c6e3a10039813 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sat, 25 May 2024 13:44:27 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E9=83=A8=E5=88=86=EF=BC=9B=E5=A4=84=E7=90=86?= =?UTF-8?q?library/index.js=E3=80=81library/init/index.js=E3=80=81connect.?= =?UTF-8?q?js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mode/connect.js | 3 + noname/library/index.js | 30 +++++--- noname/library/init/index.js | 69 +++++++++++++++---- noname/ui/create/menu/pages/exetensionMenu.js | 7 +- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/mode/connect.js b/mode/connect.js index 8977d7b24..22118d21a 100644 --- a/mode/connect.js +++ b/mode/connect.js @@ -2,6 +2,9 @@ game.import("mode", function (lib, game, ui, get, ai, _status) { return { name: "connect", + init() { + game.requireSandbox(); + }, start: function () { var directstartmode = lib.config.directstartmode; ui.create.menu(true); diff --git a/noname/library/index.js b/noname/library/index.js index 47c1b220e..ec3ca63fe 100644 --- a/noname/library/index.js +++ b/noname/library/index.js @@ -24,6 +24,7 @@ import * as Element from "./element/index.js"; import { updateURLs } from "./update-urls.js"; import { defaultHooks } from "./hooks/index.js"; import { freezeButExtensible } from "../util/index.js"; +import security from "../util/security.js"; export class Library { configprefix = "noname_0.9_"; @@ -6333,8 +6334,8 @@ export class Library { code = container.textarea.value; } try { - var character = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { character } = security.exec2(code); if (!Array.isArray(character)) { throw "err"; } @@ -6421,8 +6422,8 @@ export class Library { code = container.textarea.value; } try { - var character = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { character } = security.exec2(code); if (!Array.isArray(character)) { throw "err"; } @@ -6850,8 +6851,8 @@ export class Library { code = container.textarea.value; } try { - var character = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { character } = security.exec2(code); if (!get.is.object(character)) { throw "err"; } @@ -7752,7 +7753,8 @@ export class Library { if (Array.isArray(context)) { try { const code = context.length == 1 ? context[0].string : context.reduceRight((pre, cur) => (pre.string || pre) + "." + cur.string); - obj = eval(code); + debugger; // NEED TO VIEW DATA + obj = security.eval(`return ${code};`); if (![null, undefined].includes(obj)) { const keys = Object.getOwnPropertyNames(obj) .concat(Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) @@ -9560,8 +9562,14 @@ export class Library { if (!Array.isArray(message) || typeof lib.message.client[message[0]] !== "function") { throw "err"; } - for (var i = 1; i < message.length; i++) { - message[i] = get.parsedResult(message[i]); + if (!game.sandbox) game.sandbox = security.createSandbox(); + security.enterSandbox(game.sandbox); + try { + for (var i = 1; i < message.length; i++) { + message[i] = get.parsedResult(message[i]); + } + } finally { + security.exitSandbox(); } } catch (e) { console.log(e); @@ -9597,6 +9605,7 @@ export class Library { } game.online = false; game.ws = null; + game.sandbox = null; }, }, /** @@ -12251,8 +12260,9 @@ export class Library { log: function () { var items = []; try { + debugger; // NEED TO VIEW DATA for (var i = 0; i < arguments.length; i++) { - eval("items.push(" + arguments[i] + ")"); + items.push(security.eval(`return ${arguments[i]}`)); } } catch (e) { this.send("log", ["err"]); diff --git a/noname/library/init/index.js b/noname/library/init/index.js index 704ba8831..99774b251 100644 --- a/noname/library/init/index.js +++ b/noname/library/init/index.js @@ -11,6 +11,9 @@ import { GameEvent } from "../element/gameEvent.js"; import { GameEventPromise } from "../element/gameEventPromise.js"; import { rootURL } from "../../../noname.js"; +import security from "../../util/security.js"; +import { Domain, Marshal, Sandbox } from "../../util/sandbox.js"; + export class LibInit { /** * 部分函数的Promise版本 @@ -140,8 +143,16 @@ export class LibInit { if (!Array.isArray(message) || typeof lib.message.server[message[0]] !== "function") { throw "err"; } - for (var i = 1; i < message.length; i++) { - message[i] = get.parsedResult(message[i]); + if (!client.sandbox) client.sandbox = security.createSandbox(); + // @ts-ignore + security.enterSandbox(client.sandbox); + try { + for (var i = 1; i < message.length; i++) { + message[i] = get.parsedResult(message[i]); + } + } finally { + // @ts-ignore + security.exitSandbox(client.sandbox); } } catch (e) { console.log(e); @@ -283,7 +294,8 @@ export class LibInit { if (data.includes("sojson") || data.includes("jsjiami") || data.includes("var _0x")) alert(`检测到您安装了使用免费版sojson进行加密的扩展。请谨慎使用这些扩展,避免游戏数据遭到破坏。\n扩展文件:${pathToRead}`); } try { - window.eval(data); + debugger; // NEED TO VIEW DATA + security.eval(data); if (typeof onLoad == "function") onLoad(); } catch (error) { if (typeof onError == "function") onError(error); @@ -584,6 +596,33 @@ export class LibInit { * @returns */ parsex(item, scope) { + let ModFunction = Function; + let ModGeneratorFunction = GeneratorFunction; + // let ModAsyncFunction = AsyncFunction; + // let ModAsyncGeneratorFunction = AsyncGeneratorFunction; + + // 虽然现在 parsex 被控制到了沙盒, + // 但是因为默认沙盒还是可以额外操作东西, + // 故而对不同的运行域做了区分 + if (security.SANDBOX_ENABLED) { + const domain = Marshal.getMarshalledDomain(item) || Domain.caller; + + // 非顶级域调用情况下我们替换掉Function类型 + if (domain && domain !== Domain.topDomain) { + const sandbox = Sandbox.from(domain); + + if (!sandbox) + throw "意外的运行域: 运行域没有绑定沙盒"; + + [ + ModFunction, + ModGeneratorFunction, + // ModAsyncFunction, + // ModAsyncGeneratorFunction, + ] = security.getIsolateds(sandbox); + } + } + //by 诗笺、Tipx-L /** * @param {Function} func @@ -608,7 +647,7 @@ export class LibInit { debuggerCopy = debuggerCopy.slice(0, debuggerSkip + debuggerResult.index) + insertDebugger + debuggerCopy.slice(debuggerSkip + debuggerResult.index + debuggerResult[0].length, -1); //测试是否有错误 try { - new GeneratorFunction(debuggerCopy); + new ModGeneratorFunction(debuggerCopy); str = debuggerCopy + "}"; debuggerSkip += debuggerResult.index + insertDebugger.length; hasDebugger = true; @@ -635,7 +674,7 @@ export class LibInit { copy = copy.slice(0, skip + result.index) + insertStr + copy.slice(skip + result.index + result[0].length); //测试是否有错误 try { - new (hasDebugger ? GeneratorFunction : Function)(copy); + new (hasDebugger ? ModGeneratorFunction : ModFunction)(copy); str = copy; skip += result.index + insertStr.length; } catch (error) { @@ -647,11 +686,12 @@ export class LibInit { str = `if(event.step==${k}){event.finish();return;}` + str; } if (!scope) { - return new (hasDebugger ? GeneratorFunction : Function)("event", "step", "source", "player", "target", "targets", "card", "cards", "skill", "forced", "num", "trigger", "result", "_status", "lib", "game", "ui", "get", "ai", str); + return new (hasDebugger ? ModGeneratorFunction : ModFunction)("event", "step", "source", "player", "target", "targets", "card", "cards", "skill", "forced", "num", "trigger", "result", "_status", "lib", "game", "ui", "get", "ai", str); } else { - return scope(`function${hasDebugger ? "*" : ""} anonymous(event,step,source,player,target,targets, + new (hasDebugger ? ModGeneratorFunction : ModFunction)(str); // 防止注入喵 + return scope(`(function${hasDebugger ? "*" : ""}(event,step,source,player,target,targets, card,cards,skill,forced,num,trigger,result, - _status,lib,game,ui,get,ai){${str}}; anonymous;`); + _status,lib,game,ui,get,ai){${str}})`); } } switch (typeof item) { @@ -687,7 +727,8 @@ export class LibInit { }; } else { if (Symbol.iterator in item) return lib.init.parsex(Array.from(item)); - if (item.toString !== Object.prototype.toString) return lib.init.parsex(item.toString()); + // 根据狂神喵的建议,禁用parsex接受字符串喵 + // if (item.toString !== Object.prototype.toString) return lib.init.parsex(item.toString()); if ("render" in item) { // TODO: Object Render Parse throw new Error("NYI: Object Render Parse"); @@ -744,22 +785,24 @@ export class LibInit { content._gen = true; return content; } else if (item._parsed) return item; - // falls through - default: return Legacy(item); + default: + throw new TypeError("为确保安全禁止用parsex解析字符串"); } } eval(func) { if (typeof func == "function") { - return eval("(" + func.toString() + ")"); + debugger; // NEED TO VIEW DATA + return security.eval(`return (${func.toString()});`); } else if (typeof func == "object") { for (var i in func) { if (Object.prototype.hasOwnProperty.call(func, i)) { if (typeof func[i] == "function") { let checkObject = {}; checkObject[i] = func[i]; - return eval(`(function(){return ${get.stringify(checkObject)};})()`)[i]; + debugger; // NEED TO VIEW DATA + return security.eval(`return ${get.stringify(checkObject)};`)[i]; } else { func[i] = lib.init.eval(func[i]); } diff --git a/noname/ui/create/menu/pages/exetensionMenu.js b/noname/ui/create/menu/pages/exetensionMenu.js index d23b7b0a5..fd8ac2797 100644 --- a/noname/ui/create/menu/pages/exetensionMenu.js +++ b/noname/ui/create/menu/pages/exetensionMenu.js @@ -333,20 +333,17 @@ export const extensionMenu = function (connectMenu) { inputExtName.disabled = true; setTimeout(function () { var ext = {}; - var config = null, - help = null; - debugger; // NEED TO VIEW DATA for (var i in dash4.content) { try { if (i == "content" || i == "precontent") { - ({ config, help, return: ext[i] } = security.exec2(`return (${dash4.content[i]});`)); + ext[i] = security.exec2(`return (${dash4.content[i]});`).return; if (typeof ext[i] != "function") { throw "err"; } else { ext[i] = ext[i].toString(); } } else { - ({ config, help, return: ext[i] } = security.exec2(`${dash4.content[i]}; return (${i});`)); + ext[i] = security.exec2(dash4.content[i])[i]; if (ext[i] == null || typeof ext[i] != "object") { throw "err"; } else { From 7c1fb8675fea33e6ed3a48db78828e1971c26bd6 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sat, 25 May 2024 13:52:55 +0800 Subject: [PATCH 05/16] =?UTF-8?q?=E8=A1=A5=E5=85=85gameEventPromise.js?= =?UTF-8?q?=E3=80=81extensionMenu.js=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/library/element/gameEventPromise.js | 10 ++++- noname/ui/create/menu/pages/exetensionMenu.js | 42 +++++++++---------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/noname/library/element/gameEventPromise.js b/noname/library/element/gameEventPromise.js index b210c2021..47ca7e4ab 100644 --- a/noname/library/element/gameEventPromise.js +++ b/noname/library/element/gameEventPromise.js @@ -226,8 +226,14 @@ export class GameEventPromise extends Promise { const runCode = function (event, code) { try { // 为了使玩家调试时使用var player=xxx时不报错,故使用var - var { player, _trigger: trigger, _result: result } = event; - return eval(code); + // var { player, _trigger: trigger, _result: result } = event; + var context = { + event, + player: event.player, + _trigger: event.trigger, + _result: event.result, + }; + return security.exec(`return ${code}`, context); } catch (error) { return error; } diff --git a/noname/ui/create/menu/pages/exetensionMenu.js b/noname/ui/create/menu/pages/exetensionMenu.js index fd8ac2797..2b3ba6185 100644 --- a/noname/ui/create/menu/pages/exetensionMenu.js +++ b/noname/ui/create/menu/pages/exetensionMenu.js @@ -380,16 +380,16 @@ export const extensionMenu = function (connectMenu) { if (typeof game.readFile == "function") { info[4].push( "die:ext:" + - page.currentExtension + - "/audio/die/" + - tag.slice(tag.lastIndexOf("/") + 1) + page.currentExtension + + "/audio/die/" + + tag.slice(tag.lastIndexOf("/") + 1) ); } else { info[4].push( "die:db:extension-" + - page.currentExtension + - ":audio/die/" + - tag.slice(tag.lastIndexOf("/") + 1) + page.currentExtension + + ":audio/die/" + + tag.slice(tag.lastIndexOf("/") + 1) ); } } @@ -1679,8 +1679,8 @@ export const extensionMenu = function (connectMenu) { code = container.textarea.value; } try { - var card = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { card } = security.exec2(code); if (card == null || typeof card != "object") { throw "err"; } @@ -1769,8 +1769,8 @@ export const extensionMenu = function (connectMenu) { page.content.pack.translate[name] = translate; page.content.pack.translate[name + "_info"] = info; try { - var card = null; - eval(container.code); + debugger; // NEED TO VIEW DATA + var { card } = security.exec2(container.code); if (card == null || typeof card != "object") { throw "err"; } @@ -2138,8 +2138,8 @@ export const extensionMenu = function (connectMenu) { code = container.textarea.value; } try { - var skill = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { skill } = security.exec2(code); if (skill == null || typeof skill != "object") { throw "err"; } @@ -2321,8 +2321,8 @@ export const extensionMenu = function (connectMenu) { page.content.pack.translate[name] = translate; page.content.pack.translate[name + "_info"] = info; try { - var skill = null; - eval(container.code); + debugger; // NEED TO VIEW DATA + var { skill } = security.exec2(container.code); if (skill == null || typeof skill != "object") { throw "err"; } @@ -2452,20 +2452,20 @@ export const extensionMenu = function (connectMenu) { } try { if (link == "content" || link == "precontent") { - var func = null; - eval("func=" + code); + debugger; // NEED TO VIEW DATA + var { func } = security.exec2(`func = ${code}`); if (typeof func != "function") { throw "err"; } } else if (link == "config") { - var config = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { config } = security.exec2(code); if (config == null || typeof config != "object") { throw "err"; } } else if (link == "help") { - var help = null; - eval(code); + debugger; // NEED TO VIEW DATA + var { help } = security.exec2(code); if (help == null || typeof help != "object") { throw "err"; } @@ -2911,7 +2911,7 @@ export const extensionMenu = function (connectMenu) { referrerPolicy: "no-referrer", }) .then((response) => response.text()) - .then(eval) + .then(security.eval) .then(loaded) .catch((reason) => { console.log(reason); From 79f35e91be6adc0d57c9136c20423170ccd8cbc6 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sat, 25 May 2024 16:05:27 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mode/connect.js | 9 +- noname/game/index.js | 192 +++++++++--------- noname/ui/create/menu/pages/exetensionMenu.js | 1 + noname/ui/create/menu/pages/otherMenu.js | 2 +- noname/ui/create/menu/pages/startMenu.js | 1 + noname/util/sandbox.js | 166 ++++++++++++--- noname/util/security.js | 84 ++++++-- 7 files changed, 306 insertions(+), 149 deletions(-) diff --git a/mode/connect.js b/mode/connect.js index 22118d21a..a8bdad541 100644 --- a/mode/connect.js +++ b/mode/connect.js @@ -2,9 +2,6 @@ game.import("mode", function (lib, game, ui, get, ai, _status) { return { name: "connect", - init() { - game.requireSandbox(); - }, start: function () { var directstartmode = lib.config.directstartmode; ui.create.menu(true); @@ -56,9 +53,11 @@ game.import("mode", function (lib, game, ui, get, ai, _status) { event.textnode.textContent = "正在连接..."; clearTimeout(event.timeout); if (e) e.preventDefault(); - game.saveConfig("last_ip", node.textContent); - game.connect(node.textContent, function (success) { + const ip = node.textContent; + game.saveConfig("last_ip", ip); + game.connect(ip, function (success) { if (success) { + game.requireSandboxOn(ip); // 启用沙盒喵 var info = lib.config.reconnect_info; if (info && info[0] == _status.ip) { game.onlineID = info[1]; diff --git a/noname/game/index.js b/noname/game/index.js index 597507be1..5929dc795 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1283,6 +1283,16 @@ export class Game { } } } + /** + * ```plain + * 进入沙盒运行模式 + * ``` + * + * @param { string } ip + */ + requireSandboxOn(ip = "") { + security.requireSandboxOn(ip); + } /** * @param { string } ip * @param { (result: boolean) => any } callback @@ -1458,7 +1468,7 @@ export class Game { * @param { boolean } [options.addVideo = true] * @returns */ - tryAudio({ audioList, autoplay = true, random = true, addVideo=true}) { + tryAudio({ audioList, autoplay = true, random = true, addVideo = true }) { /** * @type {string} */ @@ -2041,8 +2051,8 @@ export class Game { ); } const blob = zip.generate({ - type: "blob", - }), + type: "blob", + }), fileNameToSaveAs = `${exportExtension.replace(/\\|\/|:|\?|"|\*|<|>|\|/g, "-")}.zip`; if (lib.device) { @@ -3967,7 +3977,7 @@ export class Game { } } if (!callback) { - callback = function () {}; + callback = function () { }; } //try{ // if(noinput){ @@ -7759,59 +7769,59 @@ export class Game { return new Promise( query ? (resolve, reject) => { - lib.status.reload++; - const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).get(query); - idbRequest.onerror = event => { - if (typeof onError == "function") { - onError(event); - game.reload2(); - resolve(); - } else { - game.reload2(); - reject(event); - } - }; - idbRequest.onsuccess = event => { - const result = event.target.result; - if (typeof onSuccess == "function") { - _status.dburgent = true; - onSuccess(result); - delete _status.dburgent; - } + lib.status.reload++; + const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).get(query); + idbRequest.onerror = event => { + if (typeof onError == "function") { + onError(event); game.reload2(); - resolve(result); - }; - } + resolve(); + } else { + game.reload2(); + reject(event); + } + }; + idbRequest.onsuccess = event => { + const result = event.target.result; + if (typeof onSuccess == "function") { + _status.dburgent = true; + onSuccess(result); + delete _status.dburgent; + } + game.reload2(); + resolve(result); + }; + } : (resolve, reject) => { - lib.status.reload++; - const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(), - object = {}; - idbRequest.onerror = event => { - if (typeof onError == "function") { - onError(event); - game.reload2(); - resolve(); - } else { - game.reload2(); - reject(event); - } - }; - idbRequest.onsuccess = event => { - const result = event.target.result; - if (result) { - object[result.key] = result.value; - result.continue(); - return; - } - if (typeof onSuccess == "function") { - _status.dburgent = true; - onSuccess(object); - delete _status.dburgent; - } + lib.status.reload++; + const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(), + object = {}; + idbRequest.onerror = event => { + if (typeof onError == "function") { + onError(event); game.reload2(); - resolve(object); - }; - } + resolve(); + } else { + game.reload2(); + reject(event); + } + }; + idbRequest.onsuccess = event => { + const result = event.target.result; + if (result) { + object[result.key] = result.value; + result.continue(); + return; + } + if (typeof onSuccess == "function") { + _status.dburgent = true; + onSuccess(object); + delete _status.dburgent; + } + game.reload2(); + resolve(object); + }; + } ); } /** @@ -7848,45 +7858,45 @@ export class Game { ); return query ? new Promise((resolve, reject) => { - lib.status.reload++; - const record = lib.db.transaction([storeName], "readwrite").objectStore(storeName).delete(query); - record.onerror = event => { - if (typeof onError == "function") { - onError(event); - game.reload2(); - resolve(); - } else { - game.reload2(); - reject(event); - } - }; - record.onsuccess = event => { - if (typeof onSuccess == "function") onSuccess(event); + lib.status.reload++; + const record = lib.db.transaction([storeName], "readwrite").objectStore(storeName).delete(query); + record.onerror = event => { + if (typeof onError == "function") { + onError(event); game.reload2(); - resolve(event); - }; - }) + resolve(); + } else { + game.reload2(); + reject(event); + } + }; + record.onsuccess = event => { + if (typeof onSuccess == "function") onSuccess(event); + game.reload2(); + resolve(event); + }; + }) : game.getDB(storeName).then(object => { - const keys = Object.keys(object); - lib.status.reload += keys.length; - const store = lib.db.transaction([storeName], "readwrite").objectStore(storeName); - return Promise.allSettled( - keys.map( - key => - new Promise((resolve, reject) => { - const request = store.delete(key); - request.onerror = event => { - game.reload2(); - reject(event); - }; - request.onsuccess = event => { - game.reload2(); - resolve(event); - }; - }) - ) - ); - }); + const keys = Object.keys(object); + lib.status.reload += keys.length; + const store = lib.db.transaction([storeName], "readwrite").objectStore(storeName); + return Promise.allSettled( + keys.map( + key => + new Promise((resolve, reject) => { + const request = store.delete(key); + request.onerror = event => { + game.reload2(); + reject(event); + }; + request.onsuccess = event => { + game.reload2(); + resolve(event); + }; + }) + ) + ); + }); } /** * @param { string } key diff --git a/noname/ui/create/menu/pages/exetensionMenu.js b/noname/ui/create/menu/pages/exetensionMenu.js index 2b3ba6185..f803f12a9 100644 --- a/noname/ui/create/menu/pages/exetensionMenu.js +++ b/noname/ui/create/menu/pages/exetensionMenu.js @@ -2906,6 +2906,7 @@ export const extensionMenu = function (connectMenu) { } } }; + debugger; // NEED TO VIEW DATA window.extension = {}; fetch(`${extensionURL}catalog.js`, { referrerPolicy: "no-referrer", diff --git a/noname/ui/create/menu/pages/otherMenu.js b/noname/ui/create/menu/pages/otherMenu.js index 8c0758c40..1a28beda1 100644 --- a/noname/ui/create/menu/pages/otherMenu.js +++ b/noname/ui/create/menu/pages/otherMenu.js @@ -1267,7 +1267,7 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM const ui=window.ui; const get=window.get; const ai=window.ai; - const cheat=window.lib.cheat; + // const cheat=window.lib.cheat; // 不再允许使用 cheat,因为它是不允许访问的变量 //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。 const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/}; return function(value){ diff --git a/noname/ui/create/menu/pages/startMenu.js b/noname/ui/create/menu/pages/startMenu.js index f7acae544..5b17276eb 100644 --- a/noname/ui/create/menu/pages/startMenu.js +++ b/noname/ui/create/menu/pages/startMenu.js @@ -125,6 +125,7 @@ export const startMenu = function (connectMenu) { true ); game.switchMode(active.mode); + game.requireSandboxOn(); } clickContainer.call(cacheMenuContainer, connectMenu); } else { diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index 54ecc24ad..a0c66c97d 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -3,13 +3,13 @@ const FILE_URL = import.meta.url; /** @typedef {any} Window */ // 方便开关确定沙盒的问题喵 -const SANDBOX_ENABLED = true; +const SANDBOX_ENABLED = false; -// 暴露方法Symbol +// 暴露方法Symbol,用于类之间通信 const SandboxExposer = Symbol("Sandbox.Exposer"); // 实例暴露 const SandboxExposer2 = Symbol("Sandbox.Exposer2"); // 静态暴露 -// 暴露方法Signal +// 暴露方法Signal,类间通信信号 const SandboxSignal_InitDomain = Symbol("InitDomain"); const SandboxSignal_GetMarshalledProxy = Symbol("GetMarshalledProxy"); const SandboxSignal_SetMarshalledProxy = Symbol("SetMarshalledProxy"); @@ -192,6 +192,7 @@ class Rule { if (!this.#allowMarshal) return false; + // 存在于封送白名单或不存在于封送黑名单 if (this.#allowMarshalTo) return this.#allowMarshalTo.has(domain); else if (this.#disallowMarshalTo) @@ -290,8 +291,11 @@ class Rule { canAccess(action, ...args) { Rule.#assertOperator(this); + // 判断行为是否允许 if (!this.isGranted(action)) return false; + + // 通过权限控制器判断是否允许 if (this.#accessControl && !this.#accessControl(action, ...args)) return false; @@ -416,6 +420,9 @@ const GLOBAL_PATHES = Object.freeze([ /** * ```plain * 初始化内建对象时就需要封送的全局变量 + * + * 这些函数的成功执行依赖于browser context + * 必须要顶级域来提供给其他运行域 * ``` */ const MARSHALLED_LIST = Object.freeze([ @@ -462,6 +469,8 @@ class Globals { /** * ```plain * 解析映射路径 + * + * 如: /a/b/c => ["/a/b/c", window.a.b.c] * ``` * * @param {string|string[]} path @@ -485,6 +494,8 @@ class Globals { /** * ```plain * 解析映射路径为索引 + * + * 如: /a/b/c => [window.a.b, "c"] * ``` * * @param {string} path @@ -516,7 +527,9 @@ class Globals { const window = domain[SandboxExposer](SandboxSignal_GetWindow); const globals = [new WeakMap(), {}]; + // 检查是否是顶级域 if (Globals.#topGlobals) { + // 不是顶级域则封送 `MARSHALLED_LIST` 的对象 const marshalleds = Globals.#topGlobals[2]; for (const path of MARSHALLED_LIST) { @@ -524,6 +537,7 @@ class Globals { obj[key] = trapMarshal(Domain.topDomain, domain, marshalleds[path]); } } else { + // 否则将 `MARSHALLED_LIST` 的对象保存 // @ts-ignore Globals.#topGlobals = globals; globals.push({}); @@ -537,10 +551,12 @@ class Globals { globals[2][key] = obj; } + // 另外构造内建对象表 for (const key of Reflect.ownKeys(window)) Globals.#builtinKeys[key] = true; } + // 构建全局变量映射 for (const path of GLOBAL_PATHES) { const [key, obj] = Globals.parseFrom(path, window); @@ -681,8 +697,10 @@ class NativeWrapper { * @param {Window} global */ static wrapInDomains(global) { + // 保存当前域的Function构造函数用于后续构建原型链 NativeWrapper.#currentFunction = global.Function; + // 封装所有函数 for (const selector of wrappingFunctions) NativeWrapper.wrapFunctions(global, selector); @@ -714,12 +732,16 @@ class NativeWrapper { const pathes = [items]; const indexes = []; + // 将所有路径转换为索引 + // 如: /a/b/c => [window.a.b, "c"] while (pathes.length) { /** @type {Array} */ // @ts-ignore const path = pathes.shift(); + // 如果已经是长度为二了 if (path.length == 2) { + // 最后一项如果不是正则表达式直接添加为索引 if (!(path[1] instanceof RegExp)) { if (path[1] in path[0]) indexes.push(path); @@ -727,6 +749,7 @@ class NativeWrapper { continue; } + // 否则需要遍历添加索引 const root = path[0]; const pattern = path[1]; indexes.push(...Reflect.ownKeys(root) @@ -739,9 +762,11 @@ class NativeWrapper { continue; } + // 如果下一个键不是正则表达式 if (!(path[1] instanceof RegExp)) { const root = path.shift(); + // 向下索引,并将 `__proto__` 改为原型获取 if (path[0] === "__proto__") path[0] = Reflect.getPrototypeOf(root); else @@ -750,10 +775,13 @@ class NativeWrapper { if (!path[0]) continue; + // 添加新的路径 pathes.push(path); continue; } + // 如果下一个键是正则表达式 + // 此时需要遍历向下索引 const root = path.shift(); const pattern = path.shift(); const keys = Reflect.ownKeys(root) @@ -765,10 +793,12 @@ class NativeWrapper { if (!keys.length) continue; + // 添加新的路径 pathes.push(...keys .map(k => [root[k], ...path])); } + // 根据索引进行封装 for (const index of indexes) // @ts-ignore NativeWrapper.wrapFunction(global, ...index, flags); @@ -786,6 +816,7 @@ class NativeWrapper { */ static wrapFunction(global, parent, name, flags) { if (flags & 1) { + // 如果路径结尾是 `*`,代表需要封装访问器(getter与setter) const descriptor = Reflect.getOwnPropertyDescriptor(parent, name); if (!descriptor @@ -793,6 +824,7 @@ class NativeWrapper { || typeof descriptor.set != "function") throw new TypeError("不支持的HTML实现"); + // 封装访问器 descriptor.get = NativeWrapper.wrapGetter(descriptor.get); descriptor.set = NativeWrapper.wrapSetter(descriptor.set); Reflect.defineProperty(parent, name, descriptor); @@ -803,9 +835,11 @@ class NativeWrapper { return; if (defaultFunction.prototype) { + // 如果此函数是一个构造函数 const wrappedApply = NativeWrapper.wrapApply(defaultFunction, flags); const wrappedConstruct = NativeWrapper.wrapConstruct(defaultFunction, flags); + // 使用代理封装 parent[name] = new Proxy(defaultFunction, { apply(target, thisArg, argArray) { return Reflect.apply(wrappedApply, thisArg, argArray); @@ -815,6 +849,7 @@ class NativeWrapper { }, }); } else { + // 否则直接进行封装 parent[name] = NativeWrapper.wrapApply( global === parent ? defaultFunction.bind(null) @@ -833,9 +868,13 @@ class NativeWrapper { * @returns {Function} */ static wrapApply(func, flags = 0) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; + // 根据是否装箱进行不同的封装 const wrapped = (flags & 2) ? function (...args) { - const list = args.map(a => NativeWrapper.boxCallback(a)); + const list = args.map(a => + NativeWrapper.boxCallback(a, prototype)); // @ts-ignore return ContextInvoker1(func, this, list); } @@ -844,10 +883,9 @@ class NativeWrapper { return ContextInvoker1(func, this, args); }; + // 构造原型链 sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, - // @ts-ignore - NativeWrapper.#currentFunction.prototype); + Reflect.setPrototypeOf(wrapped, prototype); return wrapped; } @@ -861,19 +899,22 @@ class NativeWrapper { * @returns {Function} */ static wrapConstruct(func, flags = 0) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; + // 根据是否装箱进行不同的封装 const wrapped = (flags & 2) ? function (...args) { - const list = args.map(a => NativeWrapper.boxCallback(a)); + const list = args.map(a => + NativeWrapper.boxCallback(a, prototype)); return ContextInvoker2(func, list, new.target); } : function (...args) { return ContextInvoker2(func, args, new.target); }; + // 构造原型链 sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, - // @ts-ignore - NativeWrapper.#currentFunction.prototype); + Reflect.setPrototypeOf(wrapped, prototype); return wrapped; } @@ -886,15 +927,16 @@ class NativeWrapper { * @returns {(...args: any[]) => any} */ static wrapGetter(func) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; const wrapped = function () { // @ts-ignore return NativeWrapper.unboxCallback(ContextInvoker1(func, this, [])); }; + // 构造原型链 sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, - // @ts-ignore - NativeWrapper.#currentFunction.prototype); + Reflect.setPrototypeOf(wrapped, prototype); return wrapped; } @@ -907,15 +949,17 @@ class NativeWrapper { * @returns {(...args: any[]) => any} */ static wrapSetter(func) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; const wrapped = function (value) { // @ts-ignore - return ContextInvoker1(func, this, [NativeWrapper.boxCallback(value)]); + return ContextInvoker1(func, this, + [NativeWrapper.boxCallback(value, prototype)]); }; + // 构造原型链 sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, - // @ts-ignore - NativeWrapper.#currentFunction.prototype); + Reflect.setPrototypeOf(wrapped, prototype); return wrapped; } @@ -925,15 +969,18 @@ class NativeWrapper { * ``` * * @param {Proxy} unboxed + * @param {Function} prototype * @returns */ - static boxCallback(unboxed) { + static boxCallback(unboxed, prototype) { if (typeof unboxed != "function") return unboxed; + // 读取缓存 let wrapped = NativeWrapper.#boxedMap.get(unboxed); if (!wrapped) { + // 缓存不存在则创建 wrapped = ContextInvokerCreator({ unboxed, // 向封装函数提供unboxed函数 }, function (thiz, args, newTarget) { @@ -950,10 +997,9 @@ class NativeWrapper { return unboxed; }; + // 构造原型链 sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, - // @ts-ignore - NativeWrapper.#currentFunction.prototype); + Reflect.setPrototypeOf(wrapped, prototype); NativeWrapper.#boxedMap.set(unboxed, wrapped); } @@ -973,12 +1019,14 @@ class NativeWrapper { if (!NativeWrapper.#boxedSet.has(boxed)) return boxed; + // 通过暴露器获取原始函数 return boxed[SandboxExposer] (NativeWrapper.#unboxedFunction); } } // 执行上下文传递函数,请勿动喵 +// 用于传递顶级execute context /** @type {(target: Function, thiz: Object, args: Array) => any} */ // @ts-ignore @@ -1028,6 +1076,7 @@ class DomainMonitors { * @param {Monitor} monitor */ static #installMonitor = function (thiz, monitor) { + // 解构 Monitor 相关条件 // @ts-ignore const [ actions, @@ -1050,6 +1099,7 @@ class DomainMonitors { const domainList = []; if (!allowDomains) { + // 取运行域补集 // @ts-ignore const totalDomains = new Set(Domain[SandboxExposer2] (SandboxSignal_ListDomain)); @@ -1063,12 +1113,14 @@ class DomainMonitors { } else domainList.push(...allowDomains); + // 根据允许的运行域安装 Monitor for (const domain of domainList) { let actionMap = thiz.#monitorsMap.get(domain); if (!actionMap) thiz.#monitorsMap.set(domain, actionMap = {}); + // 根据 actions 添加到不同的触发器集合 addToActionMap(actionMap); } } @@ -1082,6 +1134,7 @@ class DomainMonitors { * @param {Monitor} monitor */ static #uninstallMonitor = function (thiz, monitor) { + // 解构 Monitor 相关条件 // @ts-ignore const [ actions, @@ -1104,6 +1157,7 @@ class DomainMonitors { const domainList = []; if (!allowDomains) { + // 取运行域补集 // @ts-ignore const totalDomains = new Set(Domain[SandboxExposer2] (SandboxSignal_ListDomain)); @@ -1116,12 +1170,14 @@ class DomainMonitors { } else domainList.push(...allowDomains); + // 根据允许的运行域卸载 Monitor for (const domain of domainList) { const actionMap = thiz.#monitorsMap.get(domain); if (!actionMap) continue; + // 根据 actions 从不同的触发器集合移除 removeFromActionMap(actionMap); } } @@ -1161,12 +1217,14 @@ class DomainMonitors { static #handleNewDomain = function (thiz, domain) { let actionMap = thiz.#monitorsMap.get(domain); + // 遍历所有启用的 Monitor for (const monitor of // @ts-ignore Monitor[SandboxExposer2](SandboxSignal_ListMonitor)) { if (monitor.domain === domain) continue; + // 解构 Monitor 相关条件 const [ actions, allowDomains, @@ -1174,6 +1232,7 @@ class DomainMonitors { ] = Monitor[SandboxExposer2] (SandboxSignal_ExposeInfo, monitor); + // 判断新增的 Domain 是否是 Monitor 监听的目标 if (allowDomains && !allowDomains.has(domain)) continue; @@ -1181,6 +1240,7 @@ class DomainMonitors { && disallowDomains.has(domain)) continue; + // 根据 actions 添加到不同的触发器集合 if (!actionMap) thiz.#monitorsMap.set(domain, actionMap = {}); @@ -1232,6 +1292,7 @@ class DomainMonitors { const nameds = {}; let indexMap; + // 构造命名参数 switch (action) { case AccessAction.CALL: indexMap = { @@ -1300,6 +1361,7 @@ class DomainMonitors { Object.freeze(indexMap); Object.freeze(nameds); + // 获取可能的 Monitor 集合 const monitorMap = DomainMonitors.#getMonitorsBy(sourceDomain, targetDomain, action); const result = { preventDefault: false, @@ -1333,6 +1395,7 @@ class DomainMonitors { }, }); + // 遍历并尝试分发监听事件 for (const monitor of monitorMap) { Monitor[SandboxExposer2] (SandboxSignal_DiapatchMonitor, monitor, access, nameds, control); @@ -1467,6 +1530,7 @@ class Monitor { throw new TypeError("Monitor 不能监听自己"); } + // 使用黑白名单 if (this.#allowDomains) { for (const domain of domains) this.#allowDomains.add(domain); @@ -1500,6 +1564,7 @@ class Monitor { if (!(domain instanceof Domain)) throw new TypeError("无效的运行域"); + // 使用黑白名单 if (this.#disallowDomains) { for (const domain of domains) this.#disallowDomains.add(domain); @@ -1981,6 +2046,7 @@ class Marshal { if (isPrimitive(array)) return array; + // 构造目标域的数组,并逐个元素封送 const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); const newArray = new window.Array(array.length); @@ -2003,6 +2069,7 @@ class Marshal { if (isPrimitive(object)) return object; + // 构造目标域的对象,并逐个属性封送 const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); const newObject = new window.Object(); @@ -2035,6 +2102,7 @@ class Marshal { if (sourceDomain === targetDomain) return target; + // 检查基本封送条件 if (Marshal.#strictMarshal(target) || sourceDomain.isUnsafe(target)) throw new TypeError("对象无法封送"); @@ -2049,6 +2117,7 @@ class Marshal { // 错误封送 if (sourceDomain.isError(target)) { + // 把源错误对象克隆到目标运行域 const errorCtor = target.constructor; const mappedCtor = Globals.mapTo(errorCtor, sourceDomain, targetDomain); @@ -2174,6 +2243,8 @@ class Marshal { return Reflect.defineProperty(...args); }; + // `defineProperty`、`getOwnPropertyDescriptor`、`has` 都可能被JavaScript引擎重复调用 + // 故在执行之前,为避免 `trapDomain` 的警告,我们先进行一次判断 return isSourceDomain ? domainTrapAction() : Marshal.#trapDomain(sourceDomain, domainTrapAction); @@ -2496,6 +2567,7 @@ class Domain { if (!window.createRealms) throw new ReferenceError("Sandbox 载入时处于不安全运行域"); + // 创建新的运行变量域 // @ts-ignore global = createRealms(); this.#domainName = Math.random().toString(36).slice(2); @@ -2691,6 +2763,7 @@ class Domain { const links = Domain.#domainLinks; const list = []; + // 遍历查询并清除无效的运行域 for (let i = links.length - 1; i >= 0; i--) { const link = links[i].deref(); @@ -2824,9 +2897,11 @@ function trapMarshal(srcDomain, dstDomain, obj) { const domain = Domain.current; + // 如果不需要陷入,则直接封送 if (domain === srcDomain) return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); + // 否则先陷入,然后再封送 return Marshal[SandboxExposer2](SandboxSignal_TrapDomain, srcDomain, () => { return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); }); @@ -2957,6 +3032,9 @@ class Sandbox { Reflect.defineProperty(prototype, 'constructor', descriptor) } + // 封装当前运行域所有Function类型的构造函数 + // 确保沙盒代码无法访问真正的 Window 对象 + // (不过理论上说访问了也基本上没什么东西喵) rewriteCtor(defaultFunction.prototype, global.Function = new Proxy(defaultFunction, handler)); rewriteCtor(defaultGeneratorFunction.prototype, new Proxy(defaultGeneratorFunction, handler)); rewriteCtor(defaultAsyncFunction.prototype, new Proxy(defaultAsyncFunction, handler)); @@ -3114,10 +3192,13 @@ class Sandbox { undefined: undefined, }; + // 放置内建函数或类 Marshal[SandboxExposer2](SandboxSignal_TrapDomain, this.#domain, () => { for (const [k, v] of Object.entries(builtins)) { if (!v) delete builtins[k]; + + // 非类的函数应该要绑定 this 为 null if (typeof v == "function" && !("prototype" in v)) builtins[k] = v.bind(null); } @@ -3125,6 +3206,7 @@ class Sandbox { Object.assign(this.#scope, builtins); + // 对于常量我们需要重定义 for (const [k, v] of Object.entries(hardBuiltins)) { Reflect.defineProperty(this.#scope, k, { value: v, @@ -3133,15 +3215,6 @@ class Sandbox { configurable: false, }); } - - Reflect.defineProperty(this.#scope, "document", { - get: (function () { - // @ts-ignore - return this.#domainDocument; - }).bind(this), - enumerable: false, - configurable: false, - }); } /** @@ -3224,10 +3297,12 @@ class Sandbox { return argumentList; } + // 防止逃逸 if (p === Symbol.unscopables) return undefined; if (!(p in target)) { + // 暴露非内建的顶级全局变量 if (thiz.#freeAccess && !Globals.isBuiltinKey(p)) { const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); @@ -3255,6 +3330,8 @@ class Sandbox { return ((...args) => { const prevDomain = Domain.current; const domainAction = () => { + // 指定执行域 + // 方便后续新的函数来继承 Sandbox.#executingScope.push(scope); try { @@ -3361,6 +3438,7 @@ class Sandbox { let baseScope = thiz.#scope; thiz.#scope = new thiz.#domainObject(); + // 定义两个超级变量 Reflect.defineProperty(thiz.#scope, "window", { get: (function () { // @ts-ignore @@ -3369,12 +3447,22 @@ class Sandbox { enumerable: false, configurable: false, }); + Reflect.defineProperty(thiz.#scope, "document", { + get: (function () { + // @ts-ignore + return this.#domainDocument; + }).bind(thiz), + enumerable: false, + configurable: false, + }); if (!baseScope) return; + // 继承之前的变量域 const descriptors = Object.getOwnPropertyDescriptors(baseScope); delete descriptors.window; + delete descriptors.document; Object.defineProperties(thiz.#scope, descriptors); } @@ -3444,9 +3532,15 @@ const SANDBOX_EXPORT = { if (SANDBOX_ENABLED) { // 确保顶级运行域的原型链不暴露 if (window.top === window) { + // 如果当前是顶级运行域 const document = window.document; const createElement = document.createElement.bind(document); const appendChild = document.body.appendChild.bind(document.body); + + // 通过构造 iframe 来创建新的变量域 + // 我们需要确保顶级运行域的原型链不暴露 + // 为此我们从新的变量域重新载入当前脚本 + // 然后就可以直接冻结当前变量域的原型链 const iframe = createElement("iframe"); iframe.style.display = "none"; appendChild(iframe); @@ -3454,8 +3548,10 @@ if (SANDBOX_ENABLED) { if (!iframe.contentWindow) throw new ReferenceError("无法载入运行域"); + // 定义 createRealms 函数 Reflect.defineProperty(iframe.contentWindow, "createRealms", { value() { + // 通过构造 iframe 来创建新的变量域 const iframe = createElement("iframe"); iframe.style.display = "none"; appendChild(iframe); @@ -3469,6 +3565,7 @@ if (SANDBOX_ENABLED) { }, }); + // 传递顶级变量域、上下文执行器 // @ts-ignore iframe.contentWindow.replacedGlobal = window; // @ts-ignore @@ -3478,6 +3575,7 @@ if (SANDBOX_ENABLED) { // @ts-ignore iframe.contentWindow.replacedCIC = ContextInvokerCreator; + // 重新以新的变量域载入当前脚本 const script = iframe.contentWindow.document.createElement("script"); script.src = FILE_URL; script.type = "module"; @@ -3497,11 +3595,12 @@ if (SANDBOX_ENABLED) { delete iframe.contentWindow.replacedCI2; // @ts-ignore delete iframe.contentWindow.replacedCIC; + + // 从新的变量域暴露的对象获取导出 // @ts-ignore Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT); - iframe.remove(); + iframe.remove(); // 释放 iframe 与相关的 browser context - ({ // @ts-ignore AccessAction, @@ -3554,6 +3653,7 @@ if (SANDBOX_ENABLED) { // 改为此处初始化,防止多次初始化 Domain[SandboxExposer2](SandboxSignal_InitDomain); + // 向顶级运行域暴露导出 // @ts-ignore window.SANDBOX_EXPORT = Object.assign({}, SANDBOX_EXPORT); diff --git a/noname/util/security.js b/noname/util/security.js index 63ba5da3c..1dcf660a2 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -1,7 +1,11 @@ import { Sandbox, Domain, Marshal, Monitor, AccessAction, Rule, SANDBOX_ENABLED } from "./sandbox.js"; // 是否强制所有模式下使用沙盒 -const SANDBOX_FORCED = true; +const SANDBOX_FORCED = false; + +const TRUSTED_IPS = Object.freeze([ + "47.99.105.222", +]); let initialized = false; @@ -11,6 +15,7 @@ const sandboxStack = []; /** @type {WeakMap>} */ const isolatedsMap = new WeakMap(); +// noname 顶级变量 const topVariables = { lib: null, game: null, @@ -146,6 +151,18 @@ function requireSandbox() { sandBoxRequired = true; } +/** + * ```plain + * 进入沙盒运行模式 + * ``` + * + * @param {string} ip + */ +function requireSandboxOn(ip) { + if (!TRUSTED_IPS.includes(ip)) + sandBoxRequired = true; +} + /** * ```plain * 判断是否是沙盒运行模式 @@ -168,8 +185,12 @@ function isSandboxRequired() { * @returns {any} */ function _eval(x) { - if (!SANDBOX_ENABLED || !sandBoxRequired) - return new Function(x)(); + if (!SANDBOX_ENABLED || !sandBoxRequired) { + new Function(x); + const topVars = Object.assign({}, topVariables); + const vars = "_" + Math.random().toString(36).slice(2); + return new Function(vars, `with(${vars}){${x}}`)(topVars); + } // @ts-ignore return defaultSandbox.exec(x); @@ -191,9 +212,12 @@ function _exec(x, scope = {}) { scope = {}; if (!SANDBOX_ENABLED || !sandBoxRequired) { + // 如果没有沙盒,则进行简单模拟 new Function(x); - const name = "_" + Math.random().toString(36).slice(2); - return new Function(name, `with(${name}){return(()=>{"use strict";${x}})()}`)(scope); + const topVars = Object.assign({}, topVariables); + const vars = "__vars_" + Math.random().toString(36).slice(2); + const name = "__scope_" + Math.random().toString(36).slice(2); + return new Function(vars, name, `with(${vars}){with(${name}){${x}}}`)(topVars, scope); } // @ts-ignore @@ -221,8 +245,11 @@ function _exec2(x, scope = {}) { scope = {}; if (!SANDBOX_ENABLED || !sandBoxRequired) { + // 如果没有沙盒,则进行简单模拟 + // 进行语法检查 new Function(x); + // 构造拦截器 const intercepter = new Proxy(scope, { get(target, prop, receiver) { if (prop === Symbol.unscopables) @@ -232,7 +259,8 @@ function _exec2(x, scope = {}) { && !Reflect.has(window, prop)) throw new ReferenceError(`"${String(prop)}" is not defined`); - return Reflect.get(target, prop, receiver) || window[prop]; + return Reflect.get(target, prop, receiver) + || topVariables[prop] || window[prop]; }, has(target, prop) { return true; @@ -307,15 +335,18 @@ function initSecurity({ window, ]; + // 构造禁止函数调用的规则 const callRule = new Rule(); callRule.canMarshal = false; // 禁止获取函数 callRule.setGranted(AccessAction.CALL, false); // 禁止函数调用 callRule.setGranted(AccessAction.NEW, false); // 禁止函数new调用 + // 为禁止的函数设置规则 accessDenieds.filter(Boolean).forEach(o => { Marshal.setRule(o, callRule); }); + // 构造禁止访问的规则 const bannedRule = new Rule(); bannedRule.canMarshal = false; // 禁止获取 bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性 @@ -330,35 +361,43 @@ function initSecurity({ .filter(Boolean) .forEach(o => Marshal.setRule(o, bannedRule)); + // 构造禁止修改的规则 const writeRule = new Rule(); writeRule.setGranted(AccessAction.WRITE, false); // 禁止写入属性 writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性 + // 禁止修改 game.promises 的函数 Marshal.setRule(game.promises, writeRule); + // 对于 game 当中访问特定函数我们通过 Monitor 进行拦截 new Monitor() + // 如果是写入或重定义属性 .action(AccessAction.WRITE) .action(AccessAction.DEFINE) + // 如果目标是 game 的 ioFuncs 包含的所有函数 .require("target", game) .require("property", ...ioFuncs) + // 抛出异常 .then(() => { throw "禁止修改关键函数"; }) + // 让 Monitor 开始工作 .start(); // 差点忘记启动了喵 + // 现在 parsex 已经禁止传递字符串,这段 Monitor 不需要了 // 监听原型、toStringTag的更改 - const toStringTag = Symbol.toStringTag; - new Monitor() - .action(AccessAction.WRITE) - .action(AccessAction.DEFINE) - .action(AccessAction.META) - .require("property", toStringTag) - .then((access, nameds, control) => { - // 阻止原型、toStringTag的更改 - control.preventDefault(); - control.stopPropagation(); - control.setReturnValue(false); - }) - .start(); + // const toStringTag = Symbol.toStringTag; + // new Monitor() + // .action(AccessAction.WRITE) + // .action(AccessAction.DEFINE) + // .action(AccessAction.META) + // .require("property", toStringTag) + // .then((access, nameds, control) => { + // // 阻止原型、toStringTag的更改 + // control.preventDefault(); + // control.stopPropagation(); + // control.setReturnValue(false); + // }) + // .start(); initialized = true; } @@ -405,6 +444,7 @@ function getIsolateds(sandbox) { if (isolateds) return isolateds.slice(); + // 获取当前沙盒的Function类型 isolateds = Array.from(sandbox.exec(` return [ (function(){}).constructor, @@ -441,6 +481,8 @@ function loadPolyfills() { } } + // 将垫片函数的描述器复制出来 + for (const key of pfPrototypes) { const top = window[key]; @@ -475,6 +517,7 @@ function setupPolyfills(sandbox) { namespaces: polyfills.namespaces, }; + // 根据之前复制的垫片函数描述器定义垫片函数 sandbox.exec(` function definePolyfills(top, box) { for (const key in top) @@ -539,6 +582,8 @@ if (SANDBOX_ENABLED) { /** @type {typeof Function} */ let ModAsyncGeneratorFunction; + // 封装Function类型 + ModFunction = new Proxy(defaultFunction, { apply(target, thisArg, argumentsList) { if (!sandBoxRequired) @@ -630,6 +675,7 @@ const exports = { assertSafeObject, getIsolateds, requireSandbox, + requireSandboxOn, isSandboxRequired, initSecurity, eval: _eval, From 11b971c0c26c446ceb6b4b1c9a1b4dcf78121914 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sun, 26 May 2024 16:12:19 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/util/sandbox.js | 11 +++--- noname/util/security.js | 79 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index a0c66c97d..ae00c0fcb 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -3,7 +3,7 @@ const FILE_URL = import.meta.url; /** @typedef {any} Window */ // 方便开关确定沙盒的问题喵 -const SANDBOX_ENABLED = false; +const SANDBOX_ENABLED = true; // 暴露方法Symbol,用于类之间通信 const SandboxExposer = Symbol("Sandbox.Exposer"); // 实例暴露 @@ -3269,15 +3269,16 @@ class Sandbox { const executingScope = Sandbox.#executingScope[Sandbox.#executingScope.length - 1]; const scope = inheritScope && executingScope || thiz.#scope; - const contextName = Sandbox.#makeName("__context_", scope); - const argsName = Sandbox.#makeName("__args_", scope); + const contextName = Sandbox.#makeName("_", scope); + const argsName = Sandbox.#makeName("_", scope); + const applyName = Sandbox.#makeName("_", scope); const parameters = paramList ? paramList.join(", ") : ""; const writeContextAction = { exists: 0, extend: 1, all: 2 }[writeContext] || 0; let argumentList; - const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(()=>{"use strict";return(function(${parameters}){\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n}).call(${contextName}.this,...${argsName})})()}}}`); + const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(()=>{"use strict";return(${applyName}(function(${parameters}){\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n},${contextName}.this,${argsName}))})()}}}`); const domain = thiz.#domain; const domainWindow = thiz.#domainWindow; @@ -3295,6 +3296,8 @@ class Sandbox { return marshalledContext; case argsName: return argumentList; + case applyName: + return Reflect.apply; } // 防止逃逸 diff --git a/noname/util/security.js b/noname/util/security.js index 1dcf660a2..79c71b02e 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -1,7 +1,12 @@ import { Sandbox, Domain, Marshal, Monitor, AccessAction, Rule, SANDBOX_ENABLED } from "./sandbox.js"; // 是否强制所有模式下使用沙盒 -const SANDBOX_FORCED = false; +const SANDBOX_FORCED = true; +// 是否启用自动测试 +const SANDBOX_AUTOTEST = true; +// 是否禁用自动测试延迟 +// 这将放弃渲染,在游戏结束前无响应 +const SANDBOX_AUTOTEST_NODELAY = false; const TRUSTED_IPS = Object.freeze([ "47.99.105.222", @@ -289,8 +294,6 @@ function initSecurity({ }) { if (initialized) throw "security 已经被初始化过了"; - if (!SANDBOX_ENABLED) - return; topVariables.lib = lib; topVariables.game = game; @@ -300,6 +303,9 @@ function initSecurity({ topVariables._status = _status; topVariables.gnc = gnc; + if (!SANDBOX_ENABLED) + return; + loadPolyfills(); // @ts-ignore @@ -332,6 +338,14 @@ function initSecurity({ ...Object.values(game.promises), defaultEval, window.require, + window.process, + window.module, + window.exports, + window.cordova, + // @ts-ignore + window.NonameAndroidBridge, + // @ts-ignore + window.noname_shijianInterfaces, window, ]; @@ -399,6 +413,44 @@ function initSecurity({ // }) // .start(); + if (SANDBOX_AUTOTEST) { + // 一个测试循环喵 + if (SANDBOX_AUTOTEST_NODELAY) { + game.resume = () => { }; + game.pause = () => { }; + } + game.delay = game.delayx = () => { }; + game.asyncDelay = game.asyncDelayx = async () => { }; + + Reflect.defineProperty(lib.element.GameEvent.prototype, "animate", { + get: () => undefined, + set() { }, + enumerable: false, + configurable: false, + }); + + if (!lib.videos) + lib.videos = []; + + game.over = function (...args) { + if (_status.over) return; + _status.over = true; + setTimeout(() => { + if (!_status.auto) + return; + + const count = parseInt(localStorage.getItem("__sandboxTestCount") || "0"); + localStorage.setItem("__sandboxTestCount", String(count + 1)); + + localStorage.setItem( + lib.configprefix + "directstart", "true"); + game.reload(); + }, SANDBOX_AUTOTEST_NODELAY ? 5000 : 1000); + }; + + lib.arenaReady.push(() => ui.click.auto()); + } + initialized = true; } @@ -464,17 +516,30 @@ function getIsolateds(sandbox) { * ``` */ function loadPolyfills() { + function isNativeDescriptor(descriptor) { + if (typeof descriptor.value == "function" + && !nativePattern.test(descriptor.value.toString())) + return false; + if (typeof descriptor.get == "function" + && !nativePattern.test(descriptor.get.toString())) + return false; + if (typeof descriptor.set == "function" + && !nativePattern.test(descriptor.set.toString())) + return false; + + return true; + } + function copyDescriptors(top, box) { for (const key of Reflect.ownKeys(top)) { const descriptor = Reflect.getOwnPropertyDescriptor(top, key); if (!descriptor - || typeof descriptor.value !== "function") + || (typeof descriptor.value !== "function" + && !descriptor.get && !descriptor.set)) continue; - const body = descriptor.value.toString(); - - if (nativePattern.test(body)) + if (isNativeDescriptor(descriptor)) continue; box[key] = descriptor; From 0ab098cf4208b6ddd8504488f51f08b7a5cd78e0 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sun, 26 May 2024 18:28:53 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E9=A1=B6=E7=BA=A7await?= =?UTF-8?q?=E4=BB=A5=E9=80=82=E9=85=8D any} */ +// @ts-ignore +const ContextInvoker1 = (function (apply, target, thiz, args) { + return apply(target, thiz, args); +}).bind(null, Reflect.apply); + +/** @type {(target: Function, args: Array, newTarget: Function) => any} */ +// @ts-ignore +const ContextInvoker2 = (function (construct, target, args, newTarget) { + return construct(target, args, newTarget); +}).bind(null, Reflect.construct); + +/** @type {(closure: Object, target: Function) => ((...args: any[]) => any)} */ +// @ts-ignore +const ContextInvokerCreator = (function (apply, closure, target) { + return function (...args) { + return apply(target, closure, + // @ts-ignore + [this === window ? null : this, args, new.target]); + }; +}).bind(null, Reflect.apply); + +/** + * @param {string} path + * @param {string} name + */ +function replaceName(path, name) { + const index = path.lastIndexOf("/"); + return path.slice(0, index + 1) + name; +} + +const TARGET_URL = replaceName(import.meta.url, "sandbox.js"); +const SANDBOX_EXPORT = {}; + +async function initializeSandboxRealms() { + const document = window.document; + const createElement = document.createElement.bind(document); + const appendChild = document.body.appendChild.bind(document.body); + + // 通过构造 iframe 来创建新的变量域 + // 我们需要确保顶级运行域的原型链不暴露 + // 为此我们从新的变量域重新载入当前脚本 + // 然后就可以直接冻结当前变量域的原型链 + const iframe = createElement("iframe"); + iframe.style.display = "none"; + appendChild(iframe); + + if (!iframe.contentWindow) + throw new ReferenceError("无法载入运行域"); + + // 定义 createRealms 函数 + Reflect.defineProperty(iframe.contentWindow, "createRealms", { + value() { + // 通过构造 iframe 来创建新的变量域 + const iframe = createElement("iframe"); + iframe.style.display = "none"; + appendChild(iframe); + + const window = iframe.contentWindow; + if (!window) + throw new ReferenceError("顶级域已经被卸载"); + + iframe.remove(); + return window; + }, + }); + + // 传递顶级变量域、上下文执行器 + // @ts-ignore + iframe.contentWindow.replacedGlobal = window; + // @ts-ignore + iframe.contentWindow.replacedCI1 = ContextInvoker1; + // @ts-ignore + iframe.contentWindow.replacedCI2 = ContextInvoker2; + // @ts-ignore + iframe.contentWindow.replacedCIC = ContextInvokerCreator; + + // 重新以新的变量域载入当前脚本 + const script = iframe.contentWindow.document.createElement("script"); + script.src = TARGET_URL; + script.type = "module"; + + // 无法同步载入 + // script.async = false; + // script.defer = false; + + const promise = new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = reject; + }); + iframe.contentWindow.document.head.appendChild(script); + await promise; // Top Await Required Chrome 89 + + // @ts-ignore + if (!iframe.contentWindow.SANDBOX_EXPORT) + throw new ReferenceError("无法同步载入运行域"); + + // @ts-ignore + delete iframe.contentWindow.replacedGlobal; + // @ts-ignore + delete iframe.contentWindow.replacedCI1; + // @ts-ignore + delete iframe.contentWindow.replacedCI2; + // @ts-ignore + delete iframe.contentWindow.replacedCIC; + + // @ts-ignore + Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT); + iframe.remove(); +} + +export { + initializeSandboxRealms, + SANDBOX_EXPORT, +}; \ No newline at end of file diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index ae00c0fcb..a4851fc69 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -1,4 +1,9 @@ -const FILE_URL = import.meta.url; +import { SANDBOX_EXPORT } from "./initRealms.js"; + +// 很重要的事情! +// 请不要在在其他文件中import sandbox.js! +// 如果需要沙盒相关的类请用security.importSandbox()导入!!! +// 什么时候支持顶级await(Chrome 89)就可以改回去了,现在好麻烦哦 /** @typedef {any} Window */ @@ -1030,28 +1035,15 @@ class NativeWrapper { /** @type {(target: Function, thiz: Object, args: Array) => any} */ // @ts-ignore -const ContextInvoker1 = window.replacedCI1 - || (function (apply, target, thiz, args) { - return apply(target, thiz, args); - }).bind(null, Reflect.apply); +const ContextInvoker1 = window.replacedCI1; /** @type {(target: Function, args: Array, newTarget: Function) => any} */ // @ts-ignore -const ContextInvoker2 = window.replacedCI2 - || (function (construct, target, args, newTarget) { - return construct(target, args, newTarget); - }).bind(null, Reflect.construct); +const ContextInvoker2 = window.replacedCI2; /** @type {(closure: Object, target: Function) => ((...args: any[]) => any)} */ // @ts-ignore -const ContextInvokerCreator = window.replacedCIC - || (function (apply, closure, target) { - return function (...args) { - return apply(target, closure, - // @ts-ignore - [this === window ? null : this, args, new.target]); - }; - }).bind(null, Reflect.apply); +const ContextInvokerCreator = window.replacedCIC; /** * ```plain @@ -3521,89 +3513,9 @@ function sealObject(obj, freeze = Object.freeze) { } } -const SANDBOX_EXPORT = { - AccessAction, - Rule, - Monitor, - Marshal, - Domain, - Sandbox, -}; - -// TODO: 其他扩展的全局变量 - if (SANDBOX_ENABLED) { // 确保顶级运行域的原型链不暴露 if (window.top === window) { - // 如果当前是顶级运行域 - const document = window.document; - const createElement = document.createElement.bind(document); - const appendChild = document.body.appendChild.bind(document.body); - - // 通过构造 iframe 来创建新的变量域 - // 我们需要确保顶级运行域的原型链不暴露 - // 为此我们从新的变量域重新载入当前脚本 - // 然后就可以直接冻结当前变量域的原型链 - const iframe = createElement("iframe"); - iframe.style.display = "none"; - appendChild(iframe); - - if (!iframe.contentWindow) - throw new ReferenceError("无法载入运行域"); - - // 定义 createRealms 函数 - Reflect.defineProperty(iframe.contentWindow, "createRealms", { - value() { - // 通过构造 iframe 来创建新的变量域 - const iframe = createElement("iframe"); - iframe.style.display = "none"; - appendChild(iframe); - - const window = iframe.contentWindow; - if (!window) - throw new ReferenceError("顶级域已经被卸载"); - - iframe.remove(); - return window; - }, - }); - - // 传递顶级变量域、上下文执行器 - // @ts-ignore - iframe.contentWindow.replacedGlobal = window; - // @ts-ignore - iframe.contentWindow.replacedCI1 = ContextInvoker1; - // @ts-ignore - iframe.contentWindow.replacedCI2 = ContextInvoker2; - // @ts-ignore - iframe.contentWindow.replacedCIC = ContextInvokerCreator; - - // 重新以新的变量域载入当前脚本 - const script = iframe.contentWindow.document.createElement("script"); - script.src = FILE_URL; - script.type = "module"; - - const promise = new Promise((resolve, reject) => { - script.onload = resolve; - script.onerror = reject; - }); - iframe.contentWindow.document.head.appendChild(script); - await promise; - - // @ts-ignore - delete iframe.contentWindow.replacedGlobal; - // @ts-ignore - delete iframe.contentWindow.replacedCI1; - // @ts-ignore - delete iframe.contentWindow.replacedCI2; - // @ts-ignore - delete iframe.contentWindow.replacedCIC; - - // 从新的变量域暴露的对象获取导出 - // @ts-ignore - Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT); - iframe.remove(); // 释放 iframe 与相关的 browser context - ({ // @ts-ignore AccessAction, @@ -3658,8 +3570,14 @@ if (SANDBOX_ENABLED) { // 向顶级运行域暴露导出 // @ts-ignore - window.SANDBOX_EXPORT = - Object.assign({}, SANDBOX_EXPORT); + window.SANDBOX_EXPORT = { + AccessAction, + Rule, + Monitor, + Marshal, + Domain, + Sandbox, + }; } } diff --git a/noname/util/security.js b/noname/util/security.js index 79c71b02e..f4755ff04 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -1,5 +1,3 @@ -import { Sandbox, Domain, Marshal, Monitor, AccessAction, Rule, SANDBOX_ENABLED } from "./sandbox.js"; - // 是否强制所有模式下使用沙盒 const SANDBOX_FORCED = true; // 是否启用自动测试 @@ -12,11 +10,37 @@ const TRUSTED_IPS = Object.freeze([ "47.99.105.222", ]); +/** @type {boolean} */ +let SANDBOX_ENABLED = true; +/** @type {typeof import("./sandbox.js").AccessAction} */ +let AccessAction; +/** @type {typeof import("./sandbox.js").Domain} */ +let Domain; +/** @type {typeof import("./sandbox.js").Marshal} */ +let Marshal; +/** @type {typeof import("./sandbox.js").Monitor} */ +let Monitor; +/** @type {typeof import("./sandbox.js").Rule} */ +let Rule; +/** @type {typeof import("./sandbox.js").Sandbox} */ +let Sandbox; + +/** @typedef {import("./sandbox.js").AccessAction} AccessAction */ +/** @typedef {import("./sandbox.js").Domain} Domain */ +/** @typedef {import("./sandbox.js").Marshal} Marshal */ +/** @typedef {import("./sandbox.js").Monitor} Monitor */ +/** @typedef {import("./sandbox.js").Rule} Rule */ +/** @typedef {import("./sandbox.js").Sandbox} Sandbox */ + +/** @type {boolean} */ let initialized = false; +/** @type {Sandbox} */ +let defaultSandbox; /** @type {Array} */ const sandboxStack = []; +// 沙盒Function类型缓存 /** @type {WeakMap>} */ const isolatedsMap = new WeakMap(); @@ -46,6 +70,16 @@ const polyfills = { namespaces: {}, }; +// 被封装的Function类型 +/** @type {typeof Function} */ +let ModFunction; +/** @type {typeof Function} */ +let ModGeneratorFunction; +/** @type {typeof Function} */ +let ModAsyncFunction; +/** @type {typeof Function} */ +let ModAsyncGeneratorFunction; + /** * ```plain * 将一个沙盒作为当前联网传输的运行沙盒 @@ -283,7 +317,12 @@ function _exec2(x, scope = {}) { return scope; } -function initSecurity({ +/** + * ```plain + * 初始化模块 + * ``` + */ +async function initSecurity({ lib, game, ui, @@ -295,6 +334,15 @@ function initSecurity({ if (initialized) throw "security 已经被初始化过了"; + const sandbox = await import("./sandbox.js"); + SANDBOX_ENABLED = sandbox.SANDBOX_ENABLED; + AccessAction = sandbox.AccessAction; + Domain = sandbox.Domain; + Marshal = sandbox.Marshal; + Monitor = sandbox.Monitor; + Rule = sandbox.Rule; + Sandbox = sandbox.Sandbox; + topVariables.lib = lib; topVariables.game = game; topVariables.ui = ui; @@ -307,11 +355,7 @@ function initSecurity({ return; loadPolyfills(); - - // @ts-ignore - Object.assign(defaultSandbox.scope, topVariables); - // @ts-ignore - setupPolyfills(defaultSandbox); + initIsolatedEnvironment(); // 不允许被远程代码访问的game函数 const ioFuncs = [ @@ -510,6 +554,181 @@ function getIsolateds(sandbox) { return isolateds.slice(); } +/** + * ```plain + * 根据传入对象的运行域获取对应的Function类型 + * ``` + * + * @param {Object} item + * @returns {Array} + */ +function getIsolatedsFrom(item) { + const domain = Marshal.getMarshalledDomain(item) || Domain.caller; + + // 非顶级域调用情况下我们替换掉Function类型 + if (domain && domain !== Domain.topDomain) { + const box = Sandbox.from(domain); + + if (!box) + throw "意外的运行域: 运行域没有绑定沙盒"; + + return getIsolateds(box); + } + + return [ + ModFunction, + ModGeneratorFunction, + ModAsyncFunction, + ModAsyncGeneratorFunction, + ]; +} + +/** + * ```plain + * 导入 `sandbox.js` 的相关类 + * ``` + * + * @returns {{ + * AccessAction: typeof import("./sandbox.js").AccessAction, + * Domain: typeof import("./sandbox.js").Domain, + * Marshal: typeof import("./sandbox.js").Marshal, + * Monitor: typeof import("./sandbox.js").Monitor, + * Rule: typeof import("./sandbox.js").Rule, + * Sandbox: typeof import("./sandbox.js").Sandbox, + * }} + */ +function importSandbox() { + if (!AccessAction) + throw new ReferenceError("sandbox.js 还没有被载入"); + + return { + AccessAction, + Domain, + Marshal, + Monitor, + Rule, + Sandbox, + }; +} + +/** + * ```plain + * 初始化顶级域的Funcion类型封装 + * ``` + */ +function initIsolatedEnvironment() { + /** @type {typeof Function} */ + // @ts-ignore + const defaultFunction = function () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultGeneratorFunction = function* () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultAsyncFunction = async function () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultAsyncGeneratorFunction = async function* () { }.constructor; + + // @ts-ignore + defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵 + + // @ts-ignore + // 对于 defaultSandbox 我们要补充一些东西喵 + defaultSandbox.scope.localStorage = localStorage; + + // 对Function类型进行包裹 + /** @type {Array} */ + const [ + IsolatedFunction, + IsolatedGeneratorFunction, + IsolatedAsyncFunction, + IsolatedAsyncGeneratorFunction, + ] + // @ts-ignore + = getIsolateds(defaultSandbox); + + // 封装Function类型 + + ModFunction = new Proxy(defaultFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedFunction(...argumentsList); + }, + }); + + /** @type {typeof Function} */ + ModGeneratorFunction = new Proxy(defaultGeneratorFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedGeneratorFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedGeneratorFunction(...argumentsList); + }, + }); + + /** @type {typeof Function} */ + ModAsyncFunction = new Proxy(defaultAsyncFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncFunction(...argumentsList); + }, + }); + + /** @type {typeof Function} */ + ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncGeneratorFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); + + return new IsolatedAsyncGeneratorFunction(...argumentsList); + }, + }); + + function rewriteCtor(prototype, newCtor) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') + || { configurable: true, writable: true, enumerable: false }; + if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); + descriptor.value = newCtor; + Reflect.defineProperty(prototype, 'constructor', descriptor); + } + + // 覆盖所有的Function类型构造函数 + window.Function = ModFunction; + rewriteCtor(defaultFunction.prototype, ModFunction); + rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction); + rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction); + rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction); +} + /** * ```plain * 加载当前的垫片函数 @@ -607,127 +826,6 @@ function setupPolyfills(sandbox) { `, context); } -/** @type {typeof Function} */ -// @ts-ignore -const defaultFunction = function () { }.constructor; -/** @type {typeof Function} */ -// @ts-ignore -const defaultGeneratorFunction = function* () { }.constructor; -/** @type {typeof Function} */ -// @ts-ignore -const defaultAsyncFunction = async function () { }.constructor; -/** @type {typeof Function} */ -// @ts-ignore -const defaultAsyncGeneratorFunction = async function* () { }.constructor; - -const defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵 - -if (SANDBOX_ENABLED) { - // @ts-ignore - // 对于 defaultSandbox 我们要补充一些东西喵 - defaultSandbox.scope.localStorage = localStorage; - - // 对Function类型进行包裹 - /** @type {Array} */ - const [ - IsolatedFunction, - IsolatedGeneratorFunction, - IsolatedAsyncFunction, - IsolatedAsyncGeneratorFunction, - ] - // @ts-ignore - = getIsolateds(defaultSandbox); - - /** @type {typeof Function} */ - let ModFunction; - /** @type {typeof Function} */ - let ModGeneratorFunction; - /** @type {typeof Function} */ - let ModAsyncFunction; - /** @type {typeof Function} */ - let ModAsyncGeneratorFunction; - - // 封装Function类型 - - ModFunction = new Proxy(defaultFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedFunction(...argumentsList); - }, - }); - - /** @type {typeof Function} */ - ModGeneratorFunction = new Proxy(defaultGeneratorFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedGeneratorFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedGeneratorFunction(...argumentsList); - }, - }); - - /** @type {typeof Function} */ - ModAsyncFunction = new Proxy(defaultAsyncFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedAsyncFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedAsyncFunction(...argumentsList); - }, - }); - - /** @type {typeof Function} */ - ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedAsyncGeneratorFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); - - return new IsolatedAsyncGeneratorFunction(...argumentsList); - }, - }); - - function rewriteCtor(prototype, newCtor) { - const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') - || { configurable: true, writable: true, enumerable: false }; - if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); - descriptor.value = newCtor; - Reflect.defineProperty(prototype, 'constructor', descriptor); - } - - // 覆盖所有的Function类型构造函数 - window.Function = ModFunction; - rewriteCtor(defaultFunction.prototype, ModFunction); - rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction); - rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction); - rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction); -} - // 测试暴露喵 // window.sandbox = defaultSandbox; @@ -739,6 +837,8 @@ const exports = { isUnsafeObject, assertSafeObject, getIsolateds, + getIsolatedsFrom, + importSandbox, requireSandbox, requireSandboxOn, isSandboxRequired, From 271b8039a4f0bc6f421c333d098b6e0fe912bec0 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Sun, 26 May 2024 21:01:20 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E6=9B=B4=E6=8D=A2=E7=BC=A9=E8=BF=9B?= =?UTF-8?q?=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/util/sandbox.js | 6054 ++++++++++++++++++++------------------- noname/util/security.js | 974 +++---- 2 files changed, 3523 insertions(+), 3505 deletions(-) diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index a4851fc69..dcb0a314d 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -41,7 +41,7 @@ const SandboxSignal_ExposeInfo = Symbol("ExposeInfo"); * @returns {boolean} */ function isPrimitive(obj) { - return Object(obj) !== obj; + return Object(obj) !== obj; } /** @@ -52,54 +52,54 @@ function isPrimitive(obj) { * ``` */ class AccessAction { - // static CALL = 0; // apply - // static NEW = 1; // construct - // static READ = 2; // get - // static WRITE = 3; // set - // static DESCRIBE = 4; // getOwnPropertyDescriptor - // static DEFINE = 5; // defineProperty - // static TRACE = 6; // getPrototypeOf - // static META = 7; // setPrototypeOf - // static SEAL = 8; // preventExtensions - // static EXISTS = 9; // has - // static LIST = 10; // ownKeys - // static DELETE = 11; // delete + // static CALL = 0; // apply + // static NEW = 1; // construct + // static READ = 2; // get + // static WRITE = 3; // set + // static DESCRIBE = 4; // getOwnPropertyDescriptor + // static DEFINE = 5; // defineProperty + // static TRACE = 6; // getPrototypeOf + // static META = 7; // setPrototypeOf + // static SEAL = 8; // preventExtensions + // static EXISTS = 9; // has + // static LIST = 10; // ownKeys + // static DELETE = 11; // delete - /** ```Reflect.apply``` */ - static CALL = 0; - /** ```Reflect.construct``` */ - static NEW = 1; - /** ```Reflect.get``` */ - static READ = 2; - /** ```Reflect.set ``` */ - static WRITE = 3; - /** ```Reflect.getOwnPropertyDescriptor``` */ - static DESCRIBE = 4; - /** ```Reflect.defineProperty``` */ - static DEFINE = 5; - /** ```Reflect.getPrototypeOf``` */ - static TRACE = 6; - /** ```Reflect.setPrototypeOf``` */ - static META = 7; - /** ```Reflect.preventExtensions``` */ - static SEAL = 8; - /** ```Reflect.has``` */ - static EXISTS = 9; - /** ```Reflect.ownKeys``` */ - static LIST = 10; - /** ```Reflect.delete``` */ - static DELETE = 11 + /** ```Reflect.apply``` */ + static CALL = 0; + /** ```Reflect.construct``` */ + static NEW = 1; + /** ```Reflect.get``` */ + static READ = 2; + /** ```Reflect.set ``` */ + static WRITE = 3; + /** ```Reflect.getOwnPropertyDescriptor``` */ + static DESCRIBE = 4; + /** ```Reflect.defineProperty``` */ + static DEFINE = 5; + /** ```Reflect.getPrototypeOf``` */ + static TRACE = 6; + /** ```Reflect.setPrototypeOf``` */ + static META = 7; + /** ```Reflect.preventExtensions``` */ + static SEAL = 8; + /** ```Reflect.has``` */ + static EXISTS = 9; + /** ```Reflect.ownKeys``` */ + static LIST = 10; + /** ```Reflect.delete``` */ + static DELETE = 11 - /** - * 判断给定的action是否是AccessAction - * - * @param {number} action - * @returns - */ - static isAccessAction(action) { - return typeof action == "number" - && action >= 0 && action < 12; - } + /** + * 判断给定的action是否是AccessAction + * + * @param {number} action + * @returns + */ + static isAccessAction(action) { + return typeof action == "number" + && action >= 0 && action < 12; + } } /** @@ -112,222 +112,222 @@ class AccessAction { * ``` */ class Rule { - /** @type {Domain} */ - #domain; - /** @type {boolean} */ - #allowMarshal = true; + /** @type {Domain} */ + #domain; + /** @type {boolean} */ + #allowMarshal = true; - /** @type {WeakSet?} */ - #allowMarshalTo = null; - /** @type {WeakSet?} */ - #disallowMarshalTo = null; + /** @type {WeakSet?} */ + #allowMarshalTo = null; + /** @type {WeakSet?} */ + #disallowMarshalTo = null; - /** @type {boolean[]} */ - #permissions = new Array(12).fill(true); + /** @type {boolean[]} */ + #permissions = new Array(12).fill(true); - /** @type {((...args: any[]) => boolean)?} */ - #accessControl = null; + /** @type {((...args: any[]) => boolean)?} */ + #accessControl = null; - /** - * ```plain - * 创建一个封送规则 - * ``` - * - * @param {Rule?} rule - */ - constructor(rule = null) { - this.#domain = Domain.current; + /** + * ```plain + * 创建一个封送规则 + * ``` + * + * @param {Rule?} rule + */ + constructor(rule = null) { + this.#domain = Domain.current; - if (rule instanceof Rule) { - this.#allowMarshal = rule.#allowMarshal; - this.#allowMarshalTo = rule.#allowMarshalTo; - this.#disallowMarshalTo = rule.#disallowMarshalTo; - this.#permissions = rule.#permissions.slice(); - this.#accessControl = rule.#accessControl; - } - } + if (rule instanceof Rule) { + this.#allowMarshal = rule.#allowMarshal; + this.#allowMarshalTo = rule.#allowMarshalTo; + this.#disallowMarshalTo = rule.#disallowMarshalTo; + this.#permissions = rule.#permissions.slice(); + this.#accessControl = rule.#accessControl; + } + } - /** - * ```plain - * 检查当前是否是 Monitor 所属的运行域 - * ``` - * - * @param {Rule} thiz - */ - static #assertOperator = function (thiz) { - if (thiz.#domain !== Domain.current) - throw new Error("当前不是 Rule 所属的运行域"); - } + /** + * ```plain + * 检查当前是否是 Monitor 所属的运行域 + * ``` + * + * @param {Rule} thiz + */ + static #assertOperator = function (thiz) { + if (thiz.#domain !== Domain.current) + throw new Error("当前不是 Rule 所属的运行域"); + } - /** - * ```plain - * 是否允许对象进行封送 - * ``` - * - * @type {boolean} - */ - get canMarshal() { - Rule.#assertOperator(this); - return this.#allowMarshal; - } + /** + * ```plain + * 是否允许对象进行封送 + * ``` + * + * @type {boolean} + */ + get canMarshal() { + Rule.#assertOperator(this); + return this.#allowMarshal; + } - /** - * ```plain - * 是否允许对象进行封送 - * ``` - * - * @type {boolean} - */ - set canMarshal(newValue) { - Rule.#assertOperator(this); - this.#allowMarshal = !!newValue; - } + /** + * ```plain + * 是否允许对象进行封送 + * ``` + * + * @type {boolean} + */ + set canMarshal(newValue) { + Rule.#assertOperator(this); + this.#allowMarshal = !!newValue; + } - /** - * ```plain - * 检查当前的规则是否允许封送到指定的运行域 - * ``` - * - * @param {Domain} domain - * @returns {boolean} - */ - canMarshalTo(domain) { - Rule.#assertOperator(this); + /** + * ```plain + * 检查当前的规则是否允许封送到指定的运行域 + * ``` + * + * @param {Domain} domain + * @returns {boolean} + */ + canMarshalTo(domain) { + Rule.#assertOperator(this); - if (!this.#allowMarshal) - return false; + if (!this.#allowMarshal) + return false; - // 存在于封送白名单或不存在于封送黑名单 - if (this.#allowMarshalTo) - return this.#allowMarshalTo.has(domain); - else if (this.#disallowMarshalTo) - return !this.#disallowMarshalTo.has(domain); + // 存在于封送白名单或不存在于封送黑名单 + if (this.#allowMarshalTo) + return this.#allowMarshalTo.has(domain); + else if (this.#disallowMarshalTo) + return !this.#disallowMarshalTo.has(domain); - return true; - } + return true; + } - /** - * ```plain - * 将特定的运行域添加到当前对象的封送白名单 - * - * 请注意,封送白名单与黑名单不能同时指定 - * ``` - * - * @param {Domain} domain - */ - allowMarshalTo(domain) { - Rule.#assertOperator(this); + /** + * ```plain + * 将特定的运行域添加到当前对象的封送白名单 + * + * 请注意,封送白名单与黑名单不能同时指定 + * ``` + * + * @param {Domain} domain + */ + allowMarshalTo(domain) { + Rule.#assertOperator(this); - if (!this.#allowMarshalTo) { - if (this.#disallowMarshalTo) - throw new TypeError("封送黑名单与封送白名单不能同时存在"); + if (!this.#allowMarshalTo) { + if (this.#disallowMarshalTo) + throw new TypeError("封送黑名单与封送白名单不能同时存在"); - this.#allowMarshalTo = new WeakSet(); - } + this.#allowMarshalTo = new WeakSet(); + } - this.#allowMarshalTo.add(domain); - } + this.#allowMarshalTo.add(domain); + } - /** - * ```plain - * 将特定的运行域添加到当前对象的封送黑名单 - * - * 请注意,封送白名单与黑名单不能同时指定 - * ``` - * - * @param {Domain} domain - */ - disallowMarshalTo(domain) { - Rule.#assertOperator(this); + /** + * ```plain + * 将特定的运行域添加到当前对象的封送黑名单 + * + * 请注意,封送白名单与黑名单不能同时指定 + * ``` + * + * @param {Domain} domain + */ + disallowMarshalTo(domain) { + Rule.#assertOperator(this); - if (!this.#disallowMarshalTo) { - if (this.#allowMarshalTo) - throw new TypeError("封送黑名单与封送白名单不能同时存在"); + if (!this.#disallowMarshalTo) { + if (this.#allowMarshalTo) + throw new TypeError("封送黑名单与封送白名单不能同时存在"); - this.#disallowMarshalTo = new WeakSet(); - } + this.#disallowMarshalTo = new WeakSet(); + } - this.#disallowMarshalTo.add(domain); - } + this.#disallowMarshalTo.add(domain); + } - /** - * ```plain - * 检查给定的AccessAction是否被允许 - * ``` - * - * @param {number} action - * @returns {boolean} - */ - isGranted(action) { - Rule.#assertOperator(this); + /** + * ```plain + * 检查给定的AccessAction是否被允许 + * ``` + * + * @param {number} action + * @returns {boolean} + */ + isGranted(action) { + Rule.#assertOperator(this); - if (!AccessAction.isAccessAction(action)) - throw new TypeError("参数 action 不是一个有效的操作"); + if (!AccessAction.isAccessAction(action)) + throw new TypeError("参数 action 不是一个有效的操作"); - return this.#permissions[action]; - } + return this.#permissions[action]; + } - /** - * ```plain - * 指定给定的AccessAction是否被允许 - * ``` - * - * @param {number} action - * @param {boolean} granted - */ - setGranted(action, granted) { - Rule.#assertOperator(this); + /** + * ```plain + * 指定给定的AccessAction是否被允许 + * ``` + * + * @param {number} action + * @param {boolean} granted + */ + setGranted(action, granted) { + Rule.#assertOperator(this); - if (!AccessAction.isAccessAction(action)) - throw new TypeError("参数 action 不是一个有效的操作"); + if (!AccessAction.isAccessAction(action)) + throw new TypeError("参数 action 不是一个有效的操作"); - this.#permissions[action] = !!granted; - } + this.#permissions[action] = !!granted; + } - /** - * ```plain - * 判断在给定的AccessAction与指定的参数下是否允许访问 - * ``` - * - * @param {number} action - * @param {...any} args - * @returns {boolean} - */ - canAccess(action, ...args) { - Rule.#assertOperator(this); + /** + * ```plain + * 判断在给定的AccessAction与指定的参数下是否允许访问 + * ``` + * + * @param {number} action + * @param {...any} args + * @returns {boolean} + */ + canAccess(action, ...args) { + Rule.#assertOperator(this); - // 判断行为是否允许 - if (!this.isGranted(action)) - return false; + // 判断行为是否允许 + if (!this.isGranted(action)) + return false; - // 通过权限控制器判断是否允许 - if (this.#accessControl - && !this.#accessControl(action, ...args)) - return false; + // 通过权限控制器判断是否允许 + if (this.#accessControl + && !this.#accessControl(action, ...args)) + return false; - return true; - } + return true; + } - /** - * ```plain - * 设置当前的权限控制器 - * - * 权限控制器形参是拦截器的对应参数 - * 返回值则控制本次访问是否允许 - * ``` - * - * @param {(...args: any[]) => boolean} accessControl - */ - setAccessControl(accessControl) { - Rule.#assertOperator(this); + /** + * ```plain + * 设置当前的权限控制器 + * + * 权限控制器形参是拦截器的对应参数 + * 返回值则控制本次访问是否允许 + * ``` + * + * @param {(...args: any[]) => boolean} accessControl + */ + setAccessControl(accessControl) { + Rule.#assertOperator(this); - if (typeof accessControl != "function") - throw new TypeError("无效的权限控制器"); - if (this.#accessControl) - throw new TypeError("权限控制器已经被设置"); + if (typeof accessControl != "function") + throw new TypeError("无效的权限控制器"); + if (this.#accessControl) + throw new TypeError("权限控制器已经被设置"); - this.#accessControl = accessControl; - } + this.#accessControl = accessControl; + } } /** @@ -352,74 +352,74 @@ class Rule { * ``` */ const GLOBAL_PATHES = Object.freeze([ - "/Object", - "/Array", - "/Promise", - "/Date", - "/String", - "/Number", - "/Boolean", - "/BigInt", - "/RegExp", - "/Symbol", - "/Error", - "/TypeError", - "/SyntaxError", - "/RangeError", - "/EvalError", - "/ReferenceError", - "/Map", - "/Set", - "/WeakRef", - "/WeakMap", - "/WeakSet", - "/Object/prototype", - "/Array/prototype", - "/Function/prototype", - "/Promise/prototype", - "/Date/prototype", - "/String/prototype", - "/Number/prototype", - "/Boolean/prototype", - "/BigInt/prototype", - "/RegExp/prototype", - "/Symbol/prototype", - "/Error/prototype", - "/TypeError/prototype", - "/SyntaxError/prototype", - "/RangeError/prototype", - "/EvalError/prototype", - "/ReferenceError/prototype", - "/Map/prototype", - "/Set/prototype", - "/WeakRef/prototype", - "/WeakMap/prototype", - "/WeakSet/prototype", - ["/Generator", "(function*(){})().constructor"], - ["/AsyncGenerator", "(async function*(){})().constructor"], - ["/Generator/prototype", "(function*(){})().constructor.prototype"], - ["/AsyncGenerator/prototype", "(async function*(){})().constructor.prototype"], - ["/GeneratorFunction/prototype", "(function*(){}).constructor.prototype"], - ["/AsyncFunction/prototype", "(async()=>{}).constructor.prototype"], - ["/AsyncGeneratorFunction/prototype", "(async function*(){}).constructor.prototype"], - "/JSON", - "/Proxy", - "/Math", - "/Reflect", - "/parseInt", - "/parseFloat", - "/isNaN", - "/isFinite", - "/alert", - "/confirm", - "/console", + "/Object", + "/Array", + "/Promise", + "/Date", + "/String", + "/Number", + "/Boolean", + "/BigInt", + "/RegExp", + "/Symbol", + "/Error", + "/TypeError", + "/SyntaxError", + "/RangeError", + "/EvalError", + "/ReferenceError", + "/Map", + "/Set", + "/WeakRef", + "/WeakMap", + "/WeakSet", + "/Object/prototype", + "/Array/prototype", + "/Function/prototype", + "/Promise/prototype", + "/Date/prototype", + "/String/prototype", + "/Number/prototype", + "/Boolean/prototype", + "/BigInt/prototype", + "/RegExp/prototype", + "/Symbol/prototype", + "/Error/prototype", + "/TypeError/prototype", + "/SyntaxError/prototype", + "/RangeError/prototype", + "/EvalError/prototype", + "/ReferenceError/prototype", + "/Map/prototype", + "/Set/prototype", + "/WeakRef/prototype", + "/WeakMap/prototype", + "/WeakSet/prototype", + ["/Generator", "(function*(){})().constructor"], + ["/AsyncGenerator", "(async function*(){})().constructor"], + ["/Generator/prototype", "(function*(){})().constructor.prototype"], + ["/AsyncGenerator/prototype", "(async function*(){})().constructor.prototype"], + ["/GeneratorFunction/prototype", "(function*(){}).constructor.prototype"], + ["/AsyncFunction/prototype", "(async()=>{}).constructor.prototype"], + ["/AsyncGeneratorFunction/prototype", "(async function*(){}).constructor.prototype"], + "/JSON", + "/Proxy", + "/Math", + "/Reflect", + "/parseInt", + "/parseFloat", + "/isNaN", + "/isFinite", + "/alert", + "/confirm", + "/console", - // 危险对象不传递 - // "/Function", - // ["/GeneratorFunction", "(function*(){}).constructor"], - // ["/AsyncFunction", "(async()=>{}).constructor"], - // ["/AsyncGeneratorFunction", "(async function*(){}).constructor"], - // "/eval", + // 危险对象不传递 + // "/Function", + // ["/GeneratorFunction", "(function*(){}).constructor"], + // ["/AsyncFunction", "(async()=>{}).constructor"], + // ["/AsyncGeneratorFunction", "(async function*(){}).constructor"], + // "/eval", ]); /** @@ -431,17 +431,17 @@ const GLOBAL_PATHES = Object.freeze([ * ``` */ const MARSHALLED_LIST = Object.freeze([ - "/setTimeout", - "/clearTimeout", - "/setInterval", - "/clearInterval", - "/setImmediate", - "/clearImmediate", - "/requestAnimationFrame", - "/cancelAnimationFrame", - "/requestIdleCallback", - "/cancelIdleCallback", - "/queueMicrotask", + "/setTimeout", + "/clearTimeout", + "/setInterval", + "/clearInterval", + "/setImmediate", + "/clearImmediate", + "/requestAnimationFrame", + "/cancelAnimationFrame", + "/requestIdleCallback", + "/cancelIdleCallback", + "/queueMicrotask", ]); /** @@ -452,178 +452,178 @@ const MARSHALLED_LIST = Object.freeze([ * ``` */ class Globals { - /** @type {[WeakMap, Object, Object]} */ - static #topGlobals; - /** @type {WeakMap} */ - static #globals = new WeakMap(); - /** @type {Object} */ - static #builtinKeys = {}; + /** @type {[WeakMap, Object, Object]} */ + static #topGlobals; + /** @type {WeakMap} */ + static #globals = new WeakMap(); + /** @type {Object} */ + static #builtinKeys = {}; - /** - * ```plain - * 判断是否是顶级域的内建对象 - * ``` - * - * @param {string|symbol} key - * @returns {boolean} - */ - static isBuiltinKey(key) { - return key in Globals.#builtinKeys; // 基于hash的存在性检查效率最高喵 - } + /** + * ```plain + * 判断是否是顶级域的内建对象 + * ``` + * + * @param {string|symbol} key + * @returns {boolean} + */ + static isBuiltinKey(key) { + return key in Globals.#builtinKeys; // 基于hash的存在性检查效率最高喵 + } - /** - * ```plain - * 解析映射路径 - * - * 如: /a/b/c => ["/a/b/c", window.a.b.c] - * ``` - * - * @param {string|string[]} path - * @param {Window} window - * @returns {[string, any]} [映射键名, 映射值] - */ - static parseFrom(path, window) { - if (typeof path == "string") { - const items = path.split("/").filter(Boolean); - let obj = window; + /** + * ```plain + * 解析映射路径 + * + * 如: /a/b/c => ["/a/b/c", window.a.b.c] + * ``` + * + * @param {string|string[]} path + * @param {Window} window + * @returns {[string, any]} [映射键名, 映射值] + */ + static parseFrom(path, window) { + if (typeof path == "string") { + const items = path.split("/").filter(Boolean); + let obj = window; - for (const item of items) - if (!(obj = obj[item])) - break; + for (const item of items) + if (!(obj = obj[item])) + break; - return [path, obj]; - } else - return [path[0], window.eval(path[1])]; - } + return [path, obj]; + } else + return [path[0], window.eval(path[1])]; + } - /** - * ```plain - * 解析映射路径为索引 - * - * 如: /a/b/c => [window.a.b, "c"] - * ``` - * - * @param {string} path - * @param {Window} window - * @returns {[Object, string]} [索引对象, 索引键名] - */ - static parseIndex(path, window) { - const items = path.split("/").filter(Boolean); - const last = items.pop(); - let obj = window; + /** + * ```plain + * 解析映射路径为索引 + * + * 如: /a/b/c => [window.a.b, "c"] + * ``` + * + * @param {string} path + * @param {Window} window + * @returns {[Object, string]} [索引对象, 索引键名] + */ + static parseIndex(path, window) { + const items = path.split("/").filter(Boolean); + const last = items.pop(); + let obj = window; - for (const item of items) - if (!(obj = obj[item])) - break; + for (const item of items) + if (!(obj = obj[item])) + break; - // @ts-ignore - return [obj, last]; - } + // @ts-ignore + return [obj, last]; + } - /** - * ```plain - * 初始化运行域的全局对象映射 - * ``` - * - * @param {Domain} domain - */ - static ensureDomainGlobals(domain) { - if (!Globals.#globals.has(domain)) { - const window = domain[SandboxExposer](SandboxSignal_GetWindow); - const globals = [new WeakMap(), {}]; + /** + * ```plain + * 初始化运行域的全局对象映射 + * ``` + * + * @param {Domain} domain + */ + static ensureDomainGlobals(domain) { + if (!Globals.#globals.has(domain)) { + const window = domain[SandboxExposer](SandboxSignal_GetWindow); + const globals = [new WeakMap(), {}]; - // 检查是否是顶级域 - if (Globals.#topGlobals) { - // 不是顶级域则封送 `MARSHALLED_LIST` 的对象 - const marshalleds = Globals.#topGlobals[2]; + // 检查是否是顶级域 + if (Globals.#topGlobals) { + // 不是顶级域则封送 `MARSHALLED_LIST` 的对象 + const marshalleds = Globals.#topGlobals[2]; - for (const path of MARSHALLED_LIST) { - const [obj, key] = Globals.parseIndex(path, window); - obj[key] = trapMarshal(Domain.topDomain, domain, marshalleds[path]); - } - } else { - // 否则将 `MARSHALLED_LIST` 的对象保存 - // @ts-ignore - Globals.#topGlobals = globals; - globals.push({}); + for (const path of MARSHALLED_LIST) { + const [obj, key] = Globals.parseIndex(path, window); + obj[key] = trapMarshal(Domain.topDomain, domain, marshalleds[path]); + } + } else { + // 否则将 `MARSHALLED_LIST` 的对象保存 + // @ts-ignore + Globals.#topGlobals = globals; + globals.push({}); - for (const path of MARSHALLED_LIST) { - const [key, obj] = Globals.parseFrom(path, window); + for (const path of MARSHALLED_LIST) { + const [key, obj] = Globals.parseFrom(path, window); - if (obj == null) - continue; + if (obj == null) + continue; - globals[2][key] = obj; - } + globals[2][key] = obj; + } - // 另外构造内建对象表 - for (const key of Reflect.ownKeys(window)) - Globals.#builtinKeys[key] = true; - } + // 另外构造内建对象表 + for (const key of Reflect.ownKeys(window)) + Globals.#builtinKeys[key] = true; + } - // 构建全局变量映射 - for (const path of GLOBAL_PATHES) { - const [key, obj] = Globals.parseFrom(path, window); + // 构建全局变量映射 + for (const path of GLOBAL_PATHES) { + const [key, obj] = Globals.parseFrom(path, window); - if (obj == null) - continue; + if (obj == null) + continue; - globals[0].set(obj, key); - globals[1][key] = obj; - } + globals[0].set(obj, key); + globals[1][key] = obj; + } - // @ts-ignore - Globals.#globals.set(domain, globals); - } - } + // @ts-ignore + Globals.#globals.set(domain, globals); + } + } - /** - * ```plain - * 将一个对象映射为全局键 - * ``` - * - * @param {Domain} domain - * @param {Object} obj - */ - static findGlobalKey(domain, obj) { - Globals.ensureDomainGlobals(domain); - const globals = Globals.#globals.get(domain); - // @ts-ignore - return globals[0].get(obj); - } + /** + * ```plain + * 将一个对象映射为全局键 + * ``` + * + * @param {Domain} domain + * @param {Object} obj + */ + static findGlobalKey(domain, obj) { + Globals.ensureDomainGlobals(domain); + const globals = Globals.#globals.get(domain); + // @ts-ignore + return globals[0].get(obj); + } - /** - * ```plain - * 将一个全局键映射为对象 - * ``` - * - * @param {Domain} domain - * @param {string} key - */ - static findGlobalObject(domain, key) { - Globals.ensureDomainGlobals(domain); - const globals = Globals.#globals.get(domain); - // @ts-ignore - return globals[1][key]; - } + /** + * ```plain + * 将一个全局键映射为对象 + * ``` + * + * @param {Domain} domain + * @param {string} key + */ + static findGlobalObject(domain, key) { + Globals.ensureDomainGlobals(domain); + const globals = Globals.#globals.get(domain); + // @ts-ignore + return globals[1][key]; + } - /** - * ```plain - * 将一个运行域的全局对象映射为另一个运行域的全局对象 - * ``` - * - * @param {Object} obj - * @param {Domain} sourceDomain - * @param {Domain} targetDomain - */ - static mapTo(obj, sourceDomain, targetDomain) { - const key = Globals.findGlobalKey(sourceDomain, obj); + /** + * ```plain + * 将一个运行域的全局对象映射为另一个运行域的全局对象 + * ``` + * + * @param {Object} obj + * @param {Domain} sourceDomain + * @param {Domain} targetDomain + */ + static mapTo(obj, sourceDomain, targetDomain) { + const key = Globals.findGlobalKey(sourceDomain, obj); - if (!key) - return undefined; + if (!key) + return undefined; - return Globals.findGlobalObject(targetDomain, key); - } + return Globals.findGlobalObject(targetDomain, key); + } } /** @@ -635,26 +635,26 @@ class Globals { * ``` */ const wrappingFunctions = [ - "/setTimeout", - "/setInterval", - "/setImmediate", - "/requestAnimationFrame", - "/requestIdleCallback", - "/queueMicrotask", - "/EventTarget/prototype/addEventListener", - "/EventTarget/prototype/removeEventListener", - [/^HTML\w*?Element$/, "prototype", /^on[a-z0-9]+$/, "*"], - ["IDBRequest", "prototype", /^on[a-z0-9]+$/, "*"], - ["XMLHttpRequestEventTarget", "prototype", /^on[a-z0-9]+$/, "*"], - "/MutationObserver", + "/setTimeout", + "/setInterval", + "/setImmediate", + "/requestAnimationFrame", + "/requestIdleCallback", + "/queueMicrotask", + "/EventTarget/prototype/addEventListener", + "/EventTarget/prototype/removeEventListener", + [/^HTML\w*?Element$/, "prototype", /^on[a-z0-9]+$/, "*"], + ["IDBRequest", "prototype", /^on[a-z0-9]+$/, "*"], + ["XMLHttpRequestEventTarget", "prototype", /^on[a-z0-9]+$/, "*"], + "/MutationObserver", - // "/HTMLCanvasElement/prototype/toBlob", - // "/DataTransferItem/prototype/getAsString", - // "/LaunchQueue/prototype/setConsumer", - // "/ResizeObserver", - // "/ReportingObserver", - // "/PerformanceObserver", - // "/IntersectionObserver", + // "/HTMLCanvasElement/prototype/toBlob", + // "/DataTransferItem/prototype/getAsString", + // "/LaunchQueue/prototype/setConsumer", + // "/ResizeObserver", + // "/ReportingObserver", + // "/PerformanceObserver", + // "/IntersectionObserver", ]; // 不支持的: @@ -668,366 +668,366 @@ const wrappingFunctions = [ * ``` */ class NativeWrapper { - static #unboxedFunction = Symbol("NativeWrapper.unboxedFunction"); + static #unboxedFunction = Symbol("NativeWrapper.unboxedFunction"); - /** @type {WeakMap} */ - static #boxedMap = new WeakMap(); - /** @type {WeakSet} */ - static #boxedSet = new WeakSet(); + /** @type {WeakMap} */ + static #boxedMap = new WeakMap(); + /** @type {WeakSet} */ + static #boxedSet = new WeakSet(); - /** @type {typeof Function?} */ - static #topFunction = null; - /** @type {typeof Function?} */ - static #currentFunction = null; + /** @type {typeof Function?} */ + static #topFunction = null; + /** @type {typeof Function?} */ + static #currentFunction = null; - /** - * ```plain - * 初始化顶级运行域的Function - * ``` - * - * @param {Window} topGlobal - */ - static initTopDomain(topGlobal) { - if (NativeWrapper.#topFunction) - throw new Error("NativeWrapper 已经初始化过了"); + /** + * ```plain + * 初始化顶级运行域的Function + * ``` + * + * @param {Window} topGlobal + */ + static initTopDomain(topGlobal) { + if (NativeWrapper.#topFunction) + throw new Error("NativeWrapper 已经初始化过了"); - NativeWrapper.#topFunction = topGlobal.Function; - } + NativeWrapper.#topFunction = topGlobal.Function; + } - /** - * ```plain - * 对某个域的原生函数进行封装 - * ``` - * - * @param {Window} global - */ - static wrapInDomains(global) { - // 保存当前域的Function构造函数用于后续构建原型链 - NativeWrapper.#currentFunction = global.Function; + /** + * ```plain + * 对某个域的原生函数进行封装 + * ``` + * + * @param {Window} global + */ + static wrapInDomains(global) { + // 保存当前域的Function构造函数用于后续构建原型链 + NativeWrapper.#currentFunction = global.Function; - // 封装所有函数 - for (const selector of wrappingFunctions) - NativeWrapper.wrapFunctions(global, selector); + // 封装所有函数 + for (const selector of wrappingFunctions) + NativeWrapper.wrapFunctions(global, selector); - NativeWrapper.#currentFunction = null; - } + NativeWrapper.#currentFunction = null; + } - /** - * ```plain - * 根据选择器对原生函数进行封装 - * ``` - * - * @param {Window} global - * @param {string|Array} selector - */ - static wrapFunctions(global, selector) { - /** @type {Array} */ - const items = Array.isArray(selector) - ? selector : selector.split("/").filter(Boolean); + /** + * ```plain + * 根据选择器对原生函数进行封装 + * ``` + * + * @param {Window} global + * @param {string|Array} selector + */ + static wrapFunctions(global, selector) { + /** @type {Array} */ + const items = Array.isArray(selector) + ? selector : selector.split("/").filter(Boolean); - let flags = 2; // 默认装箱了喵 + let flags = 2; // 默认装箱了喵 - if (items[items.length - 1] === "*") { - flags |= 1; - items.pop(); - } + if (items[items.length - 1] === "*") { + flags |= 1; + items.pop(); + } - items.unshift(global); + items.unshift(global); - const pathes = [items]; - const indexes = []; + const pathes = [items]; + const indexes = []; - // 将所有路径转换为索引 - // 如: /a/b/c => [window.a.b, "c"] - while (pathes.length) { - /** @type {Array} */ - // @ts-ignore - const path = pathes.shift(); + // 将所有路径转换为索引 + // 如: /a/b/c => [window.a.b, "c"] + while (pathes.length) { + /** @type {Array} */ + // @ts-ignore + const path = pathes.shift(); - // 如果已经是长度为二了 - if (path.length == 2) { - // 最后一项如果不是正则表达式直接添加为索引 - if (!(path[1] instanceof RegExp)) { - if (path[1] in path[0]) - indexes.push(path); + // 如果已经是长度为二了 + if (path.length == 2) { + // 最后一项如果不是正则表达式直接添加为索引 + if (!(path[1] instanceof RegExp)) { + if (path[1] in path[0]) + indexes.push(path); - continue; - } + continue; + } - // 否则需要遍历添加索引 - const root = path[0]; - const pattern = path[1]; - indexes.push(...Reflect.ownKeys(root) - .filter(k => pattern.test( - typeof k == "string" - ? k : `@${k.description}`)) - .filter(k => k in root) - .map(k => [root, k])); + // 否则需要遍历添加索引 + const root = path[0]; + const pattern = path[1]; + indexes.push(...Reflect.ownKeys(root) + .filter(k => pattern.test( + typeof k == "string" + ? k : `@${k.description}`)) + .filter(k => k in root) + .map(k => [root, k])); - continue; - } + continue; + } - // 如果下一个键不是正则表达式 - if (!(path[1] instanceof RegExp)) { - const root = path.shift(); + // 如果下一个键不是正则表达式 + if (!(path[1] instanceof RegExp)) { + const root = path.shift(); - // 向下索引,并将 `__proto__` 改为原型获取 - if (path[0] === "__proto__") - path[0] = Reflect.getPrototypeOf(root); - else - path[0] = root[path[0]]; + // 向下索引,并将 `__proto__` 改为原型获取 + if (path[0] === "__proto__") + path[0] = Reflect.getPrototypeOf(root); + else + path[0] = root[path[0]]; - if (!path[0]) - continue; + if (!path[0]) + continue; - // 添加新的路径 - pathes.push(path); - continue; - } + // 添加新的路径 + pathes.push(path); + continue; + } - // 如果下一个键是正则表达式 - // 此时需要遍历向下索引 - const root = path.shift(); - const pattern = path.shift(); - const keys = Reflect.ownKeys(root) - .filter(k => pattern.test( - typeof k == "string" - ? k : `@${k.description}`)) - .filter(k => root[k]); + // 如果下一个键是正则表达式 + // 此时需要遍历向下索引 + const root = path.shift(); + const pattern = path.shift(); + const keys = Reflect.ownKeys(root) + .filter(k => pattern.test( + typeof k == "string" + ? k : `@${k.description}`)) + .filter(k => root[k]); - if (!keys.length) - continue; + if (!keys.length) + continue; - // 添加新的路径 - pathes.push(...keys - .map(k => [root[k], ...path])); - } + // 添加新的路径 + pathes.push(...keys + .map(k => [root[k], ...path])); + } - // 根据索引进行封装 - for (const index of indexes) - // @ts-ignore - NativeWrapper.wrapFunction(global, ...index, flags); - } + // 根据索引进行封装 + for (const index of indexes) + // @ts-ignore + NativeWrapper.wrapFunction(global, ...index, flags); + } - /** - * ```plain - * 对于具体的原生函数进行封装 - * ``` - * - * @param {Window} global - * @param {Object} parent - * @param {string|symbol} name - * @param {number} flags - */ - static wrapFunction(global, parent, name, flags) { - if (flags & 1) { - // 如果路径结尾是 `*`,代表需要封装访问器(getter与setter) - const descriptor = Reflect.getOwnPropertyDescriptor(parent, name); + /** + * ```plain + * 对于具体的原生函数进行封装 + * ``` + * + * @param {Window} global + * @param {Object} parent + * @param {string|symbol} name + * @param {number} flags + */ + static wrapFunction(global, parent, name, flags) { + if (flags & 1) { + // 如果路径结尾是 `*`,代表需要封装访问器(getter与setter) + const descriptor = Reflect.getOwnPropertyDescriptor(parent, name); - if (!descriptor - || typeof descriptor.get != "function" - || typeof descriptor.set != "function") - throw new TypeError("不支持的HTML实现"); + if (!descriptor + || typeof descriptor.get != "function" + || typeof descriptor.set != "function") + throw new TypeError("不支持的HTML实现"); - // 封装访问器 - descriptor.get = NativeWrapper.wrapGetter(descriptor.get); - descriptor.set = NativeWrapper.wrapSetter(descriptor.set); - Reflect.defineProperty(parent, name, descriptor); - } else { - const defaultFunction = parent[name]; + // 封装访问器 + descriptor.get = NativeWrapper.wrapGetter(descriptor.get); + descriptor.set = NativeWrapper.wrapSetter(descriptor.set); + Reflect.defineProperty(parent, name, descriptor); + } else { + const defaultFunction = parent[name]; - if (!defaultFunction) - return; + if (!defaultFunction) + return; - if (defaultFunction.prototype) { - // 如果此函数是一个构造函数 - const wrappedApply = NativeWrapper.wrapApply(defaultFunction, flags); - const wrappedConstruct = NativeWrapper.wrapConstruct(defaultFunction, flags); + if (defaultFunction.prototype) { + // 如果此函数是一个构造函数 + const wrappedApply = NativeWrapper.wrapApply(defaultFunction, flags); + const wrappedConstruct = NativeWrapper.wrapConstruct(defaultFunction, flags); - // 使用代理封装 - parent[name] = new Proxy(defaultFunction, { - apply(target, thisArg, argArray) { - return Reflect.apply(wrappedApply, thisArg, argArray); - }, - construct(target, argArray, newTarget) { - return Reflect.construct(wrappedConstruct, argArray, newTarget); - }, - }); - } else { - // 否则直接进行封装 - parent[name] = NativeWrapper.wrapApply( - global === parent - ? defaultFunction.bind(null) - : defaultFunction, flags); - } - } - } + // 使用代理封装 + parent[name] = new Proxy(defaultFunction, { + apply(target, thisArg, argArray) { + return Reflect.apply(wrappedApply, thisArg, argArray); + }, + construct(target, argArray, newTarget) { + return Reflect.construct(wrappedConstruct, argArray, newTarget); + }, + }); + } else { + // 否则直接进行封装 + parent[name] = NativeWrapper.wrapApply( + global === parent + ? defaultFunction.bind(null) + : defaultFunction, flags); + } + } + } - /** - * ```plain - * 将原生函数进行调用封装 - * ``` - * - * @param {Function} func - * @param {number} flags - * @returns {Function} - */ - static wrapApply(func, flags = 0) { - // @ts-ignore - const prototype = NativeWrapper.#currentFunction.prototype; - // 根据是否装箱进行不同的封装 - const wrapped = (flags & 2) - ? function (...args) { - const list = args.map(a => - NativeWrapper.boxCallback(a, prototype)); - // @ts-ignore - return ContextInvoker1(func, this, list); - } - : function (...args) { - // @ts-ignore - return ContextInvoker1(func, this, args); - }; + /** + * ```plain + * 将原生函数进行调用封装 + * ``` + * + * @param {Function} func + * @param {number} flags + * @returns {Function} + */ + static wrapApply(func, flags = 0) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; + // 根据是否装箱进行不同的封装 + const wrapped = (flags & 2) + ? function (...args) { + const list = args.map(a => + NativeWrapper.boxCallback(a, prototype)); + // @ts-ignore + return ContextInvoker1(func, this, list); + } + : function (...args) { + // @ts-ignore + return ContextInvoker1(func, this, args); + }; - // 构造原型链 - sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, prototype); - return wrapped; - } + // 构造原型链 + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, prototype); + return wrapped; + } - /** - * ```plain - * 将原生函数进行构造封装 - * ``` - * - * @param {Function} func - * @param {number} flags - * @returns {Function} - */ - static wrapConstruct(func, flags = 0) { - // @ts-ignore - const prototype = NativeWrapper.#currentFunction.prototype; - // 根据是否装箱进行不同的封装 - const wrapped = (flags & 2) - ? function (...args) { - const list = args.map(a => - NativeWrapper.boxCallback(a, prototype)); - return ContextInvoker2(func, list, new.target); - } - : function (...args) { - return ContextInvoker2(func, args, new.target); - }; + /** + * ```plain + * 将原生函数进行构造封装 + * ``` + * + * @param {Function} func + * @param {number} flags + * @returns {Function} + */ + static wrapConstruct(func, flags = 0) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; + // 根据是否装箱进行不同的封装 + const wrapped = (flags & 2) + ? function (...args) { + const list = args.map(a => + NativeWrapper.boxCallback(a, prototype)); + return ContextInvoker2(func, list, new.target); + } + : function (...args) { + return ContextInvoker2(func, args, new.target); + }; - // 构造原型链 - sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, prototype); - return wrapped; - } + // 构造原型链 + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, prototype); + return wrapped; + } - /** - * ```plain - * 将原生GETTER进行调用封装 - * ``` - * - * @param {Function} func - * @returns {(...args: any[]) => any} - */ - static wrapGetter(func) { - // @ts-ignore - const prototype = NativeWrapper.#currentFunction.prototype; - const wrapped = function () { - // @ts-ignore - return NativeWrapper.unboxCallback(ContextInvoker1(func, this, [])); - }; + /** + * ```plain + * 将原生GETTER进行调用封装 + * ``` + * + * @param {Function} func + * @returns {(...args: any[]) => any} + */ + static wrapGetter(func) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; + const wrapped = function () { + // @ts-ignore + return NativeWrapper.unboxCallback(ContextInvoker1(func, this, [])); + }; - // 构造原型链 - sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, prototype); - return wrapped; - } + // 构造原型链 + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, prototype); + return wrapped; + } - /** - * ```plain - * 将原生SETTER进行调用封装 - * ``` - * - * @param {Function} func - * @returns {(...args: any[]) => any} - */ - static wrapSetter(func) { - // @ts-ignore - const prototype = NativeWrapper.#currentFunction.prototype; - const wrapped = function (value) { - // @ts-ignore - return ContextInvoker1(func, this, - [NativeWrapper.boxCallback(value, prototype)]); - }; + /** + * ```plain + * 将原生SETTER进行调用封装 + * ``` + * + * @param {Function} func + * @returns {(...args: any[]) => any} + */ + static wrapSetter(func) { + // @ts-ignore + const prototype = NativeWrapper.#currentFunction.prototype; + const wrapped = function (value) { + // @ts-ignore + return ContextInvoker1(func, this, + [NativeWrapper.boxCallback(value, prototype)]); + }; - // 构造原型链 - sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, prototype); - return wrapped; - } + // 构造原型链 + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, prototype); + return wrapped; + } - /** - * ```plain - * 将回调函数进行装箱 - * ``` - * - * @param {Proxy} unboxed - * @param {Function} prototype - * @returns - */ - static boxCallback(unboxed, prototype) { - if (typeof unboxed != "function") - return unboxed; + /** + * ```plain + * 将回调函数进行装箱 + * ``` + * + * @param {Proxy} unboxed + * @param {Function} prototype + * @returns + */ + static boxCallback(unboxed, prototype) { + if (typeof unboxed != "function") + return unboxed; - // 读取缓存 - let wrapped = NativeWrapper.#boxedMap.get(unboxed); + // 读取缓存 + let wrapped = NativeWrapper.#boxedMap.get(unboxed); - if (!wrapped) { - // 缓存不存在则创建 - wrapped = ContextInvokerCreator({ - unboxed, // 向封装函数提供unboxed函数 - }, function (thiz, args, newTarget) { - return newTarget - // @ts-ignore - ? Reflect.construct(this.unboxed, args, newTarget) - // @ts-ignore - : Reflect.apply(this.unboxed, thiz, args); - }); + if (!wrapped) { + // 缓存不存在则创建 + wrapped = ContextInvokerCreator({ + unboxed, // 向封装函数提供unboxed函数 + }, function (thiz, args, newTarget) { + return newTarget + // @ts-ignore + ? Reflect.construct(this.unboxed, args, newTarget) + // @ts-ignore + : Reflect.apply(this.unboxed, thiz, args); + }); - // 设置暴露器 - wrapped[SandboxExposer] = (signal) => { - if (signal === NativeWrapper.#unboxedFunction) - return unboxed; - }; + // 设置暴露器 + wrapped[SandboxExposer] = (signal) => { + if (signal === NativeWrapper.#unboxedFunction) + return unboxed; + }; - // 构造原型链 - sealObjectTree(wrapped); - Reflect.setPrototypeOf(wrapped, prototype); - NativeWrapper.#boxedMap.set(unboxed, wrapped); - } + // 构造原型链 + sealObjectTree(wrapped); + Reflect.setPrototypeOf(wrapped, prototype); + NativeWrapper.#boxedMap.set(unboxed, wrapped); + } - NativeWrapper.#boxedSet.add(wrapped); - return wrapped; - } + NativeWrapper.#boxedSet.add(wrapped); + return wrapped; + } - /** - * ```plain - * 将回调函数进行拆箱 - * ``` - * - * @param {Function} boxed - * @returns - */ - static unboxCallback(boxed) { - if (!NativeWrapper.#boxedSet.has(boxed)) - return boxed; + /** + * ```plain + * 将回调函数进行拆箱 + * ``` + * + * @param {Function} boxed + * @returns + */ + static unboxCallback(boxed) { + if (!NativeWrapper.#boxedSet.has(boxed)) + return boxed; - // 通过暴露器获取原始函数 - return boxed[SandboxExposer] - (NativeWrapper.#unboxedFunction); - } + // 通过暴露器获取原始函数 + return boxed[SandboxExposer] + (NativeWrapper.#unboxedFunction); + } } // 执行上下文传递函数,请勿动喵 @@ -1053,384 +1053,384 @@ const ContextInvokerCreator = window.replacedCIC; * ``` */ class DomainMonitors { - /** @type {WeakMap} */ - static #domainMonitors = new WeakMap(); + /** @type {WeakMap} */ + static #domainMonitors = new WeakMap(); - /** @type {WeakMap>>} */ - #monitorsMap = new WeakMap(); + /** @type {WeakMap>>} */ + #monitorsMap = new WeakMap(); - /** - * ```plain - * 在当前运行域安装一个 Monitor - * ``` - * - * @param {DomainMonitors} thiz - * @param {Monitor} monitor - */ - static #installMonitor = function (thiz, monitor) { - // 解构 Monitor 相关条件 - // @ts-ignore - const [ - actions, - allowDomains, - disallowDomains, - ] = Monitor[SandboxExposer2] - (SandboxSignal_ExposeInfo, monitor); + /** + * ```plain + * 在当前运行域安装一个 Monitor + * ``` + * + * @param {DomainMonitors} thiz + * @param {Monitor} monitor + */ + static #installMonitor = function (thiz, monitor) { + // 解构 Monitor 相关条件 + // @ts-ignore + const [ + actions, + allowDomains, + disallowDomains, + ] = Monitor[SandboxExposer2] + (SandboxSignal_ExposeInfo, monitor); - function addToActionMap(actionMap) { - for (const action of actions) { - let monitorMap = actionMap[action]; + function addToActionMap(actionMap) { + for (const action of actions) { + let monitorMap = actionMap[action]; - if (!monitorMap) - monitorMap = actionMap[action] = new Set(); + if (!monitorMap) + monitorMap = actionMap[action] = new Set(); - monitorMap.add(monitor); - } - } + monitorMap.add(monitor); + } + } - const domainList = []; + const domainList = []; - if (!allowDomains) { - // 取运行域补集 - // @ts-ignore - const totalDomains = new Set(Domain[SandboxExposer2] - (SandboxSignal_ListDomain)); - totalDomains.delete(monitor.domain); + if (!allowDomains) { + // 取运行域补集 + // @ts-ignore + const totalDomains = new Set(Domain[SandboxExposer2] + (SandboxSignal_ListDomain)); + totalDomains.delete(monitor.domain); - if (disallowDomains) - for (const domain of disallowDomains) - totalDomains.delete(domain); + if (disallowDomains) + for (const domain of disallowDomains) + totalDomains.delete(domain); - domainList.push(...totalDomains); - } else - domainList.push(...allowDomains); + domainList.push(...totalDomains); + } else + domainList.push(...allowDomains); - // 根据允许的运行域安装 Monitor - for (const domain of domainList) { - let actionMap = thiz.#monitorsMap.get(domain); + // 根据允许的运行域安装 Monitor + for (const domain of domainList) { + let actionMap = thiz.#monitorsMap.get(domain); - if (!actionMap) - thiz.#monitorsMap.set(domain, actionMap = {}); + if (!actionMap) + thiz.#monitorsMap.set(domain, actionMap = {}); - // 根据 actions 添加到不同的触发器集合 - addToActionMap(actionMap); - } - } + // 根据 actions 添加到不同的触发器集合 + addToActionMap(actionMap); + } + } - /** - * ```plain - * 从当前运行域卸载一个 Monitor - * ``` - * - * @param {DomainMonitors} thiz - * @param {Monitor} monitor - */ - static #uninstallMonitor = function (thiz, monitor) { - // 解构 Monitor 相关条件 - // @ts-ignore - const [ - actions, - allowDomains, - disallowDomains, - ] = Monitor[SandboxExposer2] - (SandboxSignal_ExposeInfo, monitor); + /** + * ```plain + * 从当前运行域卸载一个 Monitor + * ``` + * + * @param {DomainMonitors} thiz + * @param {Monitor} monitor + */ + static #uninstallMonitor = function (thiz, monitor) { + // 解构 Monitor 相关条件 + // @ts-ignore + const [ + actions, + allowDomains, + disallowDomains, + ] = Monitor[SandboxExposer2] + (SandboxSignal_ExposeInfo, monitor); - function removeFromActionMap(actionMap) { - for (const action of actions) { - const monitorMap = actionMap[action]; + function removeFromActionMap(actionMap) { + for (const action of actions) { + const monitorMap = actionMap[action]; - if (!monitorMap) - continue; + if (!monitorMap) + continue; - monitorMap.delete(monitor); - } - } + monitorMap.delete(monitor); + } + } - const domainList = []; + const domainList = []; - if (!allowDomains) { - // 取运行域补集 - // @ts-ignore - const totalDomains = new Set(Domain[SandboxExposer2] - (SandboxSignal_ListDomain)); + if (!allowDomains) { + // 取运行域补集 + // @ts-ignore + const totalDomains = new Set(Domain[SandboxExposer2] + (SandboxSignal_ListDomain)); - if (disallowDomains) - for (const domain of disallowDomains) - totalDomains.delete(domain); + if (disallowDomains) + for (const domain of disallowDomains) + totalDomains.delete(domain); - domainList.push(...totalDomains); - } else - domainList.push(...allowDomains); + domainList.push(...totalDomains); + } else + domainList.push(...allowDomains); - // 根据允许的运行域卸载 Monitor - for (const domain of domainList) { - const actionMap = thiz.#monitorsMap.get(domain); + // 根据允许的运行域卸载 Monitor + for (const domain of domainList) { + const actionMap = thiz.#monitorsMap.get(domain); - if (!actionMap) - continue; + if (!actionMap) + continue; - // 根据 actions 从不同的触发器集合移除 - removeFromActionMap(actionMap); - } - } + // 根据 actions 从不同的触发器集合移除 + removeFromActionMap(actionMap); + } + } - /** - * ```plain - * 获取当前运行域封送到目标运行域的所有符合条件的 Monitor - * ``` - * - * @param {Domain} sourceDomain - * @param {Domain} targetDomain - * @param {number} action - * @returns {Set?} - */ - static #getMonitorsBy = function (sourceDomain, targetDomain, action) { - const instance = DomainMonitors.#domainMonitors.get(sourceDomain); + /** + * ```plain + * 获取当前运行域封送到目标运行域的所有符合条件的 Monitor + * ``` + * + * @param {Domain} sourceDomain + * @param {Domain} targetDomain + * @param {number} action + * @returns {Set?} + */ + static #getMonitorsBy = function (sourceDomain, targetDomain, action) { + const instance = DomainMonitors.#domainMonitors.get(sourceDomain); - if (!instance) - return null; + if (!instance) + return null; - const actionMap = instance.#monitorsMap.get(targetDomain); + const actionMap = instance.#monitorsMap.get(targetDomain); - if (!actionMap || !(action in actionMap)) - return null; + if (!actionMap || !(action in actionMap)) + return null; - return actionMap[action]; - } + return actionMap[action]; + } - /** - * ```plain - * 对新的运行域进行 Monitor 安装 - * ``` - * - * @param {DomainMonitors} thiz - * @param {Domain} domain - */ - static #handleNewDomain = function (thiz, domain) { - let actionMap = thiz.#monitorsMap.get(domain); + /** + * ```plain + * 对新的运行域进行 Monitor 安装 + * ``` + * + * @param {DomainMonitors} thiz + * @param {Domain} domain + */ + static #handleNewDomain = function (thiz, domain) { + let actionMap = thiz.#monitorsMap.get(domain); - // 遍历所有启用的 Monitor - for (const monitor of - // @ts-ignore - Monitor[SandboxExposer2](SandboxSignal_ListMonitor)) { - if (monitor.domain === domain) - continue; + // 遍历所有启用的 Monitor + for (const monitor of + // @ts-ignore + Monitor[SandboxExposer2](SandboxSignal_ListMonitor)) { + if (monitor.domain === domain) + continue; - // 解构 Monitor 相关条件 - const [ - actions, - allowDomains, - disallowDomains, - ] = Monitor[SandboxExposer2] - (SandboxSignal_ExposeInfo, monitor); + // 解构 Monitor 相关条件 + const [ + actions, + allowDomains, + disallowDomains, + ] = Monitor[SandboxExposer2] + (SandboxSignal_ExposeInfo, monitor); - // 判断新增的 Domain 是否是 Monitor 监听的目标 - if (allowDomains - && !allowDomains.has(domain)) - continue; - if (disallowDomains - && disallowDomains.has(domain)) - continue; + // 判断新增的 Domain 是否是 Monitor 监听的目标 + if (allowDomains + && !allowDomains.has(domain)) + continue; + if (disallowDomains + && disallowDomains.has(domain)) + continue; - // 根据 actions 添加到不同的触发器集合 - if (!actionMap) - thiz.#monitorsMap.set(domain, actionMap = {}); + // 根据 actions 添加到不同的触发器集合 + if (!actionMap) + thiz.#monitorsMap.set(domain, actionMap = {}); - for (const action of actions) { - let monitors = actionMap[action]; + for (const action of actions) { + let monitors = actionMap[action]; - if (!monitors) - monitors = actionMap[action] = new Set(); + if (!monitors) + monitors = actionMap[action] = new Set(); - monitors.add(monitor); - } - } - } + monitors.add(monitor); + } + } + } - /** - * ```plain - * 处理新的运行域 - * ``` - * - * @param {Domain} newDomain - */ - static handleNewDomain(newDomain) { - // @ts-ignore - const totalDomains = new Set(Domain[SandboxExposer2] - (SandboxSignal_ListDomain)); + /** + * ```plain + * 处理新的运行域 + * ``` + * + * @param {Domain} newDomain + */ + static handleNewDomain(newDomain) { + // @ts-ignore + const totalDomains = new Set(Domain[SandboxExposer2] + (SandboxSignal_ListDomain)); - for (const domain of totalDomains) { - const instance = DomainMonitors.#domainMonitors.get(domain); + for (const domain of totalDomains) { + const instance = DomainMonitors.#domainMonitors.get(domain); - if (!instance) - continue; + if (!instance) + continue; - DomainMonitors.#handleNewDomain(instance, newDomain); - } - } + DomainMonitors.#handleNewDomain(instance, newDomain); + } + } - /** - * ```plain - * 分发 Monitor 监听事件 - * ``` - * - * @param {Domain} sourceDomain - * @param {Domain} targetDomain - * @param {number} action - * @param {Array} args - * @returns - */ - static dispatch(sourceDomain, targetDomain, action, args) { - const nameds = {}; - let indexMap; + /** + * ```plain + * 分发 Monitor 监听事件 + * ``` + * + * @param {Domain} sourceDomain + * @param {Domain} targetDomain + * @param {number} action + * @param {Array} args + * @returns + */ + static dispatch(sourceDomain, targetDomain, action, args) { + const nameds = {}; + let indexMap; - // 构造命名参数 - switch (action) { - case AccessAction.CALL: - indexMap = { - target: 0, - thisArg: 1, - arguments: 2, - }; - break; - case AccessAction.NEW: - indexMap = { - target: 0, - arguments: 1, - newTarget: 2, - }; - break; - case AccessAction.DEFINE: - indexMap = { - target: 0, - property: 1, - descriptor: 2, - }; - break; - case AccessAction.DELETE: - case AccessAction.DESCRIBE: - case AccessAction.EXISTS: - indexMap = { - target: 0, - property: 1, - }; - break; - case AccessAction.READ: - indexMap = { - target: 0, - property: 1, - receiver: 2, - }; - break; - case AccessAction.TRACE: - case AccessAction.LIST: - case AccessAction.SEAL: - indexMap = { - target: 0, - }; - break; - case AccessAction.WRITE: - indexMap = { - target: 0, - property: 1, - value: 2, - receiver: 3, - }; - break; - case AccessAction.META: - indexMap = { - target: 0, - prototype: 1, - }; - break; - default: - throw new TypeError("不支持的访问操作"); - } + // 构造命名参数 + switch (action) { + case AccessAction.CALL: + indexMap = { + target: 0, + thisArg: 1, + arguments: 2, + }; + break; + case AccessAction.NEW: + indexMap = { + target: 0, + arguments: 1, + newTarget: 2, + }; + break; + case AccessAction.DEFINE: + indexMap = { + target: 0, + property: 1, + descriptor: 2, + }; + break; + case AccessAction.DELETE: + case AccessAction.DESCRIBE: + case AccessAction.EXISTS: + indexMap = { + target: 0, + property: 1, + }; + break; + case AccessAction.READ: + indexMap = { + target: 0, + property: 1, + receiver: 2, + }; + break; + case AccessAction.TRACE: + case AccessAction.LIST: + case AccessAction.SEAL: + indexMap = { + target: 0, + }; + break; + case AccessAction.WRITE: + indexMap = { + target: 0, + property: 1, + value: 2, + receiver: 3, + }; + break; + case AccessAction.META: + indexMap = { + target: 0, + prototype: 1, + }; + break; + default: + throw new TypeError("不支持的访问操作"); + } - for (const key in indexMap) - nameds[key] = args[indexMap[key]]; + for (const key in indexMap) + nameds[key] = args[indexMap[key]]; - Object.freeze(indexMap); - Object.freeze(nameds); + Object.freeze(indexMap); + Object.freeze(nameds); - // 获取可能的 Monitor 集合 - const monitorMap = DomainMonitors.#getMonitorsBy(sourceDomain, targetDomain, action); - const result = { - preventDefault: false, - stopPropagation: false, - returnValue: undefined, - }; + // 获取可能的 Monitor 集合 + const monitorMap = DomainMonitors.#getMonitorsBy(sourceDomain, targetDomain, action); + const result = { + preventDefault: false, + stopPropagation: false, + returnValue: undefined, + }; - if (!monitorMap || monitorMap.size == 0) - return result; + if (!monitorMap || monitorMap.size == 0) + return result; - const access = { - domain: targetDomain, - action, - }; + const access = { + domain: targetDomain, + action, + }; - const control = Object.freeze({ - preventDefault() { - result.preventDefault = true; - }, - stopPropagation() { - result.stopPropagation = true; - }, - overrideParameter(name, value) { - if (!(name in indexMap)) - throw new TypeError(`参数 ${name} 没有找到`); + const control = Object.freeze({ + preventDefault() { + result.preventDefault = true; + }, + stopPropagation() { + result.stopPropagation = true; + }, + overrideParameter(name, value) { + if (!(name in indexMap)) + throw new TypeError(`参数 ${name} 没有找到`); - args[indexMap[name]] = value; - }, - setReturnValue(value) { - result.returnValue = value; - }, - }); + args[indexMap[name]] = value; + }, + setReturnValue(value) { + result.returnValue = value; + }, + }); - // 遍历并尝试分发监听事件 - for (const monitor of monitorMap) { - Monitor[SandboxExposer2] - (SandboxSignal_DiapatchMonitor, monitor, access, nameds, control); + // 遍历并尝试分发监听事件 + for (const monitor of monitorMap) { + Monitor[SandboxExposer2] + (SandboxSignal_DiapatchMonitor, monitor, access, nameds, control); - if (result.stopPropagation) - break; - } + if (result.stopPropagation) + break; + } - return result; - } + return result; + } - /** - * ```plain - * 安装一个 Monitor 监控 - * ``` - * - * @param {Monitor} monitor - */ - static installMonitor(monitor) { - const domain = monitor.domain; - let instance = DomainMonitors.#domainMonitors.get(domain); + /** + * ```plain + * 安装一个 Monitor 监控 + * ``` + * + * @param {Monitor} monitor + */ + static installMonitor(monitor) { + const domain = monitor.domain; + let instance = DomainMonitors.#domainMonitors.get(domain); - if (!instance) - DomainMonitors.#domainMonitors - .set(domain, instance = new DomainMonitors()); + if (!instance) + DomainMonitors.#domainMonitors + .set(domain, instance = new DomainMonitors()); - DomainMonitors.#installMonitor(instance, monitor); - } + DomainMonitors.#installMonitor(instance, monitor); + } - /** - * ```plain - * 卸载一个 Monitor 监控 - * ``` - * - * @param {Monitor} monitor - */ - static uninstallMonitor(monitor) { - const domain = monitor.domain; - const instance = DomainMonitors.#domainMonitors.get(domain); + /** + * ```plain + * 卸载一个 Monitor 监控 + * ``` + * + * @param {Monitor} monitor + */ + static uninstallMonitor(monitor) { + const domain = monitor.domain; + const instance = DomainMonitors.#domainMonitors.get(domain); - if (instance) - DomainMonitors.#uninstallMonitor(instance, monitor); - } + if (instance) + DomainMonitors.#uninstallMonitor(instance, monitor); + } } /** @@ -1449,393 +1449,393 @@ class DomainMonitors { * monitor.require("property", "value"); // 指定监听 value 属性 * monitor.filter((access, nameds) => nameds.value >= 0); // 过滤掉大于等于 0 的修改 * monitor.then((access, nameds, control) => { - * control.overrideParameter("value", 0); // 将要修改的新值改回 0 + * control.overrideParameter("value", 0); // 将要修改的新值改回 0 * }); * monitor.start(); // 启动Monitor * ``` */ class Monitor { - /** @type {Set} */ - static #monitorSet = new Set(); + /** @type {Set} */ + static #monitorSet = new Set(); - /** @type {Domain} */ - #domain; - /** @type {Set?} */ - #allowDomains = null; - /** @type {Set?} */ - #disallowDomains = null; - /** @type {Set} */ - #actions = new Set(); - /** @type {Object} */ - #checkInfo = {}; - /** @type {Function?} */ - #filter = null; - /** @type {Function?} */ - #handler = null; + /** @type {Domain} */ + #domain; + /** @type {Set?} */ + #allowDomains = null; + /** @type {Set?} */ + #disallowDomains = null; + /** @type {Set} */ + #actions = new Set(); + /** @type {Object} */ + #checkInfo = {}; + /** @type {Function?} */ + #filter = null; + /** @type {Function?} */ + #handler = null; - constructor() { - this.#domain = Domain.current; - } + constructor() { + this.#domain = Domain.current; + } - /** - * ```plain - * 检查当前是否是 Monitor 所属的运行域 - * ``` - * - * @param {Monitor} thiz - */ - static #assertOperator = function (thiz) { - if (thiz.#domain !== Domain.current) - throw new Error("当前不是 Monitor 所属的运行域"); - } + /** + * ```plain + * 检查当前是否是 Monitor 所属的运行域 + * ``` + * + * @param {Monitor} thiz + */ + static #assertOperator = function (thiz) { + if (thiz.#domain !== Domain.current) + throw new Error("当前不是 Monitor 所属的运行域"); + } - /** - * ```plain - * 获取 Monitor 所属的运行域 - * ``` - */ - get domain() { - return this.#domain; - } + /** + * ```plain + * 获取 Monitor 所属的运行域 + * ``` + */ + get domain() { + return this.#domain; + } - /** - * ```plain - * 指定 Monitor 可以监听的运行域 - * 默认监听封送到的所有运行域 - * ``` - * - * @param {...Domain} domains - * @returns {this} - */ - allow(...domains) { - Monitor.#assertOperator(this); + /** + * ```plain + * 指定 Monitor 可以监听的运行域 + * 默认监听封送到的所有运行域 + * ``` + * + * @param {...Domain} domains + * @returns {this} + */ + allow(...domains) { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 在启动期间不能修改"); - if (!domains.length) - throw new TypeError("运行域至少要有一个"); + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (!domains.length) + throw new TypeError("运行域至少要有一个"); - for (const domain of domains) { - if (!(domain instanceof Domain)) - throw new TypeError("无效的运行域"); - if (domain === this.#domain) - throw new TypeError("Monitor 不能监听自己"); - } + for (const domain of domains) { + if (!(domain instanceof Domain)) + throw new TypeError("无效的运行域"); + if (domain === this.#domain) + throw new TypeError("Monitor 不能监听自己"); + } - // 使用黑白名单 - if (this.#allowDomains) { - for (const domain of domains) - this.#allowDomains.add(domain); - } else if (this.#disallowDomains) { - for (const domain of domains) - this.#disallowDomains.delete(domain); - } else - this.#allowDomains = new Set(domains); + // 使用黑白名单 + if (this.#allowDomains) { + for (const domain of domains) + this.#allowDomains.add(domain); + } else if (this.#disallowDomains) { + for (const domain of domains) + this.#disallowDomains.delete(domain); + } else + this.#allowDomains = new Set(domains); - return this; - } + return this; + } - /** - * ```plain - * 指定 Monitor 不可监听的运行域 - * 默认监听封送到的所有运行域 - * ``` - * - * @param {...Domain} domains - * @returns {this} - */ - disallow(...domains) { - Monitor.#assertOperator(this); + /** + * ```plain + * 指定 Monitor 不可监听的运行域 + * 默认监听封送到的所有运行域 + * ``` + * + * @param {...Domain} domains + * @returns {this} + */ + disallow(...domains) { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 在启动期间不能修改"); - if (!domains.length) - throw new TypeError("运行域至少要有一个"); + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (!domains.length) + throw new TypeError("运行域至少要有一个"); - for (const domain of domains) - if (!(domain instanceof Domain)) - throw new TypeError("无效的运行域"); + for (const domain of domains) + if (!(domain instanceof Domain)) + throw new TypeError("无效的运行域"); - // 使用黑白名单 - if (this.#disallowDomains) { - for (const domain of domains) - this.#disallowDomains.add(domain); - } else if (this.#allowDomains) { - for (const domain of domains) - this.#allowDomains.delete(domain); - } else - this.#disallowDomains = new Set(domains); + // 使用黑白名单 + if (this.#disallowDomains) { + for (const domain of domains) + this.#disallowDomains.add(domain); + } else if (this.#allowDomains) { + for (const domain of domains) + this.#allowDomains.delete(domain); + } else + this.#disallowDomains = new Set(domains); - return this; - } + return this; + } - /** - * ```plain - * 指定 Monitor 监听的访问动作 - * ``` - * - * @param {...number} action - * @returns {this} - */ - action(...action) { - Monitor.#assertOperator(this); + /** + * ```plain + * 指定 Monitor 监听的访问动作 + * ``` + * + * @param {...number} action + * @returns {this} + */ + action(...action) { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 在启动期间不能修改"); - if (action.length == 0 - || !action.every(AccessAction.isAccessAction)) - throw new TypeError("无效的访问动作"); + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (action.length == 0 + || !action.every(AccessAction.isAccessAction)) + throw new TypeError("无效的访问动作"); - for (const item of action) - this.#actions.add(item); + for (const item of action) + this.#actions.add(item); - return this; - } + return this; + } - /** - * - * @typedef {"target" | "thisArg" | "arguments" | "newTarget" | "property" | "descriptor" | "receiver" | "prototype" | "value"} PropertyKey - * - * @typedef {{ - * domain: Domain, - * action: number, - * }} Access - * - * @typedef {{ - * target: Object, - * thisArg?: Object, - * arguments?: Array, - * newTarget?: Function, - * property?: string | symbol, - * descriptor?: { - * value?: any, - * writable?: boolean, - * get?: () => any, - * set?: (value: any) => void, - * enumerable?: boolean, - * configurable?: boolean, - * }, - * receiver?: Object, - * prototype?: Object, - * value?: any, - * }} Nameds - * - * @typedef {{ - * preventDefault: () => void, - * stopPropagation: () => void, - * overrideParameter: (name: PropertyKey, value: any) => void, - * setReturnValue: (value: any) => void, - * }} Control - * - */ + /** + * + * @typedef {"target" | "thisArg" | "arguments" | "newTarget" | "property" | "descriptor" | "receiver" | "prototype" | "value"} PropertyKey + * + * @typedef {{ + * domain: Domain, + * action: number, + * }} Access + * + * @typedef {{ + * target: Object, + * thisArg?: Object, + * arguments?: Array, + * newTarget?: Function, + * property?: string | symbol, + * descriptor?: { + * value?: any, + * writable?: boolean, + * get?: () => any, + * set?: (value: any) => void, + * enumerable?: boolean, + * configurable?: boolean, + * }, + * receiver?: Object, + * prototype?: Object, + * value?: any, + * }} Nameds + * + * @typedef {{ + * preventDefault: () => void, + * stopPropagation: () => void, + * overrideParameter: (name: PropertyKey, value: any) => void, + * setReturnValue: (value: any) => void, + * }} Control + * + */ - /** - * ```plain - * 指定 Monitor 监听的命名参数 - * - * 命名参数可能如下: - * target: 监听的对象,访问动作:所有 - * thisArg: 调用的this对象,访问动作:CALL - * arguments: 调用的参数,访问动作:CALL, NEW - * newTarget: 构造的new.target,访问动作:NEW - * property: 访问的属性,访问动作:DEFINE, DELETE, DESCRIBE, EXISTS, READ, WRITE - * descriptor: 定义的属性描述符,访问动作:DEFINE - * receiver: 设置或读取的this对象,访问动作:READ, WRITE - * prototype: 定义的原型,访问动作:META - * value: 设置的新值,访问动作:WRITE - * ``` - * - * @param {PropertyKey} name 命名参数名称 - * @param {...any} values 命名参数可能的值 - * @returns {this} - */ - require(name, ...values) { - Monitor.#assertOperator(this); + /** + * ```plain + * 指定 Monitor 监听的命名参数 + * + * 命名参数可能如下: + * target: 监听的对象,访问动作:所有 + * thisArg: 调用的this对象,访问动作:CALL + * arguments: 调用的参数,访问动作:CALL, NEW + * newTarget: 构造的new.target,访问动作:NEW + * property: 访问的属性,访问动作:DEFINE, DELETE, DESCRIBE, EXISTS, READ, WRITE + * descriptor: 定义的属性描述符,访问动作:DEFINE + * receiver: 设置或读取的this对象,访问动作:READ, WRITE + * prototype: 定义的原型,访问动作:META + * value: 设置的新值,访问动作:WRITE + * ``` + * + * @param {PropertyKey} name 命名参数名称 + * @param {...any} values 命名参数可能的值 + * @returns {this} + */ + require(name, ...values) { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 在启动期间不能修改"); - if (typeof name != "string") - throw new TypeError("无效的检查名称"); - if (!values.length) - return this; + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (typeof name != "string") + throw new TypeError("无效的检查名称"); + if (!values.length) + return this; - let info = this.#checkInfo[name]; + let info = this.#checkInfo[name]; - if (!info) - info = this.#checkInfo[name] = new Set(); + if (!info) + info = this.#checkInfo[name] = new Set(); - for (const value of values) - info.add(value); + for (const value of values) + info.add(value); - return this; - } + return this; + } - /** - * ```plain - * 指定 Monitor 监听的过滤器 - * - * 回调参数 nameds 是一个对象,包含了 Monitor 监听的命名参数 - * ``` - * - * @param {(access: Access, nameds: Nameds) => boolean} filter 要指定的过滤器 - * @returns {this} - */ - filter(filter) { - Monitor.#assertOperator(this); + /** + * ```plain + * 指定 Monitor 监听的过滤器 + * + * 回调参数 nameds 是一个对象,包含了 Monitor 监听的命名参数 + * ``` + * + * @param {(access: Access, nameds: Nameds) => boolean} filter 要指定的过滤器 + * @returns {this} + */ + filter(filter) { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 在启动期间不能修改"); - if (typeof filter != "function") - throw new TypeError("无效的过滤器"); + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (typeof filter != "function") + throw new TypeError("无效的过滤器"); - this.#filter = filter; - return this; - } + this.#filter = filter; + return this; + } - /** - * ```plain - * 指定 Monitor 监听的回调函数 - * - * 回调参数 nameds 是一个对象,包含了 Monitor 监听的命名参数 - * 回调参数 control 是一个对象,提供本次监听的控制函数 - * control.preventDefault(value) 阻止默认的行为,并将设定的返回值作为本次代理访问的返回值 - * control.stopPropagation() 阻断后续的监听器,但不会阻止默认行为 - * control.overrideParameter(name, value) 覆盖本次监听的命名参数 - * control.setReturnValue(value) 设置本次代理访问的返回值,可以覆盖之前监听器设置的返回值 - * ``` - * - * @param {(access: Access, nameds: Nameds, control: Control) => void} handler - * @returns {this} - */ - then(handler) { - Monitor.#assertOperator(this); + /** + * ```plain + * 指定 Monitor 监听的回调函数 + * + * 回调参数 nameds 是一个对象,包含了 Monitor 监听的命名参数 + * 回调参数 control 是一个对象,提供本次监听的控制函数 + * control.preventDefault(value) 阻止默认的行为,并将设定的返回值作为本次代理访问的返回值 + * control.stopPropagation() 阻断后续的监听器,但不会阻止默认行为 + * control.overrideParameter(name, value) 覆盖本次监听的命名参数 + * control.setReturnValue(value) 设置本次代理访问的返回值,可以覆盖之前监听器设置的返回值 + * ``` + * + * @param {(access: Access, nameds: Nameds, control: Control) => void} handler + * @returns {this} + */ + then(handler) { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 在启动期间不能修改"); - if (typeof handler != "function") - throw new TypeError("无效的回调"); + if (this.isStarted) + throw new Error("Monitor 在启动期间不能修改"); + if (typeof handler != "function") + throw new TypeError("无效的回调"); - this.#handler = handler; - return this; - } + this.#handler = handler; + return this; + } - /** - * ```plain - * 判断 Monitor 是否已经启动 - * ``` - * - * @type {boolean} - */ - get isStarted() { - return Monitor.#monitorSet.has(this); - } + /** + * ```plain + * 判断 Monitor 是否已经启动 + * ``` + * + * @type {boolean} + */ + get isStarted() { + return Monitor.#monitorSet.has(this); + } - /** - * ```plain - * 启动 Monitor - * ``` - */ - start() { - Monitor.#assertOperator(this); + /** + * ```plain + * 启动 Monitor + * ``` + */ + start() { + Monitor.#assertOperator(this); - if (this.isStarted) - throw new Error("Monitor 已经启动"); - if (typeof this.#handler != "function") - throw new Error("Monitor 未指定回调函数"); + if (this.isStarted) + throw new Error("Monitor 已经启动"); + if (typeof this.#handler != "function") + throw new Error("Monitor 未指定回调函数"); - Monitor.#monitorSet.add(this); - DomainMonitors.installMonitor(this); - } + Monitor.#monitorSet.add(this); + DomainMonitors.installMonitor(this); + } - /** - * ```plain - * 停止 Monitor - * ``` - */ - stop() { - Monitor.#assertOperator(this); + /** + * ```plain + * 停止 Monitor + * ``` + */ + stop() { + Monitor.#assertOperator(this); - if (!this.isStarted) - throw new Error("Monitor 还未启动"); + if (!this.isStarted) + throw new Error("Monitor 还未启动"); - DomainMonitors.uninstallMonitor(this); - Monitor.#monitorSet.delete(this); - } + DomainMonitors.uninstallMonitor(this); + Monitor.#monitorSet.delete(this); + } - /** - * ```plain - * 向外暴露 Monitor 监听的相关数据 - * ``` - * - * @param {Monitor} thiz - */ - static #exposeInfo = function (thiz) { - return [ - thiz.#actions, - thiz.#allowDomains, - thiz.#disallowDomains - ]; - } + /** + * ```plain + * 向外暴露 Monitor 监听的相关数据 + * ``` + * + * @param {Monitor} thiz + */ + static #exposeInfo = function (thiz) { + return [ + thiz.#actions, + thiz.#allowDomains, + thiz.#disallowDomains + ]; + } - /** - * ```plain - * 检查 Monitor 监听的命名参数是否符合要求 - * ``` - * - * @param {Object} nameds - * @param {Object} checkInfo - */ - static #check = function (nameds, checkInfo) { - for (const [key, value] of Object.entries(nameds)) { - if (key in checkInfo) { - if (!checkInfo[key].has(value)) - return false; - } - } + /** + * ```plain + * 检查 Monitor 监听的命名参数是否符合要求 + * ``` + * + * @param {Object} nameds + * @param {Object} checkInfo + */ + static #check = function (nameds, checkInfo) { + for (const [key, value] of Object.entries(nameds)) { + if (key in checkInfo) { + if (!checkInfo[key].has(value)) + return false; + } + } - return true; - } + return true; + } - /** - * ```plain - * 处理 Monitor 监听事件 - * ``` - * - * @param {Monitor} thiz - * @param {number} access - * @param {Nameds} nameds - * @param {Control} control - */ - static #handle = function (thiz, access, nameds, control) { - if (!Monitor.#check(nameds, thiz.#checkInfo)) - return; + /** + * ```plain + * 处理 Monitor 监听事件 + * ``` + * + * @param {Monitor} thiz + * @param {number} access + * @param {Nameds} nameds + * @param {Control} control + */ + static #handle = function (thiz, access, nameds, control) { + if (!Monitor.#check(nameds, thiz.#checkInfo)) + return; - const filter = thiz.#filter; - if (typeof filter === 'function' && !filter(access, nameds)) - return; + const filter = thiz.#filter; + if (typeof filter === 'function' && !filter(access, nameds)) + return; - if (typeof thiz.#handler !== 'function') - throw new TypeError("Monitor 未指定回调函数"); + if (typeof thiz.#handler !== 'function') + throw new TypeError("Monitor 未指定回调函数"); - thiz.#handler(access, nameds, control); - } + thiz.#handler(access, nameds, control); + } - /** - * @param {Symbol} signal - * @param {...any} args - */ - static [SandboxExposer2](signal, ...args) { - switch (signal) { - case SandboxSignal_DiapatchMonitor: - // @ts-ignore - return Monitor.#handle(...args); - case SandboxSignal_ListMonitor: - return Monitor.#monitorSet; - case SandboxSignal_ExposeInfo: - // @ts-ignore - return Monitor.#exposeInfo(...args); - } - } + /** + * @param {Symbol} signal + * @param {...any} args + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_DiapatchMonitor: + // @ts-ignore + return Monitor.#handle(...args); + case SandboxSignal_ListMonitor: + return Monitor.#monitorSet; + case SandboxSignal_ExposeInfo: + // @ts-ignore + return Monitor.#exposeInfo(...args); + } + } } /** @@ -1844,648 +1844,662 @@ class Monitor { * ``` */ class Marshal { - static #revertTarget = Symbol("Marshal.revertTarget"); - static #sourceDomain = Symbol("Marshal.sourceDomain"); - - static #marshalRules = new WeakMap(); - static #marshalledProxies = new WeakSet(); - - constructor() { - throw new TypeError("Marshal 类无法被构造"); - } - - /** - * ```plain - * 判断是否应该封送 - * ``` - * - * @param {any} obj - * @returns {boolean} - */ - static #shouldMarshal = function (obj) { - if (obj === Marshal - || obj === Rule - || obj === AccessAction - || obj === Domain - || obj === Sandbox - || obj instanceof Domain) - return false; - - return true; - } - - /** - * ```plain - * 判断是否禁止封送 - * ``` - * - * @param {any} obj - * @returns {boolean} - */ - static #strictMarshal = function (obj) { - return obj instanceof Sandbox - || obj instanceof Rule - || obj instanceof Monitor; - } - - /** - * ```plain - * 拆除封送代理 - * ``` - * - * @typedef {[ - * Domain, - * Object, - * ]} Reverted - * - * @param {any} proxy - * @returns {Reverted} - */ - static #revertProxy = function (proxy) { - return [ - proxy[Marshal.#sourceDomain], - proxy[Marshal.#revertTarget], - ]; - } - - /** - * ```plain - * 检查封送缓存 - * ``` - * - * @param {Object} obj - * @param {Domain} domain - * @returns {Object?} - */ - static #cacheProxy = function (obj, domain) { - return domain[SandboxExposer] - (SandboxSignal_GetMarshalledProxy, obj); - } - - /** - * ```plain - * 获取指定对象的封送规则引用 - * ``` - * - * @param {Object} obj - * @returns {{rule: Rule}} - */ - static #ensureRuleRef = function (obj) { - let rule = Marshal.#marshalRules.get(obj); - - if (!rule) - Marshal.#marshalRules.set(obj, rule = { rule: null }); - - return rule; - } - - /** - * ```plain - * 判断某个对象是否指定了封送规则 - * ``` - * - * @param {Object} obj - * @returns {boolean} - */ - static hasRule(obj) { - return Marshal.#marshalRules.has(obj); - } - - /** - * ```plain - * 指定某个对象的封送规则 - * ``` - * - * @param {Object} obj - * @param {Rule} rule - */ - static setRule(obj, rule) { - if (Marshal.#marshalledProxies.has(obj)) - throw new ReferenceError("无法为封送对象设置封送规则"); - - const ref = Marshal.#ensureRuleRef(obj); - - if (ref.rule) - throw new ReferenceError("对象的封送规则已经被设置"); - - ref.rule = rule; - } - - /** - * ```plain - * 判断某个对象是否是其他运行域被封送的对象 - * ``` - * - * @param {Object} obj - * @returns {boolean} - */ - static isMarshalled(obj) { - return Marshal.#marshalledProxies.has(obj); - } - - /** - * ```plain - * 获取封送对象的源运行域 - * ``` - * - * @param {Object} obj - * @returns {Domain?} - */ - static getMarshalledDomain(obj) { - if (!Marshal.#marshalledProxies.has(obj)) - return null; - - const [domain,] = Marshal.#revertProxy(obj); - return domain; - } - - /** - * ```plain - * 陷入某个运行域并执行代码 - * ``` - * - * @param {Domain} domain - * @param {() => any} action - */ - static #trapDomain = function (domain, action) { - const prevDomain = Domain.current; - - // 如果可能,应该尽量避免陷入相同运行域 - if (prevDomain === domain) - return console.warn("trapDomain 处于相同 domain"), action(); - - Domain[SandboxExposer2](SandboxSignal_EnterDomain, domain); - - try { - return action(); - } catch (e) { - throw Marshal.#marshal(e, prevDomain); - } finally { - Domain[SandboxExposer2](SandboxSignal_ExitDomain); - } - } - - /** - * ```plain - * 封送数组 - * ``` - * - * @param {Array} array - * @param {Domain} targetDomain - * @returns {Array} - */ - static #marshalArray = function (array, targetDomain) { - if (isPrimitive(array)) - return array; - - // 构造目标域的数组,并逐个元素封送 - const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); - const newArray = new window.Array(array.length); - - for (let i = 0; i < newArray.length; i++) - newArray[i] = Marshal.#marshal(array[i], targetDomain); - - return newArray; - } - - /** - * ```plain - * 封送对象 - * ``` - * - * @param {Object} object - * @param {Domain} targetDomain - * @returns {Object} - */ - static #marshalObject = function (object, targetDomain) { - if (isPrimitive(object)) - return object; - - // 构造目标域的对象,并逐个属性封送 - const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); - const newObject = new window.Object(); - - for (const key of Reflect.ownKeys(object)) - newObject[key] = Marshal.#marshal(object[key], targetDomain); - - return newObject; - } - - /** - * @param {Object} obj - * @param {Domain} targetDomain - * @returns {Object} - */ - static #marshal = function (obj, targetDomain) { - // 基元封送 - if (isPrimitive(obj)) - return obj; - - // 尝试拆除代理 - let [sourceDomain, target] = - Marshal.#marshalledProxies.has(obj) - ? Marshal.#revertProxy(obj) - : [Domain.current, obj]; - - // target: 确保拆除了封送代理的对象 - // sourceDomain: target所属的运行域 - // targetDomain: 要封送到的运行域 - - if (sourceDomain === targetDomain) - return target; - - // 检查基本封送条件 - if (Marshal.#strictMarshal(target) - || sourceDomain.isUnsafe(target)) - throw new TypeError("对象无法封送"); - if (!Marshal.#shouldMarshal(target)) - return target; - - // 全局变量封送 - const mapped = Globals.mapTo(target, sourceDomain, targetDomain); - - if (mapped != null) - return mapped; - - // 错误封送 - if (sourceDomain.isError(target)) { - // 把源错误对象克隆到目标运行域 - const errorCtor = target.constructor; - const mappedCtor = Globals.mapTo(errorCtor, sourceDomain, targetDomain); - - if (mappedCtor) { - const newError = new mappedCtor(); - Object.defineProperties(newError, - Object.getOwnPropertyDescriptors(target)); - return newError; - } - } - - // 检查封送权限 - const ruleRef = Marshal.#ensureRuleRef(target); // 为加快访问速度使用了引用 - const rule = ruleRef.rule; - - if (rule && !rule.canMarshalTo(targetDomain)) - throw new TypeError("无法将对象封送到目标运行域"); - - // 检查封送缓存 - const cached = Marshal.#cacheProxy(target, targetDomain); - - if (cached) - return cached; - - // 创建封送代理 - const proxy = new Proxy(target, { - apply(target, thisArg, argArray) { - const defaultApply = () => { - const marshalledThis = Marshal.#marshal(thisArg, sourceDomain); - const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); - - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.CALL, - target, marshalledThis, marshalledArgs)) - throw new ReferenceError("Access denied"); - - const args = [target, marshalledThis, marshalledArgs]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.CALL, args); - - if (dispatched.preventDefault) - return Marshal.#marshal(dispatched.returnValue, targetDomain); - - // @ts-ignore - const result = Reflect.apply(...args); - return Marshal.#marshal(result, targetDomain); - }); - }; - - // 此处处理异步封送 - // 如果没有逃逸情况,此处代表着当前是异步调用 - if (Domain.current !== targetDomain) - return Marshal.#trapDomain(targetDomain, defaultApply); - - return defaultApply(); - }, - construct(target, argArray, newTarget) { - const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); - const marshalledNewTarget = Marshal.#marshal(newTarget, sourceDomain); - - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.NEW, - target, argArray, newTarget)) - throw new ReferenceError("Access denied"); - - const args = [target, marshalledArgs, marshalledNewTarget]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.NEW, args); - - if (dispatched.preventDefault) - return Marshal.#marshal(dispatched.returnValue, targetDomain); - - // @ts-ignore - const result = Reflect.construct(...args); - return Marshal.#marshal(result, targetDomain); - }); - }, - defineProperty(target, property, attributes) { - let getter = attributes.get; - let setter = attributes.set; - - if (typeof getter == "function") - getter = Marshal.#marshal(getter, sourceDomain); - if (typeof setter == "function") - setter = Marshal.#marshal(setter, sourceDomain); - - const window = sourceDomain[SandboxExposer](SandboxSignal_GetWindow); - const descriptor = new window.Object(); - - if ("value" in attributes) - descriptor.value = Marshal.#marshal(attributes.value, sourceDomain); - if ("get" in attributes) - descriptor.get = getter; - if ("set" in attributes) - descriptor.set = setter; - if ("writable" in attributes) - descriptor.writable = !!attributes.writable; - if ("enumerable" in attributes) - descriptor.enumerable = !!attributes.enumerable; - if ("configurable" in attributes) - descriptor.configurable = !!attributes.configurable; - - const isSourceDomain = sourceDomain === Domain.current; - const domainTrapAction = () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.DEFINE, - target, property, descriptor)) - throw new ReferenceError("Access denied"); - - const args = [target, property, descriptor]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.DEFINE, args); - - if (dispatched.preventDefault) - return !!dispatched.returnValue; - - // @ts-ignore - return Reflect.defineProperty(...args); - }; - - // `defineProperty`、`getOwnPropertyDescriptor`、`has` 都可能被JavaScript引擎重复调用 - // 故在执行之前,为避免 `trapDomain` 的警告,我们先进行一次判断 - return isSourceDomain - ? domainTrapAction() - : Marshal.#trapDomain(sourceDomain, domainTrapAction); - }, - deleteProperty(target, p) { - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.DELETE, target, p)) - throw new ReferenceError("Access denied"); - - const args = [target, p]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.DELETE, args); - - if (dispatched.preventDefault) - return !!dispatched.returnValue; - - // @ts-ignore - return Reflect.deleteProperty(...args); - }); - }, - get(target, p, receiver) { - // 因为 get 的东西最多,所以对此追加注释 - // 其他的拦截器都是与 get 类似 - - // 向外暴露封送 - switch (p) { - case Marshal.#revertTarget: - return target; - case Marshal.#sourceDomain: - return sourceDomain; - } - - // 默认封送 - const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); - - // 陷入源运行域执行 - return Marshal.#trapDomain(sourceDomain, () => { - // 获取封送规则并检查 - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.READ, - target, p, marshalledReceiver)) - throw new ReferenceError("Access denied"); - - // 通知 Monitor - const args = [target, p, marshalledReceiver]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.READ, args); - - // 处理 Monitor 的结果 - if (dispatched.preventDefault) - return Marshal.#marshal(dispatched.returnValue, targetDomain); - - // 执行默认流程 - // @ts-ignore - const result = Reflect.get(...args); - return Marshal.#marshal(result, targetDomain); - }); - }, - getOwnPropertyDescriptor(target, p) { - const isSourceDomain = Domain.current === sourceDomain; - - const domainTrapAction = () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.DESCRIBE, target, p)) - throw new ReferenceError("Access denied"); - - const args = [target, p]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.DESCRIBE, args); - - if (dispatched.preventDefault) - return dispatched.returnValue; - - // @ts-ignore - return Reflect.getOwnPropertyDescriptor(...args); - }; - - if (isSourceDomain) - return domainTrapAction(); - - const descriptor = Marshal.#trapDomain(sourceDomain, domainTrapAction); - return Marshal.#marshalObject(descriptor, targetDomain); - }, - getPrototypeOf(target) { - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.TRACE, target)) - throw new ReferenceError("Access denied"); - - const args = [target]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.TRACE, args); - - if (dispatched.preventDefault) - return Marshal.#marshal(dispatched.returnValue, targetDomain); - - // @ts-ignore - const result = Reflect.getPrototypeOf(...args); - const marshalledResult = Marshal.#marshal(result, targetDomain); - - if (Marshal.#marshalledProxies.has(marshalledResult)) - return null; // 没有实装hasInstance喵,只能折中处理喵 - - return marshalledResult; - }); - }, - has(target, p) { - const isSourceDomain = Domain.current === sourceDomain; - const domainTrapAction = () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.EXISTS, target, p)) - throw new ReferenceError("Access denied"); - - const args = [target, p]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.EXISTS, args); - - if (dispatched.preventDefault) - return !!dispatched.returnValue; - - // @ts-ignore - return Reflect.has(...args); - }; - - if (isSourceDomain) - return domainTrapAction(); - - return Marshal.#trapDomain(sourceDomain, domainTrapAction); - }, - isExtensible(target) { - return Reflect.isExtensible(target); - }, - ownKeys(target) { - const keys = Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.LIST, target)) - throw new ReferenceError("Access denied"); - - const args = [target]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.LIST, args); - - if (dispatched.preventDefault) - return dispatched.returnValue; - - // @ts-ignore - return Reflect.ownKeys(...args); - }); - - return Marshal.#marshalArray(keys, targetDomain); - }, - preventExtensions(target) { - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.SEAL, target)) - throw new ReferenceError("Access denied"); - - const args = [target]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.SEAL, args); - - if (dispatched.preventDefault) - return !!dispatched.returnValue; - - // @ts-ignore - return Reflect.preventExtensions(...args); - }); - }, - set(target, p, newValue, receiver) { - const marshalledNewValue = Marshal.#marshal(newValue, sourceDomain); - const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); - - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.WRITE, - target, p, marshalledNewValue, marshalledReceiver)) - throw new ReferenceError("Access denied"); - - const args = [target, p, marshalledNewValue, marshalledReceiver]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.WRITE, args); - - if (dispatched.preventDefault) - return !!dispatched.returnValue; - - // @ts-ignore - return Reflect.set(...args); - }); - }, - setPrototypeOf(target, v) { - const marshalledV = Marshal.#marshal(v, sourceDomain); - - if (Marshal.#marshalledProxies.has(marshalledV)) - return false; // 没有实装hasInstance喵,只能折中处理喵 - - return Marshal.#trapDomain(sourceDomain, () => { - const rule = ruleRef.rule; - - if (rule && !rule.canAccess(AccessAction.META, target, marshalledV)) - throw new ReferenceError("Access denied"); - - const args = [target, marshalledV]; - const dispatched = DomainMonitors.dispatch( - sourceDomain, targetDomain, AccessAction.META, args); - - if (dispatched.preventDefault) - return !!dispatched.returnValue; - - // @ts-ignore - return Reflect.setPrototypeOf(...args); - }); - }, - }); - - Marshal.#marshalledProxies.add(proxy); - targetDomain[SandboxExposer] - (SandboxSignal_SetMarshalledProxy, target, proxy); - return proxy; - } - - /** - * @param {Symbol} signal - * @param {...any} args - */ - static [SandboxExposer2](signal, ...args) { - switch (signal) { - case SandboxSignal_Marshal: - // @ts-ignore - return Marshal.#marshal(...args); - case SandboxSignal_MarshalArray: - // @ts-ignore - return Marshal.#marshalArray(...args); - case SandboxSignal_UnpackProxy: - // @ts-ignore - return Marshal.#revertProxy(...args); - case SandboxSignal_TrapDomain: - // @ts-ignore - return Marshal.#trapDomain(...args); - } - } + static #revertTarget = Symbol("Marshal.revertTarget"); + static #sourceDomain = Symbol("Marshal.sourceDomain"); + + static #marshalRules = new WeakMap(); + static #marshalledProxies = new WeakSet(); + + constructor() { + throw new TypeError("Marshal 类无法被构造"); + } + + /** + * ```plain + * 判断是否应该封送 + * ``` + * + * @param {any} obj + * @returns {boolean} + */ + static #shouldMarshal = function (obj) { + if (obj === Marshal + || obj === Rule + || obj === AccessAction + || obj === Domain + || obj === Sandbox + || obj instanceof Domain) + return false; + + return true; + } + + /** + * ```plain + * 判断是否禁止封送 + * ``` + * + * @param {any} obj + * @returns {boolean} + */ + static #strictMarshal = function (obj) { + return obj instanceof Sandbox + || obj instanceof Rule + || obj instanceof Monitor; + } + + /** + * ```plain + * 拆除封送代理 + * ``` + * + * @typedef {[ + * Domain, + * Object, + * ]} Reverted + * + * @param {any} proxy + * @returns {Reverted} + */ + static #revertProxy = function (proxy) { + return [ + proxy[Marshal.#sourceDomain], + proxy[Marshal.#revertTarget], + ]; + } + + /** + * ```plain + * 检查封送缓存 + * ``` + * + * @param {Object} obj + * @param {Domain} domain + * @returns {Object?} + */ + static #cacheProxy = function (obj, domain) { + return domain[SandboxExposer] + (SandboxSignal_GetMarshalledProxy, obj); + } + + /** + * ```plain + * 获取指定对象的封送规则引用 + * ``` + * + * @param {Object} obj + * @returns {{rule: Rule}} + */ + static #ensureRuleRef = function (obj) { + let rule = Marshal.#marshalRules.get(obj); + + if (!rule) + Marshal.#marshalRules.set(obj, rule = { rule: null }); + + return rule; + } + + /** + * ```plain + * 判断某个对象是否指定了封送规则 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + static hasRule(obj) { + return Marshal.#marshalRules.has(obj); + } + + /** + * ```plain + * 指定某个对象的封送规则 + * ``` + * + * @param {Object} obj + * @param {Rule} rule + */ + static setRule(obj, rule) { + if (Marshal.#marshalledProxies.has(obj)) + throw new ReferenceError("无法为封送对象设置封送规则"); + + const ref = Marshal.#ensureRuleRef(obj); + + if (ref.rule) + throw new ReferenceError("对象的封送规则已经被设置"); + + ref.rule = rule; + } + + /** + * ```plain + * 判断某个对象是否是其他运行域被封送的对象 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + static isMarshalled(obj) { + return Marshal.#marshalledProxies.has(obj); + } + + /** + * ```plain + * 获取封送对象的源运行域 + * ``` + * + * @param {Object} obj + * @returns {Domain?} + */ + static getMarshalledDomain(obj) { + if (!Marshal.#marshalledProxies.has(obj)) + return null; + + const [domain,] = Marshal.#revertProxy(obj); + return domain; + } + + /** + * ```plain + * 陷入某个运行域并执行代码 + * ``` + * + * @param {Domain} domain + * @param {() => any} action + */ + static #trapDomain = function (domain, action) { + const prevDomain = Domain.current; + + // 如果可能,应该尽量避免陷入相同运行域 + if (prevDomain === domain) + return console.warn("trapDomain 处于相同 domain"), action(); + + Domain[SandboxExposer2](SandboxSignal_EnterDomain, domain); + + try { + return action(); + } catch (e) { + throw Marshal.#marshal(e, prevDomain); + } finally { + Domain[SandboxExposer2](SandboxSignal_ExitDomain); + } + } + + /** + * ```plain + * 封送数组 + * ``` + * + * @param {Array} array + * @param {Domain} targetDomain + * @returns {Array} + */ + static #marshalArray = function (array, targetDomain) { + if (isPrimitive(array)) + return array; + + // 构造目标域的数组,并逐个元素封送 + const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); + const newArray = new window.Array(array.length); + + for (let i = 0; i < newArray.length; i++) + newArray[i] = Marshal.#marshal(array[i], targetDomain); + + return newArray; + } + + /** + * ```plain + * 封送对象 + * ``` + * + * @param {Object} object + * @param {Domain} targetDomain + * @returns {Object} + */ + static #marshalObject = function (object, targetDomain) { + if (isPrimitive(object)) + return object; + + // 构造目标域的对象,并逐个属性封送 + const window = targetDomain[SandboxExposer](SandboxSignal_GetWindow); + const newObject = new window.Object(); + + for (const key of Reflect.ownKeys(object)) + newObject[key] = Marshal.#marshal(object[key], targetDomain); + + return newObject; + } + + /** + * @param {Object} obj + * @param {Domain} targetDomain + * @returns {Object} + */ + static #marshal = function (obj, targetDomain) { + // 基元封送 + if (isPrimitive(obj)) + return obj; + + // 尝试拆除代理 + let [sourceDomain, target] = + Marshal.#marshalledProxies.has(obj) + ? Marshal.#revertProxy(obj) + : [Domain.current, obj]; + + // target: 确保拆除了封送代理的对象 + // sourceDomain: target所属的运行域 + // targetDomain: 要封送到的运行域 + + if (sourceDomain === targetDomain) + return target; + + // 检查基本封送条件 + if (Marshal.#strictMarshal(target) + || sourceDomain.isUnsafe(target)) + throw new TypeError("对象无法封送"); + if (!Marshal.#shouldMarshal(target)) + return target; + + // 全局变量封送 + const mapped = Globals.mapTo(target, sourceDomain, targetDomain); + + if (mapped != null) + return mapped; + + // 错误封送 + if (sourceDomain.isError(target)) { + // 把源错误对象克隆到目标运行域 + const errorCtor = target.constructor; + const mappedCtor = Globals.mapTo(errorCtor, sourceDomain, targetDomain); + + if (mappedCtor) { + const newError = new mappedCtor(); + Object.defineProperties(newError, + Object.getOwnPropertyDescriptors(target)); + return newError; + } + } + + // 检查封送权限 + const ruleRef = Marshal.#ensureRuleRef(target); // 为加快访问速度使用了引用 + const rule = ruleRef.rule; + + if (rule && !rule.canMarshalTo(targetDomain)) + throw new TypeError("无法将对象封送到目标运行域"); + + // 检查封送缓存 + const cached = Marshal.#cacheProxy(target, targetDomain); + + if (cached) + return cached; + + // 创建封送代理 + const proxy = new Proxy(target, { + apply(target, thisArg, argArray) { + const defaultApply = () => { + const marshalledThis = Marshal.#marshal(thisArg, sourceDomain); + const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.CALL, + target, marshalledThis, marshalledArgs)) + throw new ReferenceError("Access denied"); + + const args = [target, marshalledThis, marshalledArgs]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.CALL, args); + + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // @ts-ignore + const result = Reflect.apply(...args); + return Marshal.#marshal(result, targetDomain); + }); + }; + + // 此处处理异步封送 + // 如果没有逃逸情况,此处代表着当前是异步调用 + if (Domain.current !== targetDomain) + return Marshal.#trapDomain(targetDomain, defaultApply); + + return defaultApply(); + }, + construct(target, argArray, newTarget) { + const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); + const marshalledNewTarget = Marshal.#marshal(newTarget, sourceDomain); + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.NEW, + target, argArray, newTarget)) + throw new ReferenceError("Access denied"); + + const args = [target, marshalledArgs, marshalledNewTarget]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.NEW, args); + + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // @ts-ignore + const result = Reflect.construct(...args); + return Marshal.#marshal(result, targetDomain); + }); + }, + defineProperty(target, property, attributes) { + const isSourceDomain = sourceDomain === Domain.current; + + if (!isSourceDomain) { + let getter = attributes.get; + let setter = attributes.set; + + if (typeof getter == "function") + getter = Marshal.#marshal(getter, sourceDomain); + if (typeof setter == "function") + setter = Marshal.#marshal(setter, sourceDomain); + + const window = sourceDomain[SandboxExposer](SandboxSignal_GetWindow); + const descriptor = new window.Object(); + + if ("value" in attributes) + descriptor.value = Marshal.#marshal(attributes.value, sourceDomain); + if ("get" in attributes) + descriptor.get = getter; + if ("set" in attributes) + descriptor.set = setter; + if ("writable" in attributes) + descriptor.writable = !!attributes.writable; + if ("enumerable" in attributes) + descriptor.enumerable = !!attributes.enumerable; + if ("configurable" in attributes) + descriptor.configurable = !!attributes.configurable; + + attributes = descriptor; + } + + const domainTrapAction = () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.DEFINE, + target, property, attributes)) + throw new ReferenceError("Access denied"); + + const args = [target, property, attributes]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.DEFINE, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.defineProperty(...args); + }; + + // `defineProperty`、`getOwnPropertyDescriptor`、`has` 都可能被JavaScript引擎重复调用 + // 故在执行之前,为避免 `trapDomain` 的警告,我们先进行一次判断 + return isSourceDomain + ? domainTrapAction() + : Marshal.#trapDomain(sourceDomain, domainTrapAction); + }, + deleteProperty(target, p) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.DELETE, target, p)) + throw new ReferenceError("Access denied"); + + const args = [target, p]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.DELETE, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.deleteProperty(...args); + }); + }, + get(target, p, receiver) { + // 因为 get 的东西最多,所以对此追加注释 + // 其他的拦截器都是与 get 类似 + + // 向外暴露封送 + switch (p) { + case Marshal.#revertTarget: + return target; + case Marshal.#sourceDomain: + return sourceDomain; + } + + // 默认封送 + const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); + + // 陷入源运行域执行 + return Marshal.#trapDomain(sourceDomain, () => { + // 获取封送规则并检查 + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.READ, + target, p, marshalledReceiver)) + throw new ReferenceError("Access denied"); + + // 通知 Monitor + const args = [target, p, marshalledReceiver]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.READ, args); + + // 处理 Monitor 的结果 + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // 执行默认流程 + // @ts-ignore + const result = Reflect.get(...args); + return Marshal.#marshal(result, targetDomain); + }); + }, + getOwnPropertyDescriptor(target, p) { + const isSourceDomain = Domain.current === sourceDomain; + + const domainTrapAction = () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.DESCRIBE, target, p)) + throw new ReferenceError("Access denied"); + + const args = [target, p]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.DESCRIBE, args); + + if (dispatched.preventDefault) + return dispatched.returnValue; + + // @ts-ignore + return Reflect.getOwnPropertyDescriptor(...args); + }; + + if (isSourceDomain) + return domainTrapAction(); + + return Marshal.#trapDomain(sourceDomain, () => { + const descriptor = domainTrapAction(); + return Marshal.#marshalObject(descriptor, targetDomain); + }); + }, + getPrototypeOf(target) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.TRACE, target)) + throw new ReferenceError("Access denied"); + + const args = [target]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.TRACE, args); + + if (dispatched.preventDefault) + return Marshal.#marshal(dispatched.returnValue, targetDomain); + + // @ts-ignore + const result = Reflect.getPrototypeOf(...args); + const marshalledResult = Marshal.#marshal(result, targetDomain); + + if (Marshal.#marshalledProxies.has(marshalledResult)) + return null; // 没有实装hasInstance喵,只能折中处理喵 + + return marshalledResult; + }); + }, + has(target, p) { + const isSourceDomain = Domain.current === sourceDomain; + const domainTrapAction = () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.EXISTS, target, p)) + throw new ReferenceError("Access denied"); + + const args = [target, p]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.EXISTS, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.has(...args); + }; + + if (isSourceDomain) + return domainTrapAction(); + + return Marshal.#trapDomain(sourceDomain, domainTrapAction); + }, + isExtensible(target) { + return Reflect.isExtensible(target); + }, + ownKeys(target) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.LIST, target)) + throw new ReferenceError("Access denied"); + + const args = [target]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.LIST, args); + + /** @type {Array} */ + let keys; + + if (dispatched.preventDefault) { + if (!Array.isArray(dispatched.returnValue)) + throw new TypeError("`Reflect.ownKeys` 必须返回一个数组"); + + keys = dispatched.returnValue; + } else + // @ts-ignore + keys = Reflect.ownKeys(...args); + + return Marshal.#marshalArray(keys, targetDomain); + }); + + }, + preventExtensions(target) { + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.SEAL, target)) + throw new ReferenceError("Access denied"); + + const args = [target]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.SEAL, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.preventExtensions(...args); + }); + }, + set(target, p, newValue, receiver) { + const marshalledNewValue = Marshal.#marshal(newValue, sourceDomain); + const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.WRITE, + target, p, marshalledNewValue, marshalledReceiver)) + throw new ReferenceError("Access denied"); + + const args = [target, p, marshalledNewValue, marshalledReceiver]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.WRITE, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.set(...args); + }); + }, + setPrototypeOf(target, v) { + const marshalledV = Marshal.#marshal(v, sourceDomain); + + if (Marshal.#marshalledProxies.has(marshalledV)) + return false; // 没有实装hasInstance喵,只能折中处理喵 + + return Marshal.#trapDomain(sourceDomain, () => { + const rule = ruleRef.rule; + + if (rule && !rule.canAccess(AccessAction.META, target, marshalledV)) + throw new ReferenceError("Access denied"); + + const args = [target, marshalledV]; + const dispatched = DomainMonitors.dispatch( + sourceDomain, targetDomain, AccessAction.META, args); + + if (dispatched.preventDefault) + return !!dispatched.returnValue; + + // @ts-ignore + return Reflect.setPrototypeOf(...args); + }); + }, + }); + + Marshal.#marshalledProxies.add(proxy); + targetDomain[SandboxExposer] + (SandboxSignal_SetMarshalledProxy, target, proxy); + return proxy; + } + + /** + * @param {Symbol} signal + * @param {...any} args + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_Marshal: + // @ts-ignore + return Marshal.#marshal(...args); + case SandboxSignal_MarshalArray: + // @ts-ignore + return Marshal.#marshalArray(...args); + case SandboxSignal_UnpackProxy: + // @ts-ignore + return Marshal.#revertProxy(...args); + case SandboxSignal_TrapDomain: + // @ts-ignore + return Marshal.#trapDomain(...args); + } + } } /** @@ -2496,381 +2510,381 @@ class Marshal { * ``` */ class Domain { - static #hasInstance = Object[Symbol.hasInstance]; + static #hasInstance = Object[Symbol.hasInstance]; - /** @type {Array} */ - static #domainStack = []; - /** @type {Domain} */ - static #currentDomain; - /** @type {Domain} */ - static #topDomain; + /** @type {Array} */ + static #domainStack = []; + /** @type {Domain} */ + static #currentDomain; + /** @type {Domain} */ + static #topDomain; - /** @type {Array>} */ - static #domainLinks = []; + /** @type {Array>} */ + static #domainLinks = []; - #domainName = "top"; + #domainName = "top"; - /** @type {typeof Object} */ - #domainObject; - /** @type {typeof Error} */ - #domainError; - /** @type {typeof Promise} */ - #domainPromise; - /** @type {typeof HTMLIFrameElement} */ - #domainIFrame; - /** @type {Navigator} */ - #domainNavigator; - /** @type {ServiceWorker} */ - #domainServiceWorker; - /** @type {Location} */ - #domainLocation; - /** @type {Function} */ - #domainOpen; - /** @type {Function} */ - #domainClose; - /** @type {typeof Worker} */ - #domainWorker; - /** @type {Object} */ - #domainCSS; - /** @type {typeof SharedWorker} */ - #domainSharedWorker; - /** @type {Window} */ - #domainRoot; - /** @type {WeakMap} */ - #marshalledCached = new WeakMap(); + /** @type {typeof Object} */ + #domainObject; + /** @type {typeof Error} */ + #domainError; + /** @type {typeof Promise} */ + #domainPromise; + /** @type {typeof HTMLIFrameElement} */ + #domainIFrame; + /** @type {Navigator} */ + #domainNavigator; + /** @type {ServiceWorker} */ + #domainServiceWorker; + /** @type {Location} */ + #domainLocation; + /** @type {Function} */ + #domainOpen; + /** @type {Function} */ + #domainClose; + /** @type {typeof Worker} */ + #domainWorker; + /** @type {Object} */ + #domainCSS; + /** @type {typeof SharedWorker} */ + #domainSharedWorker; + /** @type {Window} */ + #domainRoot; + /** @type {WeakMap} */ + #marshalledCached = new WeakMap(); - /** @type {(array: any) => boolean} */ - #domainIsArray; + /** @type {(array: any) => boolean} */ + #domainIsArray; - /** - * ```plain - * 创建运行域 - * - * 一般不直接使用, - * 请考虑使用直接创建沙盒 - * ``` - */ - constructor() { - // @ts-ignore - let global = window.replacedGlobal || window; + /** + * ```plain + * 创建运行域 + * + * 一般不直接使用, + * 请考虑使用直接创建沙盒 + * ``` + */ + constructor() { + // @ts-ignore + let global = window.replacedGlobal || window; - if (Domain.#currentDomain) { - // @ts-ignore - if (!window.createRealms) - throw new ReferenceError("Sandbox 载入时处于不安全运行域"); + if (Domain.#currentDomain) { + // @ts-ignore + if (!window.createRealms) + throw new ReferenceError("Sandbox 载入时处于不安全运行域"); - // 创建新的运行变量域 - // @ts-ignore - global = createRealms(); - this.#domainName = Math.random().toString(36).slice(2); - } else - NativeWrapper.initTopDomain(global); + // 创建新的运行变量域 + // @ts-ignore + global = createRealms(); + this.#domainName = Math.random().toString(36).slice(2); + } else + NativeWrapper.initTopDomain(global); - this.#domainRoot = global; - this.#domainObject = global.Object; - this.#domainError = global.Error; - this.#domainPromise = global.Promise; - this.#domainIFrame = global.HTMLIFrameElement; - this.#domainNavigator = global.navigator; - this.#domainServiceWorker = global.navigator.serviceWorker; - this.#domainLocation = global.location; - this.#domainOpen = global.open; - this.#domainClose = global.close; - this.#domainWorker = global.Worker; - this.#domainCSS = global.CSS; - this.#domainSharedWorker = global.SharedWorker; + this.#domainRoot = global; + this.#domainObject = global.Object; + this.#domainError = global.Error; + this.#domainPromise = global.Promise; + this.#domainIFrame = global.HTMLIFrameElement; + this.#domainNavigator = global.navigator; + this.#domainServiceWorker = global.navigator.serviceWorker; + this.#domainLocation = global.location; + this.#domainOpen = global.open; + this.#domainClose = global.close; + this.#domainWorker = global.Worker; + this.#domainCSS = global.CSS; + this.#domainSharedWorker = global.SharedWorker; - this.#domainIsArray = global.Array.isArray; + this.#domainIsArray = global.Array.isArray; - NativeWrapper.wrapInDomains(global); - Globals.ensureDomainGlobals(this); - DomainMonitors.handleNewDomain(this); - Domain.#domainLinks.push(new WeakRef(this)); - sealObjectTree(this); + NativeWrapper.wrapInDomains(global); + Globals.ensureDomainGlobals(this); + DomainMonitors.handleNewDomain(this); + Domain.#domainLinks.push(new WeakRef(this)); + sealObjectTree(this); - global.Array.isArray = new global - .Function("domain", "array", "return this(domain, array)") - .bind(Domain.#isArray, this); - } + global.Array.isArray = new global + .Function("domain", "array", "return this(domain, array)") + .bind(Domain.#isArray, this); + } - // 实装这个要代理Object喵 - // static #hasInstanceMarshalled = function (obj) { - // if (Marshal.isMarshalled(obj)) - // [, obj] = Marshal[SandboxExposer2] - // (SandboxSignal_UnpackProxy, obj); + // 实装这个要代理Object喵 + // static #hasInstanceMarshalled = function (obj) { + // if (Marshal.isMarshalled(obj)) + // [, obj] = Marshal[SandboxExposer2] + // (SandboxSignal_UnpackProxy, obj); - // return Domain.#hasInstance.call(this, obj); - // } + // return Domain.#hasInstance.call(this, obj); + // } - // 效率影响不确定,暂不实装 - // static #marshalledThen = function (onfulfilled, onrejected) { - // if (Marshal.isMarshalled(this)) { - // const [domain, promise] = Marshal[SandboxExposer2] - // (SandboxSignal_UnpackProxy, this); + // 效率影响不确定,暂不实装 + // static #marshalledThen = function (onfulfilled, onrejected) { + // if (Marshal.isMarshalled(this)) { + // const [domain, promise] = Marshal[SandboxExposer2] + // (SandboxSignal_UnpackProxy, this); - // const marshaller = value => { - // return this(trapMarshal(domain, Domain.current, value)); - // }; + // const marshaller = value => { + // return this(trapMarshal(domain, Domain.current, value)); + // }; - // return trapMarshal(domain, Domain.current, promise.then( - // marshaller.bind(onfulfilled), - // marshaller.bind(onrejected) - // )); - // } + // return trapMarshal(domain, Domain.current, promise.then( + // marshaller.bind(onfulfilled), + // marshaller.bind(onrejected) + // )); + // } - // return [[DefaultThen]].call(this, onfulfilled, onrejected); - // } + // return [[DefaultThen]].call(this, onfulfilled, onrejected); + // } - /** - * ```plain - * 检查对象是否来自于当前的运行域 - * ``` - * - * @param {Object} obj - * @returns {boolean} - */ - isFrom(obj) { - if (Marshal.isMarshalled(obj)) { - const [domain,] = Marshal[SandboxExposer2] - (SandboxSignal_UnpackProxy, obj); - return domain === this; - } + /** + * ```plain + * 检查对象是否来自于当前的运行域 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + isFrom(obj) { + if (Marshal.isMarshalled(obj)) { + const [domain,] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, obj); + return domain === this; + } - return Domain.#hasInstance - .call(this.#domainObject, obj); - } + return Domain.#hasInstance + .call(this.#domainObject, obj); + } - /** - * ```plain - * 检查对象是否来自于当前的运行域的Promise - * ``` - * - * @param {Promise} promise - * @returns {boolean} - */ - isPromise(promise) { - if (Marshal.isMarshalled(promise)) - [, promise] = Marshal[SandboxExposer2] - (SandboxSignal_UnpackProxy, promise); + /** + * ```plain + * 检查对象是否来自于当前的运行域的Promise + * ``` + * + * @param {Promise} promise + * @returns {boolean} + */ + isPromise(promise) { + if (Marshal.isMarshalled(promise)) + [, promise] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, promise); - return Domain.#hasInstance - .call(this.#domainPromise, promise); - } + return Domain.#hasInstance + .call(this.#domainPromise, promise); + } - /** - * ```plain - * 检查对象是否来自于当前的运行域的Error - * ``` - * - * @param {Error} error - * @returns {boolean} - */ - isError(error) { - if (Marshal.isMarshalled(error)) - [, error] = Marshal[SandboxExposer2] - (SandboxSignal_UnpackProxy, error); + /** + * ```plain + * 检查对象是否来自于当前的运行域的Error + * ``` + * + * @param {Error} error + * @returns {boolean} + */ + isError(error) { + if (Marshal.isMarshalled(error)) + [, error] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, error); - return Domain.#hasInstance - .call(this.#domainError, error); - } + return Domain.#hasInstance + .call(this.#domainError, error); + } - /** - * ```plain - * 检查对象是否来自于当前的运行域的危险对象 - * ``` - * - * @param {Object} obj - * @returns {boolean} - */ - isUnsafe(obj) { - if (Marshal.isMarshalled(obj)) - [, obj] = Marshal[SandboxExposer2] - (SandboxSignal_UnpackProxy, obj); + /** + * ```plain + * 检查对象是否来自于当前的运行域的危险对象 + * ``` + * + * @param {Object} obj + * @returns {boolean} + */ + isUnsafe(obj) { + if (Marshal.isMarshalled(obj)) + [, obj] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, obj); - if (obj === this.#domainRoot) - return true; - if (Domain.#hasInstance.call(this.#domainIFrame, obj)) - return true; - if (obj === this.#domainNavigator) - return true; - if (obj === this.#domainServiceWorker) - return true; - if (obj === this.#domainLocation) - return true; - if (obj === this.#domainOpen) - return true; - if (obj === this.#domainClose) - return true; - if (Domain.#hasInstance.call(this.#domainWorker, obj)) - return true; - if (obj === this.#domainCSS) - return true; - if (Domain.#hasInstance.call(this.#domainSharedWorker, obj)) - return true; + if (obj === this.#domainRoot) + return true; + if (Domain.#hasInstance.call(this.#domainIFrame, obj)) + return true; + if (obj === this.#domainNavigator) + return true; + if (obj === this.#domainServiceWorker) + return true; + if (obj === this.#domainLocation) + return true; + if (obj === this.#domainOpen) + return true; + if (obj === this.#domainClose) + return true; + if (Domain.#hasInstance.call(this.#domainWorker, obj)) + return true; + if (obj === this.#domainCSS) + return true; + if (Domain.#hasInstance.call(this.#domainSharedWorker, obj)) + return true; - return false; - } + return false; + } - toString() { - return `[Domain ${this.#domainName}]`; - } + toString() { + return `[Domain ${this.#domainName}]`; + } - /** - * ```plain - * 替代 Array.isArray 并暴露给外部 - * 确保 Array.isArray 对代理数组放宽 - * ``` - * - * @param {any} array - * @returns {boolean} - */ - static #isArray = function (domain, array) { - if (Marshal.isMarshalled(array)) - [domain, array] = Marshal[SandboxExposer2] - (SandboxSignal_UnpackProxy, array); + /** + * ```plain + * 替代 Array.isArray 并暴露给外部 + * 确保 Array.isArray 对代理数组放宽 + * ``` + * + * @param {any} array + * @returns {boolean} + */ + static #isArray = function (domain, array) { + if (Marshal.isMarshalled(array)) + [domain, array] = Marshal[SandboxExposer2] + (SandboxSignal_UnpackProxy, array); - return domain.#domainIsArray(array); - }; + return domain.#domainIsArray(array); + }; - /** - * @param {Domain} domain - */ - static #enterDomain = function (domain) { - Domain.#domainStack.push(Domain.#currentDomain); - Domain.#currentDomain = domain; - } + /** + * @param {Domain} domain + */ + static #enterDomain = function (domain) { + Domain.#domainStack.push(Domain.#currentDomain); + Domain.#currentDomain = domain; + } - static #exitDomain = function () { - if (Domain.#domainStack.length < 1) - throw new ReferenceError("无法弹出更多的运行域"); + static #exitDomain = function () { + if (Domain.#domainStack.length < 1) + throw new ReferenceError("无法弹出更多的运行域"); - // @ts-ignore - Domain.#currentDomain = Domain.#domainStack.pop(); - } + // @ts-ignore + Domain.#currentDomain = Domain.#domainStack.pop(); + } - /** - * @returns {Array} - */ - static #listDomain = function () { - const links = Domain.#domainLinks; - const list = []; + /** + * @returns {Array} + */ + static #listDomain = function () { + const links = Domain.#domainLinks; + const list = []; - // 遍历查询并清除无效的运行域 - for (let i = links.length - 1; i >= 0; i--) { - const link = links[i].deref(); + // 遍历查询并清除无效的运行域 + for (let i = links.length - 1; i >= 0; i--) { + const link = links[i].deref(); - if (!link) - links.splice(i, 1); + if (!link) + links.splice(i, 1); - list.push(link); - } + list.push(link); + } - // @ts-ignore - return list; - } + // @ts-ignore + return list; + } - /** - * ```plain - * 获取当前运行域 - * ``` - * - * @type {Domain} - */ - static get current() { - return Domain.#currentDomain; - } + /** + * ```plain + * 获取当前运行域 + * ``` + * + * @type {Domain} + */ + static get current() { + return Domain.#currentDomain; + } - /** - * ```plain - * 获取调用链中上一个运行域 - * ``` - * - * @type {Domain?} - */ - static get caller() { - for (let i = Domain.#domainStack.length; i >= 0; i--) { - const domain = Domain.#domainStack[i]; + /** + * ```plain + * 获取调用链中上一个运行域 + * ``` + * + * @type {Domain?} + */ + static get caller() { + for (let i = Domain.#domainStack.length; i >= 0; i--) { + const domain = Domain.#domainStack[i]; - if (domain !== Domain.#currentDomain) - return domain; - } + if (domain !== Domain.#currentDomain) + return domain; + } - return null; - } + return null; + } - /** - * ```plain - * 获取顶级运行域 - * ``` - * - * @type {Domain} - */ - static get topDomain() { - return Domain.#topDomain; - } + /** + * ```plain + * 获取顶级运行域 + * ``` + * + * @type {Domain} + */ + static get topDomain() { + return Domain.#topDomain; + } - /** - * ```plain - * 检查当前的调用是否来自可信的运行域 - * - * 如果检查顶级运行域,则要求没有进行任何其他运行域的陷入 - * 如果检查非顶级运行域,则要求只有顶级运行域与给定运行域的陷入 - * ``` - * - * @param {Domain} domain - */ - static isBelievable(domain) { - if (domain === Domain.#topDomain) - return !Domain.#domainStack.length; + /** + * ```plain + * 检查当前的调用是否来自可信的运行域 + * + * 如果检查顶级运行域,则要求没有进行任何其他运行域的陷入 + * 如果检查非顶级运行域,则要求只有顶级运行域与给定运行域的陷入 + * ``` + * + * @param {Domain} domain + */ + static isBelievable(domain) { + if (domain === Domain.#topDomain) + return !Domain.#domainStack.length; - return Domain.#domainStack.concat([Domain.#currentDomain]) - .every(d => d === Domain.#topDomain || d === domain); - } + return Domain.#domainStack.concat([Domain.#currentDomain]) + .every(d => d === Domain.#topDomain || d === domain); + } - /** - * @param {Symbol} signal - * @param {...any} args - */ - [SandboxExposer](signal, ...args) { - switch (signal) { - case SandboxSignal_GetMarshalledProxy: - // @ts-ignore - return this.#marshalledCached.get(...args); - case SandboxSignal_SetMarshalledProxy: - // @ts-ignore - return void this.#marshalledCached.set(...args); - case SandboxSignal_GetWindow: - return this.#domainRoot; - case SandboxSignal_GetPromise: - return this.#domainPromise; - } - } + /** + * @param {Symbol} signal + * @param {...any} args + */ + [SandboxExposer](signal, ...args) { + switch (signal) { + case SandboxSignal_GetMarshalledProxy: + // @ts-ignore + return this.#marshalledCached.get(...args); + case SandboxSignal_SetMarshalledProxy: + // @ts-ignore + return void this.#marshalledCached.set(...args); + case SandboxSignal_GetWindow: + return this.#domainRoot; + case SandboxSignal_GetPromise: + return this.#domainPromise; + } + } - /** - * @param {Symbol} signal - * @param {...any} args - */ - static [SandboxExposer2](signal, ...args) { - switch (signal) { - case SandboxSignal_InitDomain: - if (Domain.#currentDomain) - throw new TypeError("顶级运行域已经被初始化"); + /** + * @param {Symbol} signal + * @param {...any} args + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_InitDomain: + if (Domain.#currentDomain) + throw new TypeError("顶级运行域已经被初始化"); - Domain.#currentDomain = new Domain(); - Domain.#topDomain = Domain.#currentDomain; - return; - case SandboxSignal_EnterDomain: - // @ts-ignore - return Domain.#enterDomain(...args); - case SandboxSignal_ExitDomain: - return Domain.#exitDomain(); - case SandboxSignal_ListDomain: - return Domain.#listDomain(); - case SandboxSignal_IsArray: - // @ts-ignore - return Domain.#isArray(...args); - } - } + Domain.#currentDomain = new Domain(); + Domain.#topDomain = Domain.#currentDomain; + return; + case SandboxSignal_EnterDomain: + // @ts-ignore + return Domain.#enterDomain(...args); + case SandboxSignal_ExitDomain: + return Domain.#exitDomain(); + case SandboxSignal_ListDomain: + return Domain.#listDomain(); + case SandboxSignal_IsArray: + // @ts-ignore + return Domain.#isArray(...args); + } + } } /** @@ -2884,19 +2898,19 @@ class Domain { * @returns */ function trapMarshal(srcDomain, dstDomain, obj) { - if (srcDomain === dstDomain) - return obj; + if (srcDomain === dstDomain) + return obj; - const domain = Domain.current; + const domain = Domain.current; - // 如果不需要陷入,则直接封送 - if (domain === srcDomain) - return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); + // 如果不需要陷入,则直接封送 + if (domain === srcDomain) + return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); - // 否则先陷入,然后再封送 - return Marshal[SandboxExposer2](SandboxSignal_TrapDomain, srcDomain, () => { - return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); - }); + // 否则先陷入,然后再封送 + return Marshal[SandboxExposer2](SandboxSignal_TrapDomain, srcDomain, () => { + return Marshal[SandboxExposer2](SandboxSignal_Marshal, obj, dstDomain); + }); } /** @@ -2906,687 +2920,687 @@ function trapMarshal(srcDomain, dstDomain, obj) { * ``` */ class Sandbox { - /** @type {WeakMap} */ - static #domainMap = new WeakMap(); - /** @type {Array} */ - static #executingScope = []; + /** @type {WeakMap} */ + static #domainMap = new WeakMap(); + /** @type {Array} */ + static #executingScope = []; - /** @type {Object} */ - #scope; - /** @type {Array} */ - #scopeStack = []; + /** @type {Object} */ + #scope; + /** @type {Array} */ + #scopeStack = []; - /** @type {Domain} */ - #sourceDomain; - /** @type {Domain} */ - #domain; - /** @type {Window} */ - #domainWindow; - /** @type {Document?} */ - #domainDocument; - /** @type {typeof Object} */ - #domainObject = Object; - /** @type {typeof Function} */ - #domainFunction = Function; + /** @type {Domain} */ + #sourceDomain; + /** @type {Domain} */ + #domain; + /** @type {Window} */ + #domainWindow; + /** @type {Document?} */ + #domainDocument; + /** @type {typeof Object} */ + #domainObject = Object; + /** @type {typeof Function} */ + #domainFunction = Function; - /** - * ```plain - * 当在当前scope中访问不到变量时, - * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 - * 去读取部分非内建的全局变量(仅读取) - * - * 此开关有风险,请谨慎使用 - * ``` - * - * @type {boolean} - */ - #freeAccess = false; + /** + * ```plain + * 当在当前scope中访问不到变量时, + * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 + * 去读取部分非内建的全局变量(仅读取) + * + * 此开关有风险,请谨慎使用 + * ``` + * + * @type {boolean} + */ + #freeAccess = false; - /** - * 创建一个新的沙盒 - */ - constructor() { - this.#sourceDomain = Domain.current; - this.#domain = new Domain(); - this.#domainWindow = this.#domain[SandboxExposer](SandboxSignal_GetWindow); - this.#domainDocument = null; // 默认不开放DOM,而且我们也缺少BrowserContext - this.#domainObject = this.#domainWindow.Object; - this.#domainFunction = this.#domainWindow.Function; - Sandbox.#domainMap.set(this.#domain, this); - Sandbox.#createScope(this); - Sandbox.#initDomainFunctions(this, this.#domainWindow); - } + /** + * 创建一个新的沙盒 + */ + constructor() { + this.#sourceDomain = Domain.current; + this.#domain = new Domain(); + this.#domainWindow = this.#domain[SandboxExposer](SandboxSignal_GetWindow); + this.#domainDocument = null; // 默认不开放DOM,而且我们也缺少BrowserContext + this.#domainObject = this.#domainWindow.Object; + this.#domainFunction = this.#domainWindow.Function; + Sandbox.#domainMap.set(this.#domain, this); + Sandbox.#createScope(this); + Sandbox.#initDomainFunctions(this, this.#domainWindow); + } - /** - * ```plain - * 检查沙盒操作运行域 - * ``` - * - * @param {Sandbox} thiz - */ - static #assertOperator = function (thiz) { - if (thiz.#sourceDomain !== Domain.current) - throw new TypeError("当前运行域不是沙盒的所有运行域"); - } + /** + * ```plain + * 检查沙盒操作运行域 + * ``` + * + * @param {Sandbox} thiz + */ + static #assertOperator = function (thiz) { + if (thiz.#sourceDomain !== Domain.current) + throw new TypeError("当前运行域不是沙盒的所有运行域"); + } - /** - * ```plain - * 封装沙盒的 Function 函数 - * ``` - * - * @param {Sandbox} thiz - * @param {Window} global - */ - static #initDomainFunctions = function (thiz, global) { - /** @type {typeof Function} */ - const defaultFunction = global.Function; - /** @type {typeof Function} */ - const defaultGeneratorFunction = global.eval("(function*(){}).constructor"); - /** @type {typeof Function} */ - const defaultAsyncFunction = global.eval("(async function(){}).constructor"); - /** @type {typeof Function} */ - const defaultAsyncGeneratorFunction = global.eval("(async function*(){}).constructor"); + /** + * ```plain + * 封装沙盒的 Function 函数 + * ``` + * + * @param {Sandbox} thiz + * @param {Window} global + */ + static #initDomainFunctions = function (thiz, global) { + /** @type {typeof Function} */ + const defaultFunction = global.Function; + /** @type {typeof Function} */ + const defaultGeneratorFunction = global.eval("(function*(){}).constructor"); + /** @type {typeof Function} */ + const defaultAsyncFunction = global.eval("(async function(){}).constructor"); + /** @type {typeof Function} */ + const defaultAsyncGeneratorFunction = global.eval("(async function*(){}).constructor"); - /** - * @param {typeof Function} target - * @param {Array} argArray - * @returns - */ - function functionCtor(target, argArray) { - if (!argArray.length) - return new target(); + /** + * @param {typeof Function} target + * @param {Array} argArray + * @returns + */ + function functionCtor(target, argArray) { + if (!argArray.length) + return new target(); - argArray = Array.from(argArray); + argArray = Array.from(argArray); - const code = argArray.slice(-1)[0]; - const params = argArray.slice(0, -1); - new target(code); // 防止注入 + const code = argArray.slice(-1)[0]; + const params = argArray.slice(0, -1); + new target(code); // 防止注入 - const compiled = Sandbox.#compileCore(thiz, code, null, params, true); - compiled[Symbol.toStringTag] = `function (${params.join(", ")}) {\n${code}\n}`; - return compiled; - } + const compiled = Sandbox.#compileCore(thiz, code, null, params, true); + compiled[Symbol.toStringTag] = `function (${params.join(", ")}) {\n${code}\n}`; + return compiled; + } - const handler = { - apply(target, thisArg, argArray) { - return functionCtor(target, argArray); - }, - construct(target, argArray, newTarget) { - return functionCtor(target, argArray); - }, - }; + const handler = { + apply(target, thisArg, argArray) { + return functionCtor(target, argArray); + }, + construct(target, argArray, newTarget) { + return functionCtor(target, argArray); + }, + }; - function rewriteCtor(prototype, newCtor) { - const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') - || { configurable: true, writable: true, enumerable: false }; - if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); - descriptor.value = newCtor; - Reflect.defineProperty(prototype, 'constructor', descriptor) - } + function rewriteCtor(prototype, newCtor) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') + || { configurable: true, writable: true, enumerable: false }; + if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); + descriptor.value = newCtor; + Reflect.defineProperty(prototype, 'constructor', descriptor) + } - // 封装当前运行域所有Function类型的构造函数 - // 确保沙盒代码无法访问真正的 Window 对象 - // (不过理论上说访问了也基本上没什么东西喵) - rewriteCtor(defaultFunction.prototype, global.Function = new Proxy(defaultFunction, handler)); - rewriteCtor(defaultGeneratorFunction.prototype, new Proxy(defaultGeneratorFunction, handler)); - rewriteCtor(defaultAsyncFunction.prototype, new Proxy(defaultAsyncFunction, handler)); - rewriteCtor(defaultAsyncGeneratorFunction.prototype, new Proxy(defaultAsyncGeneratorFunction, handler)); - } + // 封装当前运行域所有Function类型的构造函数 + // 确保沙盒代码无法访问真正的 Window 对象 + // (不过理论上说访问了也基本上没什么东西喵) + rewriteCtor(defaultFunction.prototype, global.Function = new Proxy(defaultFunction, handler)); + rewriteCtor(defaultGeneratorFunction.prototype, new Proxy(defaultGeneratorFunction, handler)); + rewriteCtor(defaultAsyncFunction.prototype, new Proxy(defaultAsyncFunction, handler)); + rewriteCtor(defaultAsyncGeneratorFunction.prototype, new Proxy(defaultAsyncGeneratorFunction, handler)); + } - /** - * ```plain - * 获取当前的scope - * ``` - * - * @type {Object} - */ - get scope() { - Sandbox.#assertOperator(this); - return trapMarshal(this.#domain, Domain.current, this.#scope); - } + /** + * ```plain + * 获取当前的scope + * ``` + * + * @type {Object} + */ + get scope() { + Sandbox.#assertOperator(this); + return trapMarshal(this.#domain, Domain.current, this.#scope); + } - /** - * ```plain - * 获取当前沙盒内的运行域 - * ``` - * - * @type {Domain} - */ - get domain() { - return this.#domain; - } + /** + * ```plain + * 获取当前沙盒内的运行域 + * ``` + * + * @type {Domain} + */ + get domain() { + return this.#domain; + } - /** - * ```plain - * 获取当前沙盒内的document对象 - * ``` - * - * @type {Document} - */ - get document() { - Sandbox.#assertOperator(this); - return trapMarshal(this.#domain, Domain.current, this.#domainDocument); - } + /** + * ```plain + * 获取当前沙盒内的document对象 + * ``` + * + * @type {Document} + */ + get document() { + Sandbox.#assertOperator(this); + return trapMarshal(this.#domain, Domain.current, this.#domainDocument); + } - /** - * ```plain - * 设置当前沙盒内的document对象 - * ``` - * - * @type {Document} - */ - set document(value) { - Sandbox.#assertOperator(this); - this.#domainDocument = Marshal[SandboxExposer2] - (SandboxSignal_Marshal, value, this.#domain); - } + /** + * ```plain + * 设置当前沙盒内的document对象 + * ``` + * + * @type {Document} + */ + set document(value) { + Sandbox.#assertOperator(this); + this.#domainDocument = Marshal[SandboxExposer2] + (SandboxSignal_Marshal, value, this.#domain); + } - /** - * ```plain - * 当在当前scope中访问不到变量时, - * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 - * 去读取部分非内建的全局变量(仅读取) - * - * 此开关有风险,请谨慎使用 - * ``` - * - * @type {boolean} - */ - get freeAccess() { - Sandbox.#assertOperator(this); - return this.#freeAccess; - } + /** + * ```plain + * 当在当前scope中访问不到变量时, + * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 + * 去读取部分非内建的全局变量(仅读取) + * + * 此开关有风险,请谨慎使用 + * ``` + * + * @type {boolean} + */ + get freeAccess() { + Sandbox.#assertOperator(this); + return this.#freeAccess; + } - /** - * ```plain - * 当在当前scope中访问不到变量时, - * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 - * 去读取部分非内建的全局变量(仅读取) - * - * 此开关有风险,请谨慎使用 - * ``` - * - * @type {boolean} - */ - set freeAccess(value) { - Sandbox.#assertOperator(this); - this.#freeAccess = !!value; - } + /** + * ```plain + * 当在当前scope中访问不到变量时, + * 是否允许沙盒代码可以穿透到顶级域的全局变量域中 + * 去读取部分非内建的全局变量(仅读取) + * + * 此开关有风险,请谨慎使用 + * ``` + * + * @type {boolean} + */ + set freeAccess(value) { + Sandbox.#assertOperator(this); + this.#freeAccess = !!value; + } - /** - * ```plain - * 向当前域注入内建对象 - * - * 如果使用在使用了 `initBuiltins` 之后进行 `pushScope`, - * 则将自动继承前面的内建对象,无需再次调用 `initBuiltins` - * ``` - */ - initBuiltins() { - Sandbox.#assertOperator(this); + /** + * ```plain + * 向当前域注入内建对象 + * + * 如果使用在使用了 `initBuiltins` 之后进行 `pushScope`, + * 则将自动继承前面的内建对象,无需再次调用 `initBuiltins` + * ``` + */ + initBuiltins() { + Sandbox.#assertOperator(this); - /** - * ```plain - * 如果要扩充沙盒的内建函数或类,请在此增加喵 - * ``` - */ - const builtins = { - Object: this.#domainObject, - Function: this.#domainFunction, - Array: this.#domainWindow.Array, - Math: this.#domainWindow.Math, - Date: this.#domainWindow.Date, - String: this.#domainWindow.String, - Number: this.#domainWindow.Number, - Boolean: this.#domainWindow.Boolean, - RegExp: this.#domainWindow.RegExp, - Error: this.#domainWindow.Error, - TypeError: this.#domainWindow.TypeError, - RangeError: this.#domainWindow.RangeError, - SyntaxError: this.#domainWindow.RangeError, - EvalError: this.#domainWindow.EvalError, - ReferenceError: this.#domainWindow.ReferenceError, - Promise: this.#domainWindow.Promise, - Map: this.#domainWindow.Map, - Set: this.#domainWindow.Set, - WeakMap: this.#domainWindow.WeakMap, - WeakSet: this.#domainWindow.WeakSet, - WeakRef: this.#domainWindow.WeakRef, - Symbol: this.#domainWindow.Symbol, - Proxy: this.#domainWindow.Proxy, - Reflect: this.#domainWindow.Reflect, - BigInt: this.#domainWindow.BigInt, - JSON: this.#domainWindow.JSON, - eval: this.#domainWindow.eval, - setTimeout: this.#domainWindow.setTimeout, - clearTimeout: this.#domainWindow.clearTimeout, - setInterval: this.#domainWindow.setInterval, - clearInterval: this.#domainWindow.clearInterval, - setImmediate: this.#domainWindow.setImmediate, - clearImmediate: this.#domainWindow.clearImmediate, - requestAnimationFrame: this.#domainWindow.requestAnimationFrame, - cancelAnimationFrame: this.#domainWindow.cancelAnimationFrame, - requestIdleCallback: this.#domainWindow.requestIdleCallback, - cancelIdleCallback: this.#domainWindow.cancelIdleCallback, - queueMicrotask: this.#domainWindow.queueMicrotask, - MutationObserver: this.#domainWindow.MutationObserver, - alert: this.#domainWindow.alert, - confirm: this.#domainWindow.confirm, - console: this.#domainWindow.console, - parseInt: this.#domainWindow.parseInt, - parseFloat: this.#domainWindow.parseFloat, - isFinite: this.#domainWindow.isFinite, - isNaN: this.#domainWindow.isNaN, - }; + /** + * ```plain + * 如果要扩充沙盒的内建函数或类,请在此增加喵 + * ``` + */ + const builtins = { + Object: this.#domainObject, + Function: this.#domainFunction, + Array: this.#domainWindow.Array, + Math: this.#domainWindow.Math, + Date: this.#domainWindow.Date, + String: this.#domainWindow.String, + Number: this.#domainWindow.Number, + Boolean: this.#domainWindow.Boolean, + RegExp: this.#domainWindow.RegExp, + Error: this.#domainWindow.Error, + TypeError: this.#domainWindow.TypeError, + RangeError: this.#domainWindow.RangeError, + SyntaxError: this.#domainWindow.RangeError, + EvalError: this.#domainWindow.EvalError, + ReferenceError: this.#domainWindow.ReferenceError, + Promise: this.#domainWindow.Promise, + Map: this.#domainWindow.Map, + Set: this.#domainWindow.Set, + WeakMap: this.#domainWindow.WeakMap, + WeakSet: this.#domainWindow.WeakSet, + WeakRef: this.#domainWindow.WeakRef, + Symbol: this.#domainWindow.Symbol, + Proxy: this.#domainWindow.Proxy, + Reflect: this.#domainWindow.Reflect, + BigInt: this.#domainWindow.BigInt, + JSON: this.#domainWindow.JSON, + eval: this.#domainWindow.eval, + setTimeout: this.#domainWindow.setTimeout, + clearTimeout: this.#domainWindow.clearTimeout, + setInterval: this.#domainWindow.setInterval, + clearInterval: this.#domainWindow.clearInterval, + setImmediate: this.#domainWindow.setImmediate, + clearImmediate: this.#domainWindow.clearImmediate, + requestAnimationFrame: this.#domainWindow.requestAnimationFrame, + cancelAnimationFrame: this.#domainWindow.cancelAnimationFrame, + requestIdleCallback: this.#domainWindow.requestIdleCallback, + cancelIdleCallback: this.#domainWindow.cancelIdleCallback, + queueMicrotask: this.#domainWindow.queueMicrotask, + MutationObserver: this.#domainWindow.MutationObserver, + alert: this.#domainWindow.alert, + confirm: this.#domainWindow.confirm, + console: this.#domainWindow.console, + parseInt: this.#domainWindow.parseInt, + parseFloat: this.#domainWindow.parseFloat, + isFinite: this.#domainWindow.isFinite, + isNaN: this.#domainWindow.isNaN, + }; - const hardBuiltins = { - NaN: NaN, - Infinity: Infinity, - undefined: undefined, - }; + const hardBuiltins = { + NaN: NaN, + Infinity: Infinity, + undefined: undefined, + }; - // 放置内建函数或类 - Marshal[SandboxExposer2](SandboxSignal_TrapDomain, this.#domain, () => { - for (const [k, v] of Object.entries(builtins)) { - if (!v) - delete builtins[k]; + // 放置内建函数或类 + Marshal[SandboxExposer2](SandboxSignal_TrapDomain, this.#domain, () => { + for (const [k, v] of Object.entries(builtins)) { + if (!v) + delete builtins[k]; - // 非类的函数应该要绑定 this 为 null - if (typeof v == "function" && !("prototype" in v)) - builtins[k] = v.bind(null); - } - }); + // 非类的函数应该要绑定 this 为 null + if (typeof v == "function" && !("prototype" in v)) + builtins[k] = v.bind(null); + } + }); - Object.assign(this.#scope, builtins); + Object.assign(this.#scope, builtins); - // 对于常量我们需要重定义 - for (const [k, v] of Object.entries(hardBuiltins)) { - Reflect.defineProperty(this.#scope, k, { - value: v, - writable: false, - enumerable: false, - configurable: false, - }); - } - } + // 对于常量我们需要重定义 + for (const [k, v] of Object.entries(hardBuiltins)) { + Reflect.defineProperty(this.#scope, k, { + value: v, + writable: false, + enumerable: false, + configurable: false, + }); + } + } - /** - * ```plain - * 基于当前的scope克隆一个新的scope - * 然后将原本的scope压入栈中 - * ``` - */ - pushScope() { - Sandbox.#assertOperator(this); - this.#scopeStack.push(this.#scope); - Sandbox.#createScope(this); - } + /** + * ```plain + * 基于当前的scope克隆一个新的scope + * 然后将原本的scope压入栈中 + * ``` + */ + pushScope() { + Sandbox.#assertOperator(this); + this.#scopeStack.push(this.#scope); + Sandbox.#createScope(this); + } - /** - * ```plain - * 丢弃当前的scope并从栈中弹出原本的scope - * ``` - */ - popScope() { - Sandbox.#assertOperator(this); + /** + * ```plain + * 丢弃当前的scope并从栈中弹出原本的scope + * ``` + */ + popScope() { + Sandbox.#assertOperator(this); - if (!this.#scopeStack) - throw new ReferenceError("没有更多的scope可以弹出"); + if (!this.#scopeStack) + throw new ReferenceError("没有更多的scope可以弹出"); - this.#scope = this.#scopeStack.pop(); - } + this.#scope = this.#scopeStack.pop(); + } - /** - * ```plain - * 核心编译函数 - * ``` - * - * @param {Sandbox} thiz 当前沙盒实例 - * @param {string} code 代码字符串 - * @param {Object?} context 额外的执行上下文 - * @param {Array?} paramList 参数名列表,以此来创建可以传递参数的函数 - * @param {boolean?} inheritScope 是否继承当前正在执行的scope而不是当前沙盒的scope - * @param {"exists"|"extend"|"all"} writeContext 当执行的代码尝试为未声明的变量赋值时,应该 根据context与window的变量写入(默认行为)|默认行为并且新的变量写入context|全部写入context - * @returns - */ - static #compileCore = function (thiz, code, context = null, - paramList = null, inheritScope = false, writeContext = 'exists') { - if (typeof code != "string") - throw new TypeError("代码需要是一个字符串"); + /** + * ```plain + * 核心编译函数 + * ``` + * + * @param {Sandbox} thiz 当前沙盒实例 + * @param {string} code 代码字符串 + * @param {Object?} context 额外的执行上下文 + * @param {Array?} paramList 参数名列表,以此来创建可以传递参数的函数 + * @param {boolean?} inheritScope 是否继承当前正在执行的scope而不是当前沙盒的scope + * @param {"exists"|"extend"|"all"} writeContext 当执行的代码尝试为未声明的变量赋值时,应该 根据context与window的变量写入(默认行为)|默认行为并且新的变量写入context|全部写入context + * @returns + */ + static #compileCore = function (thiz, code, context = null, + paramList = null, inheritScope = false, writeContext = 'exists') { + if (typeof code != "string") + throw new TypeError("代码需要是一个字符串"); - if (isPrimitive(context)) - context = {}; + if (isPrimitive(context)) + context = {}; - // 进行语法检查,防止注入 - new thiz.#domainFunction(code); + // 进行语法检查,防止注入 + new thiz.#domainFunction(code); - const executingScope = Sandbox.#executingScope[Sandbox.#executingScope.length - 1]; - const scope = inheritScope && executingScope || thiz.#scope; - const contextName = Sandbox.#makeName("_", scope); - const argsName = Sandbox.#makeName("_", scope); - const applyName = Sandbox.#makeName("_", scope); - const parameters = paramList - ? paramList.join(", ") : ""; - const writeContextAction = { exists: 0, extend: 1, all: 2 }[writeContext] || 0; + const executingScope = Sandbox.#executingScope[Sandbox.#executingScope.length - 1]; + const scope = inheritScope && executingScope || thiz.#scope; + const contextName = Sandbox.#makeName("_", scope); + const argsName = Sandbox.#makeName("_", scope); + const applyName = Sandbox.#makeName("_", scope); + const parameters = paramList + ? paramList.join(", ") : ""; + const writeContextAction = { exists: 0, extend: 1, all: 2 }[writeContext] || 0; - let argumentList; + let argumentList; - const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(()=>{"use strict";return(${applyName}(function(${parameters}){\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n},${contextName}.this,${argsName}))})()}}}`); + const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(()=>{"use strict";return(${applyName}(function(${parameters}){\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n},${contextName}.this,${argsName}))})()}}}`); - const domain = thiz.#domain; - const domainWindow = thiz.#domainWindow; - const marshalledContext = Marshal[SandboxExposer2] - (SandboxSignal_Marshal, context, domain); + const domain = thiz.#domain; + const domainWindow = thiz.#domainWindow; + const marshalledContext = Marshal[SandboxExposer2] + (SandboxSignal_Marshal, context, domain); - // 构建上下文拦截器 - const intercepter = new Proxy(scope, { - has() { - return true; - }, - get(target, p) { - switch (p) { - case contextName: - return marshalledContext; - case argsName: - return argumentList; - case applyName: - return Reflect.apply; - } + // 构建上下文拦截器 + const intercepter = new Proxy(scope, { + has() { + return true; + }, + get(target, p) { + switch (p) { + case contextName: + return marshalledContext; + case argsName: + return argumentList; + case applyName: + return Reflect.apply; + } - // 防止逃逸 - if (p === Symbol.unscopables) - return undefined; + // 防止逃逸 + if (p === Symbol.unscopables) + return undefined; - if (!(p in target)) { - // 暴露非内建的顶级全局变量 - if (thiz.#freeAccess - && !Globals.isBuiltinKey(p)) { - const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); + if (!(p in target)) { + // 暴露非内建的顶级全局变量 + if (thiz.#freeAccess + && !Globals.isBuiltinKey(p)) { + const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); - if (p in topWindow) - return trapMarshal(Domain.topDomain, domain, topWindow[p]); - } + if (p in topWindow) + return trapMarshal(Domain.topDomain, domain, topWindow[p]); + } - throw new domainWindow.ReferenceError(`${String(p)} is not defined`); - } + throw new domainWindow.ReferenceError(`${String(p)} is not defined`); + } - return target[p]; - }, - set(target, p, v) { - if (writeContextAction == 2 - || (writeContextAction == 1 && !(p in target))) - return Reflect.set(marshalledContext, p, v); + return target[p]; + }, + set(target, p, v) { + if (writeContextAction == 2 + || (writeContextAction == 1 && !(p in target))) + return Reflect.set(marshalledContext, p, v); - return Reflect.set(target, p, v); - }, - }); + return Reflect.set(target, p, v); + }, + }); - // 构建陷入的沙盒闭包 - // 同时对返回值进行封送 - return ((...args) => { - const prevDomain = Domain.current; - const domainAction = () => { - // 指定执行域 - // 方便后续新的函数来继承 - Sandbox.#executingScope.push(scope); + // 构建陷入的沙盒闭包 + // 同时对返回值进行封送 + return ((...args) => { + const prevDomain = Domain.current; + const domainAction = () => { + // 指定执行域 + // 方便后续新的函数来继承 + Sandbox.#executingScope.push(scope); - try { - argumentList = Marshal[SandboxExposer2] - (SandboxSignal_MarshalArray, args, domain); - const result = raw.call(null, intercepter); - return Marshal[SandboxExposer2] - (SandboxSignal_Marshal, result, prevDomain); - } finally { - Sandbox.#executingScope.pop(); - } - }; + try { + argumentList = Marshal[SandboxExposer2] + (SandboxSignal_MarshalArray, args, domain); + const result = raw.call(null, intercepter); + return Marshal[SandboxExposer2] + (SandboxSignal_Marshal, result, prevDomain); + } finally { + Sandbox.#executingScope.pop(); + } + }; - if (prevDomain === domain) - return domainAction(); + if (prevDomain === domain) + return domainAction(); - return Marshal[SandboxExposer2] - (SandboxSignal_TrapDomain, domain, domainAction); - }).bind(null); // 编译函数不应该发送 - } + return Marshal[SandboxExposer2] + (SandboxSignal_TrapDomain, domain, domainAction); + }).bind(null); // 编译函数不应该发送 + } - /** - * ```plain - * 基于给定的代码与当前的scope来构造一个闭包函数 - * - * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope - * 另外可以通过context.this属性来指定函数的this - * - * 请注意,当沙盒闭包函数构造后,scope将被闭包固定 - * 这意味着pushScope与popScope不会影响到构造好的函数 - * ``` - * - * @param {string} code 沙盒闭包函数的代码 - * @param {Object?} context 临时上下文 - * @returns {(...args: any[]) => any} 构造的沙盒闭包函数 - */ - compile(code, context = null) { - return Sandbox.#compileCore(this, code, context); - } + /** + * ```plain + * 基于给定的代码与当前的scope来构造一个闭包函数 + * + * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope + * 另外可以通过context.this属性来指定函数的this + * + * 请注意,当沙盒闭包函数构造后,scope将被闭包固定 + * 这意味着pushScope与popScope不会影响到构造好的函数 + * ``` + * + * @param {string} code 沙盒闭包函数的代码 + * @param {Object?} context 临时上下文 + * @returns {(...args: any[]) => any} 构造的沙盒闭包函数 + */ + compile(code, context = null) { + return Sandbox.#compileCore(this, code, context); + } - /** - * ```plain - * 基于当前的scope在沙盒环境下执行给定的代码 - * - * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope - * 另外可以通过context.this属性来指定函数的this - * ``` - * - * @param {string} code 沙盒闭包函数的代码 - * @param {Object?} context 临时上下文 - * @returns 执行代码的返回值 - */ - exec(code, context = null) { - return this.compile(code, context)(); - } + /** + * ```plain + * 基于当前的scope在沙盒环境下执行给定的代码 + * + * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope + * 另外可以通过context.this属性来指定函数的this + * ``` + * + * @param {string} code 沙盒闭包函数的代码 + * @param {Object?} context 临时上下文 + * @returns 执行代码的返回值 + */ + exec(code, context = null) { + return this.compile(code, context)(); + } - /** - * ```plain - * 基于当前的scope在沙盒环境下执行给定的代码 - * - * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope - * 另外可以通过context.this属性来指定函数的this - * - * 与exec的区别在于,此函数可以指定未定义变量赋值行为 - * 当 `readonly` 为false时,不存在的全局变量的赋值行为将被转移到context里面 - * 当 `readonly` 为true(默认)时,任何全局变量的赋值行为将被转移到context里面 - * ``` - * - * @param {string} code 沙盒闭包函数的代码 - * @param {Object?} context 临时上下文(没有给出将自动创建) - * @param {boolean} readonly 是否拦截所有全局变量的赋值 - * @returns {[any, Object]} [执行代码的返回值, 参数context] - */ - exec2(code, context = null, readonly = true) { - if (isPrimitive(context)) - context = {}; + /** + * ```plain + * 基于当前的scope在沙盒环境下执行给定的代码 + * + * 参数context指定临时上下文,类似与scope但是里面的变量优先级高于scope + * 另外可以通过context.this属性来指定函数的this + * + * 与exec的区别在于,此函数可以指定未定义变量赋值行为 + * 当 `readonly` 为false时,不存在的全局变量的赋值行为将被转移到context里面 + * 当 `readonly` 为true(默认)时,任何全局变量的赋值行为将被转移到context里面 + * ``` + * + * @param {string} code 沙盒闭包函数的代码 + * @param {Object?} context 临时上下文(没有给出将自动创建) + * @param {boolean} readonly 是否拦截所有全局变量的赋值 + * @returns {[any, Object]} [执行代码的返回值, 参数context] + */ + exec2(code, context = null, readonly = true) { + if (isPrimitive(context)) + context = {}; - const compiled = Sandbox.#compileCore(this, code, context, - null, false, readonly ? "all" : "extend"); - return [compiled(), context]; - } + const compiled = Sandbox.#compileCore(this, code, context, + null, false, readonly ? "all" : "extend"); + return [compiled(), context]; + } - /** - * ```plain - * 根据运行域获取沙盒对象 - * ``` - * - * @param {Domain} domain - * @returns {Sandbox?} - */ - static from(domain) { - const sandbox = Sandbox.#domainMap.get(domain); + /** + * ```plain + * 根据运行域获取沙盒对象 + * ``` + * + * @param {Domain} domain + * @returns {Sandbox?} + */ + static from(domain) { + const sandbox = Sandbox.#domainMap.get(domain); - if (!sandbox) - return null; + if (!sandbox) + return null; - if (sandbox.#sourceDomain !== Domain.current) - throw new TypeError("当前运行域不是沙盒的所有运行域"); + if (sandbox.#sourceDomain !== Domain.current) + throw new TypeError("当前运行域不是沙盒的所有运行域"); - return sandbox; - } + return sandbox; + } - static #createScope = function (thiz) { - let baseScope = thiz.#scope; - thiz.#scope = new thiz.#domainObject(); + static #createScope = function (thiz) { + let baseScope = thiz.#scope; + thiz.#scope = new thiz.#domainObject(); - // 定义两个超级变量 - Reflect.defineProperty(thiz.#scope, "window", { - get: (function () { - // @ts-ignore - return this; - }).bind(thiz.#scope), - enumerable: false, - configurable: false, - }); - Reflect.defineProperty(thiz.#scope, "document", { - get: (function () { - // @ts-ignore - return this.#domainDocument; - }).bind(thiz), - enumerable: false, - configurable: false, - }); + // 定义两个超级变量 + Reflect.defineProperty(thiz.#scope, "window", { + get: (function () { + // @ts-ignore + return this; + }).bind(thiz.#scope), + enumerable: false, + configurable: false, + }); + Reflect.defineProperty(thiz.#scope, "document", { + get: (function () { + // @ts-ignore + return this.#domainDocument; + }).bind(thiz), + enumerable: false, + configurable: false, + }); - if (!baseScope) - return; + if (!baseScope) + return; - // 继承之前的变量域 - const descriptors = Object.getOwnPropertyDescriptors(baseScope); - delete descriptors.window; - delete descriptors.document; - Object.defineProperties(thiz.#scope, descriptors); - } + // 继承之前的变量域 + const descriptors = Object.getOwnPropertyDescriptors(baseScope); + delete descriptors.window; + delete descriptors.document; + Object.defineProperties(thiz.#scope, descriptors); + } - static #makeName = function (prefix, conflict) { - let builtName; + static #makeName = function (prefix, conflict) { + let builtName; - do { - builtName = prefix + Math.random().toString(36).slice(2); - } while (builtName in conflict); + do { + builtName = prefix + Math.random().toString(36).slice(2); + } while (builtName in conflict); - return builtName; - } + return builtName; + } } function sealClass(clazz) { - sealObjectTree(clazz); + sealObjectTree(clazz); - if (typeof clazz == "function") - sealObjectTree(clazz.prototype); - else if (clazz.constructor) - sealObjectTree(clazz.constructor); + if (typeof clazz == "function") + sealObjectTree(clazz.prototype); + else if (clazz.constructor) + sealObjectTree(clazz.constructor); } // FREEZE FROM SOUL! function sealObjectTree(obj) { - // @ts-ignore - sealObject(obj, o => { - if (!Reflect.isExtensible(o)) // 防止1103 - return; - if (o === obj) - return void Object.freeze(o); + // @ts-ignore + sealObject(obj, o => { + if (!Reflect.isExtensible(o)) // 防止1103 + return; + if (o === obj) + return void Object.freeze(o); - sealObjectTree(o); - }); + sealObjectTree(o); + }); } function sealObject(obj, freeze = Object.freeze) { - if (isPrimitive(obj)) - return; + if (isPrimitive(obj)) + return; - const descriptors = Object.getOwnPropertyDescriptors(obj); + const descriptors = Object.getOwnPropertyDescriptors(obj); - freeze(obj); + freeze(obj); - // 防止通过函数属性传值 - for (const [key, descriptor] of Object.entries(descriptors)) { - if (descriptor.get) - freeze(descriptor.get); - if (descriptor.set) - freeze(descriptor.set); - if (!isPrimitive(descriptor.value)) - freeze(descriptor.value); - } + // 防止通过函数属性传值 + for (const [key, descriptor] of Object.entries(descriptors)) { + if (descriptor.get) + freeze(descriptor.get); + if (descriptor.set) + freeze(descriptor.set); + if (!isPrimitive(descriptor.value)) + freeze(descriptor.value); + } } if (SANDBOX_ENABLED) { - // 确保顶级运行域的原型链不暴露 - if (window.top === window) { - ({ - // @ts-ignore - AccessAction, - // @ts-ignore - Rule, - // @ts-ignore - Monitor, - // @ts-ignore - Marshal, - // @ts-ignore - Domain, - // @ts-ignore - Sandbox, - } = SANDBOX_EXPORT); - } else { - // 防止被不信任代码更改 - sealClass(AccessAction); - sealClass(Rule); - sealClass(Globals); - sealClass(DomainMonitors); - sealClass(Monitor); - sealClass(Marshal); - sealClass(Domain); - sealClass(Sandbox); + // 确保顶级运行域的原型链不暴露 + if (window.top === window) { + ({ + // @ts-ignore + AccessAction, + // @ts-ignore + Rule, + // @ts-ignore + Monitor, + // @ts-ignore + Marshal, + // @ts-ignore + Domain, + // @ts-ignore + Sandbox, + } = SANDBOX_EXPORT); + } else { + // 防止被不信任代码更改 + sealClass(AccessAction); + sealClass(Rule); + sealClass(Globals); + sealClass(DomainMonitors); + sealClass(Monitor); + sealClass(Marshal); + sealClass(Domain); + sealClass(Sandbox); - sealClass(Object); - sealClass(Array); - sealClass(Function); - sealClass(Promise); - sealClass(RegExp); - sealClass(String); - sealClass(Number); - sealClass(Boolean); - sealClass(Symbol); - sealClass(Reflect); - sealClass(Proxy); - sealClass(Date); - sealClass(Math); - sealClass(Error); - sealClass(TypeError); - sealClass(ReferenceError); - sealClass(RangeError); - sealClass(EvalError); - sealClass(SyntaxError); + sealClass(Object); + sealClass(Array); + sealClass(Function); + sealClass(Promise); + sealClass(RegExp); + sealClass(String); + sealClass(Number); + sealClass(Boolean); + sealClass(Symbol); + sealClass(Reflect); + sealClass(Proxy); + sealClass(Date); + sealClass(Math); + sealClass(Error); + sealClass(TypeError); + sealClass(ReferenceError); + sealClass(RangeError); + sealClass(EvalError); + sealClass(SyntaxError); - sealClass(function* () { }.constructor); - sealClass(async function () { }.constructor); - sealClass(async function* () { }.constructor); + sealClass(function* () { }.constructor); + sealClass(async function () { }.constructor); + sealClass(async function* () { }.constructor); - // 改为此处初始化,防止多次初始化 - Domain[SandboxExposer2](SandboxSignal_InitDomain); + // 改为此处初始化,防止多次初始化 + Domain[SandboxExposer2](SandboxSignal_InitDomain); - // 向顶级运行域暴露导出 - // @ts-ignore - window.SANDBOX_EXPORT = { - AccessAction, - Rule, - Monitor, - Marshal, - Domain, - Sandbox, - }; - } + // 向顶级运行域暴露导出 + // @ts-ignore + window.SANDBOX_EXPORT = { + AccessAction, + Rule, + Monitor, + Marshal, + Domain, + Sandbox, + }; + } } export { - AccessAction, - Rule, - Monitor, - Marshal, - Domain, - Sandbox, - SANDBOX_ENABLED, + AccessAction, + Rule, + Monitor, + Marshal, + Domain, + Sandbox, + SANDBOX_ENABLED, }; \ No newline at end of file diff --git a/noname/util/security.js b/noname/util/security.js index f4755ff04..590e71080 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -7,7 +7,7 @@ const SANDBOX_AUTOTEST = true; const SANDBOX_AUTOTEST_NODELAY = false; const TRUSTED_IPS = Object.freeze([ - "47.99.105.222", + "47.99.105.222", ]); /** @type {boolean} */ @@ -46,13 +46,13 @@ const isolatedsMap = new WeakMap(); // noname 顶级变量 const topVariables = { - lib: null, - game: null, - ui: null, - get: null, - ai: null, - _status: null, - gnc: null, + lib: null, + game: null, + ui: null, + get: null, + ai: null, + _status: null, + gnc: null, }; const defaultEval = window.eval; @@ -66,8 +66,8 @@ const nativePattern = /^function \w*\(\) \{ \[native code\] \}$/; // 垫片备份 const polyfills = { - prototypes: {}, - namespaces: {}, + prototypes: {}, + namespaces: {}, }; // 被封装的Function类型 @@ -88,13 +88,13 @@ let ModAsyncGeneratorFunction; * @param {Sandbox} box */ function enterSandbox(box) { - if (!SANDBOX_ENABLED) - return; + if (!SANDBOX_ENABLED) + return; - if (!Domain.isBelievable(Domain.topDomain)) - throw "无法在沙盒里面访问"; + if (!Domain.isBelievable(Domain.topDomain)) + throw "无法在沙盒里面访问"; - sandboxStack.push(box); + sandboxStack.push(box); } /** @@ -103,15 +103,15 @@ function enterSandbox(box) { * ``` */ function exitSandbox() { - if (!SANDBOX_ENABLED) - return; + if (!SANDBOX_ENABLED) + return; - if (!Domain.isBelievable(Domain.topDomain)) - throw "无法在沙盒里面访问"; - if (!sandboxStack.length) - return; + if (!Domain.isBelievable(Domain.topDomain)) + throw "无法在沙盒里面访问"; + if (!sandboxStack.length) + return; - sandboxStack.pop(); + sandboxStack.pop(); } /** @@ -123,28 +123,28 @@ function exitSandbox() { * @param {string?} prop 指定要检查的属性描述符 */ function isUnsafeObject(obj, prop = null) { - if (!SANDBOX_ENABLED) - return true; + if (!SANDBOX_ENABLED) + return true; - if (prop != null) { - const descriptor = Object.getOwnPropertyDescriptor(obj, prop); + if (prop != null) { + const descriptor = Object.getOwnPropertyDescriptor(obj, prop); - if (descriptor) { - if (descriptor.get - && isUnsafeObject(descriptor.get)) - return true; - if (descriptor.set - && isUnsafeObject(descriptor.set)) - return true; - if (isUnsafeObject(descriptor.value)) - return true; - } - } + if (descriptor) { + if (descriptor.get + && isUnsafeObject(descriptor.get)) + return true; + if (descriptor.set + && isUnsafeObject(descriptor.set)) + return true; + if (isUnsafeObject(descriptor.value)) + return true; + } + } - if (isPrimitive(obj)) - return false; + if (isPrimitive(obj)) + return false; - return !Domain.topDomain.isFrom(obj); + return !Domain.topDomain.isFrom(obj); } /** @@ -156,15 +156,15 @@ function isUnsafeObject(obj, prop = null) { * @param {string?} prop 指定要检查的属性描述符 */ function assertSafeObject(obj, prop = null) { - if (isUnsafeObject(obj, prop)) - throw "unsafe object denied"; + if (isUnsafeObject(obj, prop)) + throw "unsafe object denied"; } /** * @param {Object?} obj */ function isPrimitive(obj) { - return Object(obj) !== obj; + return Object(obj) !== obj; } /** @@ -175,10 +175,10 @@ function isPrimitive(obj) { * @returns {Sandbox?} */ function currentSandbox() { - if (!SANDBOX_ENABLED) - return null; + if (!SANDBOX_ENABLED) + return null; - return sandboxStack[sandboxStack.length - 1] || defaultSandbox; + return sandboxStack[sandboxStack.length - 1] || defaultSandbox; } /** @@ -187,7 +187,7 @@ function currentSandbox() { * ``` */ function requireSandbox() { - sandBoxRequired = true; + sandBoxRequired = true; } /** @@ -198,8 +198,8 @@ function requireSandbox() { * @param {string} ip */ function requireSandboxOn(ip) { - if (!TRUSTED_IPS.includes(ip)) - sandBoxRequired = true; + if (!TRUSTED_IPS.includes(ip)) + sandBoxRequired = true; } /** @@ -210,7 +210,7 @@ function requireSandboxOn(ip) { * @returns {boolean} */ function isSandboxRequired() { - return SANDBOX_ENABLED && sandBoxRequired; + return SANDBOX_ENABLED && sandBoxRequired; } /** @@ -224,15 +224,15 @@ function isSandboxRequired() { * @returns {any} */ function _eval(x) { - if (!SANDBOX_ENABLED || !sandBoxRequired) { - new Function(x); - const topVars = Object.assign({}, topVariables); - const vars = "_" + Math.random().toString(36).slice(2); - return new Function(vars, `with(${vars}){${x}}`)(topVars); - } + if (!SANDBOX_ENABLED || !sandBoxRequired) { + new Function(x); + const topVars = Object.assign({}, topVariables); + const vars = "_" + Math.random().toString(36).slice(2); + return new Function(vars, `with(${vars}){${x}}`)(topVars); + } - // @ts-ignore - return defaultSandbox.exec(x); + // @ts-ignore + return defaultSandbox.exec(x); } /** @@ -247,20 +247,20 @@ function _eval(x) { * @returns {any} */ function _exec(x, scope = {}) { - if (isPrimitive(scope)) - scope = {}; + if (isPrimitive(scope)) + scope = {}; - if (!SANDBOX_ENABLED || !sandBoxRequired) { - // 如果没有沙盒,则进行简单模拟 - new Function(x); - const topVars = Object.assign({}, topVariables); - const vars = "__vars_" + Math.random().toString(36).slice(2); - const name = "__scope_" + Math.random().toString(36).slice(2); - return new Function(vars, name, `with(${vars}){with(${name}){${x}}}`)(topVars, scope); - } + if (!SANDBOX_ENABLED || !sandBoxRequired) { + // 如果没有沙盒,则进行简单模拟 + new Function(x); + const topVars = Object.assign({}, topVariables); + const vars = "__vars_" + Math.random().toString(36).slice(2); + const name = "__scope_" + Math.random().toString(36).slice(2); + return new Function(vars, name, `with(${vars}){with(${name}){${x}}}`)(topVars, scope); + } - // @ts-ignore - return defaultSandbox.exec(x, scope); + // @ts-ignore + return defaultSandbox.exec(x, scope); } /** @@ -277,44 +277,44 @@ function _exec(x, scope = {}) { * @returns {Object} */ function _exec2(x, scope = {}) { - if (scope == "window") { - scope = {}; - scope.window = scope; - } else if (isPrimitive(scope)) - scope = {}; + if (scope == "window") { + scope = {}; + scope.window = scope; + } else if (isPrimitive(scope)) + scope = {}; - if (!SANDBOX_ENABLED || !sandBoxRequired) { - // 如果没有沙盒,则进行简单模拟 - // 进行语法检查 - new Function(x); + if (!SANDBOX_ENABLED || !sandBoxRequired) { + // 如果没有沙盒,则进行简单模拟 + // 进行语法检查 + new Function(x); - // 构造拦截器 - const intercepter = new Proxy(scope, { - get(target, prop, receiver) { - if (prop === Symbol.unscopables) - return undefined; + // 构造拦截器 + const intercepter = new Proxy(scope, { + get(target, prop, receiver) { + if (prop === Symbol.unscopables) + return undefined; - if (!Reflect.has(target, prop) - && !Reflect.has(window, prop)) - throw new ReferenceError(`"${String(prop)}" is not defined`); + if (!Reflect.has(target, prop) + && !Reflect.has(window, prop)) + throw new ReferenceError(`"${String(prop)}" is not defined`); - return Reflect.get(target, prop, receiver) - || topVariables[prop] || window[prop]; - }, - has(target, prop) { - return true; - }, - }); + return Reflect.get(target, prop, receiver) + || topVariables[prop] || window[prop]; + }, + has(target, prop) { + return true; + }, + }); - const result = new Function("_", `with(_){return(()=>{"use strict";\n${x}})()}`)(intercepter); - scope.return = result; - return scope; - } + const result = new Function("_", `with(_){return(()=>{"use strict";\n${x}})()}`)(intercepter); + scope.return = result; + return scope; + } - // @ts-ignore - const [result] = defaultSandbox.exec2(x, scope); - scope.return = result; - return scope; + // @ts-ignore + const [result] = defaultSandbox.exec2(x, scope); + scope.return = result; + return scope; } /** @@ -323,179 +323,179 @@ function _exec2(x, scope = {}) { * ``` */ async function initSecurity({ - lib, - game, - ui, - get, - ai, - _status, - gnc, + lib, + game, + ui, + get, + ai, + _status, + gnc, }) { - if (initialized) - throw "security 已经被初始化过了"; + if (initialized) + throw "security 已经被初始化过了"; - const sandbox = await import("./sandbox.js"); - SANDBOX_ENABLED = sandbox.SANDBOX_ENABLED; - AccessAction = sandbox.AccessAction; - Domain = sandbox.Domain; - Marshal = sandbox.Marshal; - Monitor = sandbox.Monitor; - Rule = sandbox.Rule; - Sandbox = sandbox.Sandbox; + const sandbox = await import("./sandbox.js"); + SANDBOX_ENABLED = sandbox.SANDBOX_ENABLED; + AccessAction = sandbox.AccessAction; + Domain = sandbox.Domain; + Marshal = sandbox.Marshal; + Monitor = sandbox.Monitor; + Rule = sandbox.Rule; + Sandbox = sandbox.Sandbox; - topVariables.lib = lib; - topVariables.game = game; - topVariables.ui = ui; - topVariables.get = get; - topVariables.ai = ai; - topVariables._status = _status; - topVariables.gnc = gnc; + topVariables.lib = lib; + topVariables.game = game; + topVariables.ui = ui; + topVariables.get = get; + topVariables.ai = ai; + topVariables._status = _status; + topVariables.gnc = gnc; - if (!SANDBOX_ENABLED) - return; + if (!SANDBOX_ENABLED) + return; - loadPolyfills(); - initIsolatedEnvironment(); + loadPolyfills(); + initIsolatedEnvironment(); - // 不允许被远程代码访问的game函数 - const ioFuncs = [ - "download", - "readFile", - "readFileAsText", - "writeFile", - "removeFile", - "getFileList", - "ensureDirectory", - "createDir", - "removeDir", - "checkForUpdate", - "checkForAssetUpdate", - "importExtension", - "export", - "multiDownload2", - "multiDownload", - "fetch", - ]; + // 不允许被远程代码访问的game函数 + const ioFuncs = [ + "download", + "readFile", + "readFileAsText", + "writeFile", + "removeFile", + "getFileList", + "ensureDirectory", + "createDir", + "removeDir", + "checkForUpdate", + "checkForAssetUpdate", + "importExtension", + "export", + "multiDownload2", + "multiDownload", + "fetch", + ]; - const accessDenieds = [ - ...ioFuncs.map(n => game[n]).filter(Boolean), - ...Object.values(game.promises), - defaultEval, - window.require, - window.process, - window.module, - window.exports, - window.cordova, - // @ts-ignore - window.NonameAndroidBridge, - // @ts-ignore - window.noname_shijianInterfaces, - window, - ]; + const accessDenieds = [ + ...ioFuncs.map(n => game[n]).filter(Boolean), + ...Object.values(game.promises), + defaultEval, + window.require, + window.process, + window.module, + window.exports, + window.cordova, + // @ts-ignore + window.NonameAndroidBridge, + // @ts-ignore + window.noname_shijianInterfaces, + window, + ]; - // 构造禁止函数调用的规则 - const callRule = new Rule(); - callRule.canMarshal = false; // 禁止获取函数 - callRule.setGranted(AccessAction.CALL, false); // 禁止函数调用 - callRule.setGranted(AccessAction.NEW, false); // 禁止函数new调用 + // 构造禁止函数调用的规则 + const callRule = new Rule(); + callRule.canMarshal = false; // 禁止获取函数 + callRule.setGranted(AccessAction.CALL, false); // 禁止函数调用 + callRule.setGranted(AccessAction.NEW, false); // 禁止函数new调用 - // 为禁止的函数设置规则 - accessDenieds.filter(Boolean).forEach(o => { - Marshal.setRule(o, callRule); - }); + // 为禁止的函数设置规则 + accessDenieds.filter(Boolean).forEach(o => { + Marshal.setRule(o, callRule); + }); - // 构造禁止访问的规则 - const bannedRule = new Rule(); - bannedRule.canMarshal = false; // 禁止获取 - bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性 - bannedRule.setGranted(AccessAction.WRITE, false); // 禁止读取属性 + // 构造禁止访问的规则 + const bannedRule = new Rule(); + bannedRule.canMarshal = false; // 禁止获取 + bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性 + bannedRule.setGranted(AccessAction.WRITE, false); // 禁止读取属性 - // 禁止访问关键对象 - [ - lib.cheat, - lib.node, - lib.message, - ] - .filter(Boolean) - .forEach(o => Marshal.setRule(o, bannedRule)); + // 禁止访问关键对象 + [ + lib.cheat, + lib.node, + lib.message, + ] + .filter(Boolean) + .forEach(o => Marshal.setRule(o, bannedRule)); - // 构造禁止修改的规则 - const writeRule = new Rule(); - writeRule.setGranted(AccessAction.WRITE, false); // 禁止写入属性 - writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性 - // 禁止修改 game.promises 的函数 - Marshal.setRule(game.promises, writeRule); + // 构造禁止修改的规则 + const writeRule = new Rule(); + writeRule.setGranted(AccessAction.WRITE, false); // 禁止写入属性 + writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性 + // 禁止修改 game.promises 的函数 + Marshal.setRule(game.promises, writeRule); - // 对于 game 当中访问特定函数我们通过 Monitor 进行拦截 - new Monitor() - // 如果是写入或重定义属性 - .action(AccessAction.WRITE) - .action(AccessAction.DEFINE) - // 如果目标是 game 的 ioFuncs 包含的所有函数 - .require("target", game) - .require("property", ...ioFuncs) - // 抛出异常 - .then(() => { - throw "禁止修改关键函数"; - }) - // 让 Monitor 开始工作 - .start(); // 差点忘记启动了喵 + // 对于 game 当中访问特定函数我们通过 Monitor 进行拦截 + new Monitor() + // 如果是写入或重定义属性 + .action(AccessAction.WRITE) + .action(AccessAction.DEFINE) + // 如果目标是 game 的 ioFuncs 包含的所有函数 + .require("target", game) + .require("property", ...ioFuncs) + // 抛出异常 + .then(() => { + throw "禁止修改关键函数"; + }) + // 让 Monitor 开始工作 + .start(); // 差点忘记启动了喵 - // 现在 parsex 已经禁止传递字符串,这段 Monitor 不需要了 - // 监听原型、toStringTag的更改 - // const toStringTag = Symbol.toStringTag; - // new Monitor() - // .action(AccessAction.WRITE) - // .action(AccessAction.DEFINE) - // .action(AccessAction.META) - // .require("property", toStringTag) - // .then((access, nameds, control) => { - // // 阻止原型、toStringTag的更改 - // control.preventDefault(); - // control.stopPropagation(); - // control.setReturnValue(false); - // }) - // .start(); + // 现在 parsex 已经禁止传递字符串,这段 Monitor 不需要了 + // 监听原型、toStringTag的更改 + // const toStringTag = Symbol.toStringTag; + // new Monitor() + // .action(AccessAction.WRITE) + // .action(AccessAction.DEFINE) + // .action(AccessAction.META) + // .require("property", toStringTag) + // .then((access, nameds, control) => { + // // 阻止原型、toStringTag的更改 + // control.preventDefault(); + // control.stopPropagation(); + // control.setReturnValue(false); + // }) + // .start(); - if (SANDBOX_AUTOTEST) { - // 一个测试循环喵 - if (SANDBOX_AUTOTEST_NODELAY) { - game.resume = () => { }; - game.pause = () => { }; - } - game.delay = game.delayx = () => { }; - game.asyncDelay = game.asyncDelayx = async () => { }; + if (SANDBOX_AUTOTEST) { + // 一个测试循环喵 + if (SANDBOX_AUTOTEST_NODELAY) { + game.resume = () => { }; + game.pause = () => { }; + } + game.delay = game.delayx = () => { }; + game.asyncDelay = game.asyncDelayx = async () => { }; - Reflect.defineProperty(lib.element.GameEvent.prototype, "animate", { - get: () => undefined, - set() { }, - enumerable: false, - configurable: false, - }); + Reflect.defineProperty(lib.element.GameEvent.prototype, "animate", { + get: () => undefined, + set() { }, + enumerable: false, + configurable: false, + }); - if (!lib.videos) - lib.videos = []; + if (!lib.videos) + lib.videos = []; - game.over = function (...args) { - if (_status.over) return; - _status.over = true; - setTimeout(() => { - if (!_status.auto) - return; + game.over = function (...args) { + if (_status.over) return; + _status.over = true; + setTimeout(() => { + if (!_status.auto) + return; - const count = parseInt(localStorage.getItem("__sandboxTestCount") || "0"); - localStorage.setItem("__sandboxTestCount", String(count + 1)); + const count = parseInt(localStorage.getItem("__sandboxTestCount") || "0"); + localStorage.setItem("__sandboxTestCount", String(count + 1)); - localStorage.setItem( - lib.configprefix + "directstart", "true"); - game.reload(); - }, SANDBOX_AUTOTEST_NODELAY ? 5000 : 1000); - }; + localStorage.setItem( + lib.configprefix + "directstart", "true"); + game.reload(); + }, SANDBOX_AUTOTEST_NODELAY ? 5000 : 1000); + }; - lib.arenaReady.push(() => ui.click.auto()); - } + lib.arenaReady.push(() => ui.click.auto()); + } - initialized = true; + initialized = true; } /** @@ -506,24 +506,24 @@ async function initSecurity({ * @returns {Sandbox?} */ function createSandbox() { - if (!SANDBOX_ENABLED) - return null; + if (!SANDBOX_ENABLED) + return null; - const box = new Sandbox(); - box.freeAccess = true; - box.initBuiltins(); + const box = new Sandbox(); + box.freeAccess = true; + box.initBuiltins(); - // 向沙盒提供顶级运行域的文档对象 - // TODO: 仅提供必要的document函数(?) - box.document = document; + // 向沙盒提供顶级运行域的文档对象 + // TODO: 仅提供必要的document函数(?) + box.document = document; - // 传递七个变量 - Object.assign(box.scope, topVariables); - // 复制垫片函数 - setupPolyfills(box); + // 传递七个变量 + Object.assign(box.scope, topVariables); + // 复制垫片函数 + setupPolyfills(box); - box.pushScope(); - return box; + box.pushScope(); + return box; } /** @@ -535,23 +535,23 @@ function createSandbox() { * @returns {Array} */ function getIsolateds(sandbox) { - let isolateds = isolatedsMap.get(sandbox); + let isolateds = isolatedsMap.get(sandbox); - if (isolateds) - return isolateds.slice(); + if (isolateds) + return isolateds.slice(); - // 获取当前沙盒的Function类型 - isolateds = Array.from(sandbox.exec(` - return [ - (function(){}).constructor, - (function*(){}).constructor, - (async function(){}).constructor, - (async function*(){}).constructor, - ]; - `)); + // 获取当前沙盒的Function类型 + isolateds = Array.from(sandbox.exec(` + return [ + (function(){}).constructor, + (function*(){}).constructor, + (async function(){}).constructor, + (async function*(){}).constructor, + ]; + `)); - isolatedsMap.set(sandbox, isolateds); - return isolateds.slice(); + isolatedsMap.set(sandbox, isolateds); + return isolateds.slice(); } /** @@ -563,24 +563,24 @@ function getIsolateds(sandbox) { * @returns {Array} */ function getIsolatedsFrom(item) { - const domain = Marshal.getMarshalledDomain(item) || Domain.caller; + const domain = Marshal.getMarshalledDomain(item) || Domain.caller; - // 非顶级域调用情况下我们替换掉Function类型 - if (domain && domain !== Domain.topDomain) { - const box = Sandbox.from(domain); + // 非顶级域调用情况下我们替换掉Function类型 + if (domain && domain !== Domain.topDomain) { + const box = Sandbox.from(domain); - if (!box) - throw "意外的运行域: 运行域没有绑定沙盒"; + if (!box) + throw "意外的运行域: 运行域没有绑定沙盒"; - return getIsolateds(box); - } + return getIsolateds(box); + } - return [ - ModFunction, - ModGeneratorFunction, - ModAsyncFunction, - ModAsyncGeneratorFunction, - ]; + return [ + ModFunction, + ModGeneratorFunction, + ModAsyncFunction, + ModAsyncGeneratorFunction, + ]; } /** @@ -589,26 +589,26 @@ function getIsolatedsFrom(item) { * ``` * * @returns {{ - * AccessAction: typeof import("./sandbox.js").AccessAction, - * Domain: typeof import("./sandbox.js").Domain, - * Marshal: typeof import("./sandbox.js").Marshal, - * Monitor: typeof import("./sandbox.js").Monitor, - * Rule: typeof import("./sandbox.js").Rule, - * Sandbox: typeof import("./sandbox.js").Sandbox, + * AccessAction: typeof import("./sandbox.js").AccessAction, + * Domain: typeof import("./sandbox.js").Domain, + * Marshal: typeof import("./sandbox.js").Marshal, + * Monitor: typeof import("./sandbox.js").Monitor, + * Rule: typeof import("./sandbox.js").Rule, + * Sandbox: typeof import("./sandbox.js").Sandbox, * }} */ function importSandbox() { - if (!AccessAction) - throw new ReferenceError("sandbox.js 还没有被载入"); + if (!AccessAction) + throw new ReferenceError("sandbox.js 还没有被载入"); - return { - AccessAction, - Domain, - Marshal, - Monitor, - Rule, - Sandbox, - }; + return { + AccessAction, + Domain, + Marshal, + Monitor, + Rule, + Sandbox, + }; } /** @@ -617,116 +617,116 @@ function importSandbox() { * ``` */ function initIsolatedEnvironment() { - /** @type {typeof Function} */ - // @ts-ignore - const defaultFunction = function () { }.constructor; - /** @type {typeof Function} */ - // @ts-ignore - const defaultGeneratorFunction = function* () { }.constructor; - /** @type {typeof Function} */ - // @ts-ignore - const defaultAsyncFunction = async function () { }.constructor; - /** @type {typeof Function} */ - // @ts-ignore - const defaultAsyncGeneratorFunction = async function* () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultFunction = function () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultGeneratorFunction = function* () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultAsyncFunction = async function () { }.constructor; + /** @type {typeof Function} */ + // @ts-ignore + const defaultAsyncGeneratorFunction = async function* () { }.constructor; - // @ts-ignore - defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵 + // @ts-ignore + defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵 - // @ts-ignore - // 对于 defaultSandbox 我们要补充一些东西喵 - defaultSandbox.scope.localStorage = localStorage; + // @ts-ignore + // 对于 defaultSandbox 我们要补充一些东西喵 + defaultSandbox.scope.localStorage = localStorage; - // 对Function类型进行包裹 - /** @type {Array} */ - const [ - IsolatedFunction, - IsolatedGeneratorFunction, - IsolatedAsyncFunction, - IsolatedAsyncGeneratorFunction, - ] - // @ts-ignore - = getIsolateds(defaultSandbox); + // 对Function类型进行包裹 + /** @type {Array} */ + const [ + IsolatedFunction, + IsolatedGeneratorFunction, + IsolatedAsyncFunction, + IsolatedAsyncGeneratorFunction, + ] + // @ts-ignore + = getIsolateds(defaultSandbox); - // 封装Function类型 + // 封装Function类型 - ModFunction = new Proxy(defaultFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); + ModFunction = new Proxy(defaultFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); + return new IsolatedFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedFunction(...argumentsList); - }, - }); + return new IsolatedFunction(...argumentsList); + }, + }); - /** @type {typeof Function} */ - ModGeneratorFunction = new Proxy(defaultGeneratorFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); + /** @type {typeof Function} */ + ModGeneratorFunction = new Proxy(defaultGeneratorFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedGeneratorFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); + return new IsolatedGeneratorFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedGeneratorFunction(...argumentsList); - }, - }); + return new IsolatedGeneratorFunction(...argumentsList); + }, + }); - /** @type {typeof Function} */ - ModAsyncFunction = new Proxy(defaultAsyncFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); + /** @type {typeof Function} */ + ModAsyncFunction = new Proxy(defaultAsyncFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedAsyncFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); + return new IsolatedAsyncFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedAsyncFunction(...argumentsList); - }, - }); + return new IsolatedAsyncFunction(...argumentsList); + }, + }); - /** @type {typeof Function} */ - ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, { - apply(target, thisArg, argumentsList) { - if (!sandBoxRequired) - return new target(...argumentsList); + /** @type {typeof Function} */ + ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, { + apply(target, thisArg, argumentsList) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedAsyncGeneratorFunction(...argumentsList); - }, - construct(target, argumentsList, newTarget) { - if (!sandBoxRequired) - return new target(...argumentsList); + return new IsolatedAsyncGeneratorFunction(...argumentsList); + }, + construct(target, argumentsList, newTarget) { + if (!sandBoxRequired) + return new target(...argumentsList); - return new IsolatedAsyncGeneratorFunction(...argumentsList); - }, - }); + return new IsolatedAsyncGeneratorFunction(...argumentsList); + }, + }); - function rewriteCtor(prototype, newCtor) { - const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') - || { configurable: true, writable: true, enumerable: false }; - if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); - descriptor.value = newCtor; - Reflect.defineProperty(prototype, 'constructor', descriptor); - } + function rewriteCtor(prototype, newCtor) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') + || { configurable: true, writable: true, enumerable: false }; + if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数"); + descriptor.value = newCtor; + Reflect.defineProperty(prototype, 'constructor', descriptor); + } - // 覆盖所有的Function类型构造函数 - window.Function = ModFunction; - rewriteCtor(defaultFunction.prototype, ModFunction); - rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction); - rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction); - rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction); + // 覆盖所有的Function类型构造函数 + window.Function = ModFunction; + rewriteCtor(defaultFunction.prototype, ModFunction); + rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction); + rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction); + rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction); } /** @@ -735,55 +735,55 @@ function initIsolatedEnvironment() { * ``` */ function loadPolyfills() { - function isNativeDescriptor(descriptor) { - if (typeof descriptor.value == "function" - && !nativePattern.test(descriptor.value.toString())) - return false; - if (typeof descriptor.get == "function" - && !nativePattern.test(descriptor.get.toString())) - return false; - if (typeof descriptor.set == "function" - && !nativePattern.test(descriptor.set.toString())) - return false; + function isNativeDescriptor(descriptor) { + if (typeof descriptor.value == "function" + && !nativePattern.test(descriptor.value.toString())) + return false; + if (typeof descriptor.get == "function" + && !nativePattern.test(descriptor.get.toString())) + return false; + if (typeof descriptor.set == "function" + && !nativePattern.test(descriptor.set.toString())) + return false; - return true; - } + return true; + } - function copyDescriptors(top, box) { - for (const key of Reflect.ownKeys(top)) { - const descriptor = Reflect.getOwnPropertyDescriptor(top, key); + function copyDescriptors(top, box) { + for (const key of Reflect.ownKeys(top)) { + const descriptor = Reflect.getOwnPropertyDescriptor(top, key); - if (!descriptor - || (typeof descriptor.value !== "function" - && !descriptor.get && !descriptor.set)) - continue; + if (!descriptor + || (typeof descriptor.value !== "function" + && !descriptor.get && !descriptor.set)) + continue; - if (isNativeDescriptor(descriptor)) - continue; + if (isNativeDescriptor(descriptor)) + continue; - box[key] = descriptor; - } - } + box[key] = descriptor; + } + } - // 将垫片函数的描述器复制出来 + // 将垫片函数的描述器复制出来 - for (const key of pfPrototypes) { - const top = window[key]; + for (const key of pfPrototypes) { + const top = window[key]; - if (!top || !top.prototype) - continue; + if (!top || !top.prototype) + continue; - copyDescriptors(top.prototype, polyfills.prototypes[key] = {}); - } + copyDescriptors(top.prototype, polyfills.prototypes[key] = {}); + } - for (const key of pfNamespaces) { - const top = window[key]; + for (const key of pfNamespaces) { + const top = window[key]; - if (!top) - continue; + if (!top) + continue; - copyDescriptors(top, polyfills.namespaces[key] = {}); - } + copyDescriptors(top, polyfills.namespaces[key] = {}); + } } /** @@ -794,59 +794,63 @@ function loadPolyfills() { * @param {Sandbox} sandbox */ function setupPolyfills(sandbox) { - const context = { - pfPrototypes, - pfNamespaces, - prototypes: polyfills.prototypes, - namespaces: polyfills.namespaces, - }; + const context = { + pfPrototypes, + pfNamespaces, + prototypes: polyfills.prototypes, + namespaces: polyfills.namespaces, + }; - // 根据之前复制的垫片函数描述器定义垫片函数 - sandbox.exec(` - function definePolyfills(top, box) { - for (const key in top) - Reflect.defineProperty(box, key, top[key]); - } + // 根据之前复制的垫片函数描述器定义垫片函数 + sandbox.exec(` + function definePolyfills(top, box) { + for (const key in top) + Reflect.defineProperty(box, key, top[key]); + } - for (const key of pfPrototypes) { - if (key in prototypes) - definePolyfills( - prototypes[key], - window[key].prototype - ); - } + for (const key of pfPrototypes) { + if (key in prototypes) + definePolyfills( + prototypes[key], + window[key].prototype + ); + } - for (const key of pfNamespaces) { - if (key in namespaces) - definePolyfills( - namespaces[key], - window[key] - ); - } - `, context); + for (const key of pfNamespaces) { + if (key in namespaces) + definePolyfills( + namespaces[key], + window[key] + ); + } + `, context); } // 测试暴露喵 -// window.sandbox = defaultSandbox; +// Reflect.defineProperty(window, "sandbox", { +// get: () => defaultSandbox, +// set: () => { }, +// configurable: true, +// }); const exports = { - enterSandbox, - exitSandbox, - currentSandbox, - createSandbox, - isUnsafeObject, - assertSafeObject, - getIsolateds, - getIsolatedsFrom, - importSandbox, - requireSandbox, - requireSandboxOn, - isSandboxRequired, - initSecurity, - eval: _eval, - exec: _exec, - exec2: _exec2, - SANDBOX_ENABLED, + enterSandbox, + exitSandbox, + currentSandbox, + createSandbox, + isUnsafeObject, + assertSafeObject, + getIsolateds, + getIsolatedsFrom, + importSandbox, + requireSandbox, + requireSandboxOn, + isSandboxRequired, + initSecurity, + eval: _eval, + exec: _exec, + exec2: _exec2, + SANDBOX_ENABLED, }; Object.freeze(exports); From 7c0fa0b02ea28b84d87c3d71fed61c4293406258 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Mon, 27 May 2024 14:16:18 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=81=94=E6=9C=BAbug?= =?UTF-8?q?=EF=BC=9B=E5=8E=BB=E9=99=A4=E9=83=A8=E5=88=86debugger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mode/connect.js | 2 +- noname/game/index.js | 2 +- noname/get/index.js | 6 +- noname/init/index.js | 18 +-- noname/library/element/client.js | 6 +- noname/library/index.js | 56 ++++---- noname/library/init/index.js | 3 - noname/ui/create/menu/pages/exetensionMenu.js | 10 +- noname/util/initRealms.js | 4 - noname/util/sandbox.js | 28 ++-- noname/util/security.js | 130 ++++++++++++++---- 11 files changed, 163 insertions(+), 102 deletions(-) diff --git a/mode/connect.js b/mode/connect.js index a8bdad541..31272f31c 100644 --- a/mode/connect.js +++ b/mode/connect.js @@ -57,7 +57,7 @@ game.import("mode", function (lib, game, ui, get, ai, _status) { game.saveConfig("last_ip", ip); game.connect(ip, function (success) { if (success) { - game.requireSandboxOn(ip); // 启用沙盒喵 + game.requireSandboxOn(ip); var info = lib.config.reconnect_info; if (info && info[0] == _status.ip) { game.onlineID = info[1]; diff --git a/noname/game/index.js b/noname/game/index.js index 5929dc795..870e1e2a3 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1346,7 +1346,7 @@ export class Game { * @param {*} message */ sendTo(id, message) { - return new lib.element.Client(new lib.element.NodeWS(id)).send(message); + return new lib.element.Client(new lib.element.NodeWS(id), true).send(message); } createServer() { lib.node.clients = []; diff --git a/noname/get/index.js b/noname/get/index.js index f461cad68..422765f4a 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -1521,7 +1521,9 @@ export class Get { str = str.trim(); const arrowMatch = get.#arrowPattern.exec(str); if (arrowMatch) { - const body = `return ${str.slice(arrowMatch[0].length)}`; + let body = str.slice(arrowMatch[0].length).trim(); + if (body.startsWith("{") && body.endsWith("}")) body = body.slice(1, -1); + else body = `return ${body}`; if (!get.isFunctionBody(body)) { console.error("发现疑似恶意的远程代码:", str); return `()=>console.error("尝试执行疑似恶意的远程代码")`; @@ -1558,7 +1560,9 @@ export class Get { } infoFuncOL(info) { let func; + console.log("[infoFuncOL] info:", info); const str = get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入 + console.log("[infoFuncOL] pured:", str); try { // js内置的函数 if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { }; diff --git a/noname/init/index.js b/noname/init/index.js index 262d6c802..768f316fa 100644 --- a/noname/init/index.js +++ b/noname/init/index.js @@ -128,13 +128,6 @@ export async function boot() { // 初始化沙盒的Realms await initializeSandboxRealms(); - // 初始化security - const securityModule = await import("../util/security.js"); - const security = securityModule.default; - security.initSecurity({ - lib, game, ui, get, ai, _status, gnc, - }); - // 设定游戏加载时间,超过时间未加载就提醒 const configLoadTime = localStorage.getItem(lib.configprefix + "loadtime"); // 现在不暴露到全局变量里了,直接传给onload @@ -243,6 +236,7 @@ export async function boot() { await window.initReadWriteFunction(g).catch((e) => { console.error("文件读写函数初始化失败:", e); }); + delete window.initReadWriteFunction; // 后续用不到了喵 } window.onbeforeunload = function () { if (config.get("confirm_exit") && !_status.reloading) { @@ -254,6 +248,13 @@ export async function boot() { } } + // 初始化security + const securityModule = await import("../util/security.js"); + const security = securityModule.default; + security.initSecurity({ + lib, game, ui, get, ai, _status, gnc, + }); + const loadCssPromise = loadCss(); const loadConfigPromise = loadConfig(); await loadCssPromise; @@ -489,8 +490,7 @@ export async function boot() { //var backup_onload=lib.init.onload; _status.evaluatingExtension = true; try { - debugger; // NEED TO VIEW DATA - security.eval(extcontent); + security.eval(extcontent); // 喵? } catch (e) { console.log(e); } diff --git a/noname/library/element/client.js b/noname/library/element/client.js index 279fa579b..8bdc1c103 100644 --- a/noname/library/element/client.js +++ b/noname/library/element/client.js @@ -9,7 +9,7 @@ export class Client { /** * @param {import('../index.js').NodeWS | InstanceType | Client} ws */ - constructor(ws) { + constructor(ws, temp) { if (ws instanceof Client) throw new Error("Client cannot copy."); this.ws = ws; /** @@ -18,7 +18,9 @@ export class Client { // @ts-ignore this.id = ws.wsid || get.id(); this.closed = false; - this.sandbox = security.createSandbox(); + + if (!temp) + this.sandbox = security.createSandbox(); } send() { if (this.closed) return this; diff --git a/noname/library/index.js b/noname/library/index.js index ec3ca63fe..6b1c23cda 100644 --- a/noname/library/index.js +++ b/noname/library/index.js @@ -306,14 +306,14 @@ export class Library { typeof yingbianZhuzhanAI == "function" ? yingbianZhuzhanAI(player, card, source, targets) : cardx => { - var info = get.info(card); - if (info && info.ai && info.ai.yingbian) { - var ai = info.ai.yingbian(card, source, targets, player); - if (!ai) return 0; - return ai - get.value(cardx); - } else if (get.attitude(player, source) <= 0) return 0; - return 5 - get.value(cardx); - }, + var info = get.info(card); + if (info && info.ai && info.ai.yingbian) { + var ai = info.ai.yingbian(card, source, targets, player); + if (!ai) return 0; + return ai - get.value(cardx); + } else if (get.attitude(player, source) <= 0) return 0; + return 5 - get.value(cardx); + }, }); if (!game.online) return; _status.event._resultid = id; @@ -6334,7 +6334,6 @@ export class Library { code = container.textarea.value; } try { - debugger; // NEED TO VIEW DATA var { character } = security.exec2(code); if (!Array.isArray(character)) { throw "err"; @@ -6422,7 +6421,6 @@ export class Library { code = container.textarea.value; } try { - debugger; // NEED TO VIEW DATA var { character } = security.exec2(code); if (!Array.isArray(character)) { throw "err"; @@ -6851,7 +6849,6 @@ export class Library { code = container.textarea.value; } try { - debugger; // NEED TO VIEW DATA var { character } = security.exec2(code); if (!get.is.object(character)) { throw "err"; @@ -7753,8 +7750,10 @@ export class Library { if (Array.isArray(context)) { try { const code = context.length == 1 ? context[0].string : context.reduceRight((pre, cur) => (pre.string || pre) + "." + cur.string); - debugger; // NEED TO VIEW DATA - obj = security.eval(`return ${code};`); + obj = security.exec(`return ${code};`, { + event, trigger, player, card, cards, + result, source, target, targets, + }); if (![null, undefined].includes(obj)) { const keys = Object.getOwnPropertyNames(obj) .concat(Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) @@ -8042,10 +8041,10 @@ export class Library { genAwait(item) { return gnc.is.generator(item) ? gnc.of(function* () { - for (const content of item) { - yield content; - } - })() + for (const content of item) { + yield content; + } + })() : Promise.resolve(item); } gnc = { @@ -10622,16 +10621,16 @@ export class Library { const cardName = get.name(cards[0], player); return cardName ? new lib.element.VCard({ - name: cardName, - nature: get.nature(cards[0], player), - suit: get.suit(cards[0], player), - number: get.number(cards[0], player), - isCard: true, - cards: [cards[0]], - storage: { - stratagem_buffed: 1, - }, - }) + name: cardName, + nature: get.nature(cards[0], player), + suit: get.suit(cards[0], player), + number: get.number(cards[0], player), + isCard: true, + cards: [cards[0]], + storage: { + stratagem_buffed: 1, + }, + }) : new lib.element.VCard(); } return null; @@ -12260,7 +12259,6 @@ export class Library { log: function () { var items = []; try { - debugger; // NEED TO VIEW DATA for (var i = 0; i < arguments.length; i++) { items.push(security.eval(`return ${arguments[i]}`)); } @@ -12531,7 +12529,7 @@ export class Library { navigator.clipboard .readText() .then(read) - .catch(_ => {}); + .catch(_ => { }); } else { var input = ui.create.node("textarea", ui.window, { opacity: "0" }); input.select(); diff --git a/noname/library/init/index.js b/noname/library/init/index.js index 6ea159fba..cb443f707 100644 --- a/noname/library/init/index.js +++ b/noname/library/init/index.js @@ -293,7 +293,6 @@ export class LibInit { if (data.includes("sojson") || data.includes("jsjiami") || data.includes("var _0x")) alert(`检测到您安装了使用免费版sojson进行加密的扩展。请谨慎使用这些扩展,避免游戏数据遭到破坏。\n扩展文件:${pathToRead}`); } try { - debugger; // NEED TO VIEW DATA security.eval(data); if (typeof onLoad == "function") onLoad(); } catch (error) { @@ -775,7 +774,6 @@ export class LibInit { eval(func) { if (typeof func == "function") { - debugger; // NEED TO VIEW DATA return security.eval(`return (${func.toString()});`); } else if (typeof func == "object") { for (var i in func) { @@ -783,7 +781,6 @@ export class LibInit { if (typeof func[i] == "function") { let checkObject = {}; checkObject[i] = func[i]; - debugger; // NEED TO VIEW DATA return security.eval(`return ${get.stringify(checkObject)};`)[i]; } else { func[i] = lib.init.eval(func[i]); diff --git a/noname/ui/create/menu/pages/exetensionMenu.js b/noname/ui/create/menu/pages/exetensionMenu.js index f803f12a9..bc21044c5 100644 --- a/noname/ui/create/menu/pages/exetensionMenu.js +++ b/noname/ui/create/menu/pages/exetensionMenu.js @@ -1679,7 +1679,6 @@ export const extensionMenu = function (connectMenu) { code = container.textarea.value; } try { - debugger; // NEED TO VIEW DATA var { card } = security.exec2(code); if (card == null || typeof card != "object") { throw "err"; @@ -1769,7 +1768,6 @@ export const extensionMenu = function (connectMenu) { page.content.pack.translate[name] = translate; page.content.pack.translate[name + "_info"] = info; try { - debugger; // NEED TO VIEW DATA var { card } = security.exec2(container.code); if (card == null || typeof card != "object") { throw "err"; @@ -2138,7 +2136,6 @@ export const extensionMenu = function (connectMenu) { code = container.textarea.value; } try { - debugger; // NEED TO VIEW DATA var { skill } = security.exec2(code); if (skill == null || typeof skill != "object") { throw "err"; @@ -2321,7 +2318,6 @@ export const extensionMenu = function (connectMenu) { page.content.pack.translate[name] = translate; page.content.pack.translate[name + "_info"] = info; try { - debugger; // NEED TO VIEW DATA var { skill } = security.exec2(container.code); if (skill == null || typeof skill != "object") { throw "err"; @@ -2452,19 +2448,16 @@ export const extensionMenu = function (connectMenu) { } try { if (link == "content" || link == "precontent") { - debugger; // NEED TO VIEW DATA var { func } = security.exec2(`func = ${code}`); if (typeof func != "function") { throw "err"; } } else if (link == "config") { - debugger; // NEED TO VIEW DATA var { config } = security.exec2(code); if (config == null || typeof config != "object") { throw "err"; } } else if (link == "help") { - debugger; // NEED TO VIEW DATA var { help } = security.exec2(code); if (help == null || typeof help != "object") { throw "err"; @@ -2906,13 +2899,12 @@ export const extensionMenu = function (connectMenu) { } } }; - debugger; // NEED TO VIEW DATA window.extension = {}; fetch(`${extensionURL}catalog.js`, { referrerPolicy: "no-referrer", }) .then((response) => response.text()) - .then(security.eval) + .then(security.eval) // 返回的是HTML? .then(loaded) .catch((reason) => { console.log(reason); diff --git a/noname/util/initRealms.js b/noname/util/initRealms.js index e277ed948..8710575b3 100644 --- a/noname/util/initRealms.js +++ b/noname/util/initRealms.js @@ -97,10 +97,6 @@ async function initializeSandboxRealms() { iframe.contentWindow.document.head.appendChild(script); await promise; // Top Await Required Chrome 89 - // @ts-ignore - if (!iframe.contentWindow.SANDBOX_EXPORT) - throw new ReferenceError("无法同步载入运行域"); - // @ts-ignore delete iframe.contentWindow.replacedGlobal; // @ts-ignore diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index dcb0a314d..972c5e82d 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -316,7 +316,7 @@ class Rule { * 返回值则控制本次访问是否允许 * ``` * - * @param {(...args: any[]) => boolean} accessControl + * @param {(action: number, ...args: any[]) => boolean} accessControl */ setAccessControl(accessControl) { Rule.#assertOperator(this); @@ -442,6 +442,7 @@ const MARSHALLED_LIST = Object.freeze([ "/requestIdleCallback", "/cancelIdleCallback", "/queueMicrotask", + "/MutationObserver", ]); /** @@ -2146,7 +2147,7 @@ class Marshal { if (rule && !rule.canAccess(AccessAction.CALL, target, marshalledThis, marshalledArgs)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, marshalledThis, marshalledArgs]; const dispatched = DomainMonitors.dispatch( @@ -2177,7 +2178,7 @@ class Marshal { if (rule && !rule.canAccess(AccessAction.NEW, target, argArray, newTarget)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, marshalledArgs, marshalledNewTarget]; const dispatched = DomainMonitors.dispatch( @@ -2227,7 +2228,7 @@ class Marshal { if (rule && !rule.canAccess(AccessAction.DEFINE, target, property, attributes)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, property, attributes]; const dispatched = DomainMonitors.dispatch( @@ -2251,7 +2252,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.DELETE, target, p)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, p]; const dispatched = DomainMonitors.dispatch( @@ -2286,7 +2287,7 @@ class Marshal { if (rule && !rule.canAccess(AccessAction.READ, target, p, marshalledReceiver)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); // 通知 Monitor const args = [target, p, marshalledReceiver]; @@ -2310,7 +2311,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.DESCRIBE, target, p)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, p]; const dispatched = DomainMonitors.dispatch( @@ -2336,7 +2337,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.TRACE, target)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target]; const dispatched = DomainMonitors.dispatch( @@ -2361,7 +2362,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.EXISTS, target, p)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, p]; const dispatched = DomainMonitors.dispatch( @@ -2387,7 +2388,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.LIST, target)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target]; const dispatched = DomainMonitors.dispatch( @@ -2414,7 +2415,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.SEAL, target)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target]; const dispatched = DomainMonitors.dispatch( @@ -2436,7 +2437,7 @@ class Marshal { if (rule && !rule.canAccess(AccessAction.WRITE, target, p, marshalledNewValue, marshalledReceiver)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, p, marshalledNewValue, marshalledReceiver]; const dispatched = DomainMonitors.dispatch( @@ -2459,7 +2460,7 @@ class Marshal { const rule = ruleRef.rule; if (rule && !rule.canAccess(AccessAction.META, target, marshalledV)) - throw new ReferenceError("Access denied"); + throw new ReferenceError("封送对象的源运行域禁止了此项操作"); const args = [target, marshalledV]; const dispatched = DomainMonitors.dispatch( @@ -3190,6 +3191,7 @@ class Sandbox { parseFloat: this.#domainWindow.parseFloat, isFinite: this.#domainWindow.isFinite, isNaN: this.#domainWindow.isNaN, + Domain: Domain, }; const hardBuiltins = { diff --git a/noname/util/security.js b/noname/util/security.js index 590e71080..78823224d 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -1,15 +1,17 @@ // 是否强制所有模式下使用沙盒 const SANDBOX_FORCED = true; // 是否启用自动测试 -const SANDBOX_AUTOTEST = true; +const SANDBOX_AUTOTEST = false; // 是否禁用自动测试延迟 // 这将放弃渲染,在游戏结束前无响应 const SANDBOX_AUTOTEST_NODELAY = false; +const WSURL_FOR_IP = /ws:\/\/(\d+.\d+.\d+.\d+):\d+\//; const TRUSTED_IPS = Object.freeze([ "47.99.105.222", ]); +// 声明导入类 /** @type {boolean} */ let SANDBOX_ENABLED = true; /** @type {typeof import("./sandbox.js").AccessAction} */ @@ -45,6 +47,7 @@ const sandboxStack = []; const isolatedsMap = new WeakMap(); // noname 顶级变量 +/** @type {Object} */ const topVariables = { lib: null, game: null, @@ -54,8 +57,13 @@ const topVariables = { _status: null, gnc: null, }; + +// eval保存 const defaultEval = window.eval; +// 对于 `lib.init.start` 的首次编译我们放宽 +let initStartParsed = false; +// 是否软启用沙盒 let sandBoxRequired = SANDBOX_FORCED; // 可能的垫片函数 @@ -198,8 +206,19 @@ function requireSandbox() { * @param {string} ip */ function requireSandboxOn(ip) { - if (!TRUSTED_IPS.includes(ip)) + if (!TRUSTED_IPS.includes(ip)) { sandBoxRequired = true; + return; + } + + if (SANDBOX_FORCED + && topVariables.game + && topVariables.game.ws) { + const match = WSURL_FOR_IP.exec(topVariables.game.ws.url); + + if (match && match[1] === ip) + sandBoxRequired = false; + } } /** @@ -213,6 +232,28 @@ function isSandboxRequired() { return SANDBOX_ENABLED && sandBoxRequired; } +/** + * ```plain + * 是否可以跳过沙盒进行编译 + * ``` + * + * @param {any} item + * @returns {boolean} + */ +function canSkipSandbox(item) { + if (!topVariables.lib) + return false; + + if (item === topVariables.lib.init.start) { + if (!initStartParsed) { + initStartParsed = true; + return true; + } + } + + return false; +} + /** * ```plain * 简单的、不带上下文的模拟eval函数 @@ -382,15 +423,8 @@ async function initSecurity({ ...Object.values(game.promises), defaultEval, window.require, - window.process, - window.module, - window.exports, - window.cordova, // @ts-ignore - window.NonameAndroidBridge, - // @ts-ignore - window.noname_shijianInterfaces, - window, + window.define, ]; // 构造禁止函数调用的规则 @@ -409,12 +443,25 @@ async function initSecurity({ bannedRule.canMarshal = false; // 禁止获取 bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性 bannedRule.setGranted(AccessAction.WRITE, false); // 禁止读取属性 + bannedRule.setGranted(AccessAction.DEFINE, false); // 禁止定义属性 + bannedRule.setGranted(AccessAction.DESCRIBE, false); // 禁止描述属性 + bannedRule.setGranted(AccessAction.TRACE, false); // 禁止获取原型 + bannedRule.setGranted(AccessAction.META, false); // 禁止设置原型 // 禁止访问关键对象 [ lib.cheat, lib.node, lib.message, + window.process, + window.module, + window.exports, + window.cordova, + // @ts-ignore + window.NonameAndroidBridge, + // @ts-ignore + window.noname_shijianInterfaces, + window, ] .filter(Boolean) .forEach(o => Marshal.setRule(o, bannedRule)); @@ -434,9 +481,10 @@ async function initSecurity({ // 如果目标是 game 的 ioFuncs 包含的所有函数 .require("target", game) .require("property", ...ioFuncs) + .require("property", "ws") // 抛出异常 - .then(() => { - throw "禁止修改关键函数"; + .then((access, nameds, control) => { + throw `禁止沙盒修改 \`game.${nameds.prototype}\` 属性`; }) // 让 Monitor 开始工作 .start(); // 差点忘记启动了喵 @@ -517,6 +565,18 @@ function createSandbox() { // TODO: 仅提供必要的document函数(?) box.document = document; + if (topVariables.game + && topVariables.game.ws) { + const match = WSURL_FOR_IP.exec(topVariables.game.ws.url); + + if (match && TRUSTED_IPS.includes(match[1])) { + box.scope.ArrayBuffer = ArrayBuffer; + box.scope.localStorage = localStorage; + box.scope.exports = undefined; + box.scope.define = undefined; + } + } + // 传递七个变量 Object.assign(box.scope, topVariables); // 复制垫片函数 @@ -563,6 +623,15 @@ function getIsolateds(sandbox) { * @returns {Array} */ function getIsolatedsFrom(item) { + if (canSkipSandbox(item) || !SANDBOX_ENABLED) { + return [ + defaultFunction, + defaultGeneratorFunction, + defaultAsyncFunction, + defaultAsyncGeneratorFunction, + ]; + } + const domain = Marshal.getMarshalledDomain(item) || Domain.caller; // 非顶级域调用情况下我们替换掉Function类型 @@ -611,25 +680,26 @@ function importSandbox() { }; } +// 原本的Function类型记录 +/** @type {typeof Function} */ +// @ts-ignore +const defaultFunction = function () { }.constructor; +/** @type {typeof Function} */ +// @ts-ignore +const defaultGeneratorFunction = function* () { }.constructor; +/** @type {typeof Function} */ +// @ts-ignore +const defaultAsyncFunction = async function () { }.constructor; +/** @type {typeof Function} */ +// @ts-ignore +const defaultAsyncGeneratorFunction = async function* () { }.constructor; + /** * ```plain * 初始化顶级域的Funcion类型封装 * ``` */ function initIsolatedEnvironment() { - /** @type {typeof Function} */ - // @ts-ignore - const defaultFunction = function () { }.constructor; - /** @type {typeof Function} */ - // @ts-ignore - const defaultGeneratorFunction = function* () { }.constructor; - /** @type {typeof Function} */ - // @ts-ignore - const defaultAsyncFunction = async function () { }.constructor; - /** @type {typeof Function} */ - // @ts-ignore - const defaultAsyncGeneratorFunction = async function* () { }.constructor; - // @ts-ignore defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵 @@ -827,11 +897,11 @@ function setupPolyfills(sandbox) { } // 测试暴露喵 -// Reflect.defineProperty(window, "sandbox", { -// get: () => defaultSandbox, -// set: () => { }, -// configurable: true, -// }); +Reflect.defineProperty(window, "sandbox", { + get: () => defaultSandbox, + set: () => { }, + configurable: true, +}); const exports = { enterSandbox, From 7a8a98424cae3ab26470061fe2f2a4c1272ccf81 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Mon, 27 May 2024 15:59:48 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=86=97=E4=BD=99?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=9B=E8=A6=86=E7=9B=96=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/util/security.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/noname/util/security.js b/noname/util/security.js index 78823224d..4ad2c20f1 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -565,18 +565,6 @@ function createSandbox() { // TODO: 仅提供必要的document函数(?) box.document = document; - if (topVariables.game - && topVariables.game.ws) { - const match = WSURL_FOR_IP.exec(topVariables.game.ws.url); - - if (match && TRUSTED_IPS.includes(match[1])) { - box.scope.ArrayBuffer = ArrayBuffer; - box.scope.localStorage = localStorage; - box.scope.exports = undefined; - box.scope.define = undefined; - } - } - // 传递七个变量 Object.assign(box.scope, topVariables); // 复制垫片函数 @@ -897,11 +885,11 @@ function setupPolyfills(sandbox) { } // 测试暴露喵 -Reflect.defineProperty(window, "sandbox", { - get: () => defaultSandbox, - set: () => { }, - configurable: true, -}); +// Reflect.defineProperty(window, "sandbox", { +// get: () => defaultSandbox, +// set: () => { }, +// configurable: true, +// }); const exports = { enterSandbox, From 03380d1d93e2e1dc12e34180fa23379925a6ecce Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Wed, 29 May 2024 01:53:01 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug=EF=BC=9B=E6=8F=90?= =?UTF-8?q?=E9=AB=98=E6=B2=99=E7=9B=92=E7=A8=B3=E5=AE=9A=E6=80=A7=EF=BC=9B?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B2=99=E7=9B=92=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/get/index.js | 18 +- noname/library/element/client.js | 18 +- noname/library/index.js | 98 +++++++--- noname/library/init/index.js | 10 +- noname/util/sandbox.js | 326 +++++++++++++++++++++++-------- noname/util/security.js | 60 +++--- 6 files changed, 385 insertions(+), 145 deletions(-) diff --git a/noname/get/index.js b/noname/get/index.js index 422765f4a..c7ce0f832 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -1515,7 +1515,7 @@ export class Get { * ``` * * @param {string} str - * @returns + * @returns {string} */ pureFunctionStr(str) { str = str.trim(); @@ -1551,7 +1551,13 @@ export class Get { if (func._filter_args) { return "_noname_func:" + JSON.stringify(get.stringifiedResult(func._filter_args, 3)); } - const str = func.toString(); + const { Marshal, Sandbox } = security.importSandbox(); + const domain = Marshal.getMarshalledDomain(func); + if (domain) { + const sandbox = Sandbox.from(domain); + if (sandbox && "client" in sandbox) throw new Error("不应该二次扩散远程代码"); + } + const str = Marshal.decompileFunction(func); // js内置的函数 if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}"; return "_noname_func:" + get.pureFunctionStr(str); @@ -4960,10 +4966,10 @@ function freezeSlot(obj, key) { Reflect.defineProperty(obj, key, descriptor); } -freezeSlot(Get, "isFunctionBody"); -freezeSlot(Get, "pureFunctionStr"); -freezeSlot(Get, "funcInfoOL"); -freezeSlot(Get, "infoFuncOL"); +freezeSlot(Get.prototype, "isFunctionBody"); +freezeSlot(Get.prototype, "pureFunctionStr"); +freezeSlot(Get.prototype, "funcInfoOL"); +freezeSlot(Get.prototype, "infoFuncOL"); export let get = new Get(); /** diff --git a/noname/library/element/client.js b/noname/library/element/client.js index 8bdc1c103..86bae66c8 100644 --- a/noname/library/element/client.js +++ b/noname/library/element/client.js @@ -8,8 +8,9 @@ import security from "../../util/security.js"; export class Client { /** * @param {import('../index.js').NodeWS | InstanceType | Client} ws + * @param {boolean} temp */ - constructor(ws, temp) { + constructor(ws, temp = false) { if (ws instanceof Client) throw new Error("Client cannot copy."); this.ws = ws; /** @@ -19,8 +20,21 @@ export class Client { this.id = ws.wsid || get.id(); this.closed = false; - if (!temp) + if (!temp) { this.sandbox = security.createSandbox(); + if (this.sandbox) { + Reflect.defineProperty(this, "sandbox", { + value: this.sandbox, + writable: false, + configurable: false, + }); + Reflect.defineProperty(this.sandbox, "client", { + value: this, + writable: false, + configurable: false, + }); + } + } } send() { if (this.closed) return this; diff --git a/noname/library/index.js b/noname/library/index.js index 6b1c23cda..3b27266de 100644 --- a/noname/library/index.js +++ b/noname/library/index.js @@ -164,8 +164,9 @@ export class Library { * } } */ node; + // 谁写的值类型是string,这也太离谱了喵 /** - * @type { { [key: string]: string } } + * @type { { [key: string]: Player } } */ playerOL; /** @@ -9561,14 +9562,13 @@ export class Library { if (!Array.isArray(message) || typeof lib.message.client[message[0]] !== "function") { throw "err"; } - if (!game.sandbox) game.sandbox = security.createSandbox(); - security.enterSandbox(game.sandbox); + if (game.sandbox) security.enterSandbox(game.sandbox); try { for (var i = 1; i < message.length; i++) { message[i] = get.parsedResult(message[i]); } } finally { - security.exitSandbox(); + if (game.sandbox) security.exitSandbox(); } } catch (e) { console.log(e); @@ -12011,7 +12011,9 @@ export class Library { cardPile = {}; message = { server: { - /** @this { any } */ + /** + * @this {import("./element/client.js").Client} + */ init(version, config, banned_info) { if (lib.node.banned.includes(banned_info)) { this.send("denied", "banned"); @@ -12030,9 +12032,14 @@ export class Library { lib.node.clients.remove(this); this.closed = true; } else if (!_status.waitingForPlayer) { - if (game.phaseNumber && lib.configOL.observe) { + if (!config.nickname) { + this.send("denied", "banned"); + lib.node.clients.remove(this); + this.closed = true; + } else if (game.phaseNumber && lib.configOL.observe) { lib.node.observing.push(this); this.send("reinit", lib.configOL, get.arenaState(), game.getState ? game.getState() : {}, game.ip, game.players[0].playerid, null, _status.cardtag); + game.broadcastAll(name => game.log("玩家 ", `#y${name}`, " 进入房间旁观"), config.nickname); if (!ui.removeObserve) { ui.removeObserve = ui.create.system( "移除旁观", @@ -12077,37 +12084,53 @@ export class Library { this.send("init", this.id, lib.configOL, game.ip, window.isNonameServer, game.roomId); } }, - /** @this { any } */ + /** + * @this {import("./element/client.js").Client} + */ inited() { this.inited = true; if (_status.waitingForPlayer) { game.updateWaiting(); } }, - /** @this { any } */ + /** + * @this {import("./element/client.js").Client} + */ reinited() { this.inited = true; }, - result: function (result) { + /** + * @this {import("./element/client.js").Client} + */ + result(result) { if (lib.node.observing.includes(this)) return; var player = lib.playerOL[this.id]; if (player) { player.unwait(result); } }, - tempResult: function (result) { + /** + * @this {import("./element/client.js").Client} + */ + tempResult(result) { if (lib.node.observing.includes(this)) return; var player = lib.playerOL[this.id]; if (player) { player.tempUnwait(result); } }, - startGame: function () { + /** + * @this {import("./element/client.js").Client} + */ + startGame() { if (this.id == game.onlinezhu) { game.resume(); } }, - changeRoomConfig: function (config) { + /** + * @this {import("./element/client.js").Client} + */ + changeRoomConfig(config) { if (this.id == game.onlinezhu) { game.broadcastAll(function (config) { for (var i in config) { @@ -12134,7 +12157,10 @@ export class Library { } } }, - changeNumConfig: function (num, index, bool) { + /** + * @this {import("./element/client.js").Client} + */ + changeNumConfig(num, index, bool) { if (this.id == game.onlinezhu) { lib.configOL.number = num; game.send("server", "config", lib.configOL); @@ -12148,14 +12174,20 @@ export class Library { } } }, - throwEmotion: function (target, emotion, rotate) { + /** + * @this {import("./element/client.js").Client} + */ + throwEmotion(target, emotion, rotate) { if (lib.node.observing.includes(this)) return; var player = lib.playerOL[this.id]; if (player) { player.throwEmotion(target, emotion, rotate); } }, - emotion: function (id, pack, emotion) { + /** + * @this {import("./element/client.js").Client} + */ + emotion(id, pack, emotion) { if (lib.node.observing.includes(this)) return; var that = this; if ( @@ -12185,7 +12217,10 @@ export class Library { } if (player) player.emotion(pack, emotion); }, - chat: function (id, str) { + /** + * @this {import("./element/client.js").Client} + */ + chat(id, str) { if (lib.node.observing.includes(this)) return; var that = this; if ( @@ -12215,8 +12250,18 @@ export class Library { } if (player) player.chat(str); }, - giveup: function (player) { + /** + * ```plain + * 当客机向主机发送投降请求时的回调 + * ``` + * + * @this {import("./element/client.js").Client} + * @param {Player} player + */ + giveup(player) { if (lib.node.observing.includes(this) || !player || !player._giveUp) return; + var self = lib.playerOL[this.id]; + if (self !== player) return; // 禁止让别人投降 _status.event.next.length = 0; game .createEvent("giveup", false) @@ -12227,7 +12272,10 @@ export class Library { player.die("nosource").includeOut = true; }).player = player; }, - auto: function () { + /** + * @this {import("./element/client.js").Client} + */ + auto() { if (lib.node.observing.includes(this)) return; var player = lib.playerOL[this.id]; if (player) { @@ -12238,7 +12286,10 @@ export class Library { }, player); } }, - unauto: function () { + /** + * @this {import("./element/client.js").Client} + */ + unauto() { if (lib.node.observing.includes(this)) return; var player = lib.playerOL[this.id]; if (player) { @@ -12249,18 +12300,21 @@ export class Library { }, player); } }, - exec: function (func) { + exec(func) { // if(typeof func=='function'){ // var args=Array.from(arguments); // args.shift(); // func.apply(this,args); // } }, - log: function () { + /** + * @this {import("./element/client.js").Client} + */ + log() { var items = []; try { for (var i = 0; i < arguments.length; i++) { - items.push(security.eval(`return ${arguments[i]}`)); + items.push(this.sandbox.exec(`return ${arguments[i]}`)); } } catch (e) { this.send("log", ["err"]); diff --git a/noname/library/init/index.js b/noname/library/init/index.js index cb443f707..b5cfee990 100644 --- a/noname/library/init/index.js +++ b/noname/library/init/index.js @@ -142,23 +142,21 @@ export class LibInit { if (!Array.isArray(message) || typeof lib.message.server[message[0]] !== "function") { throw "err"; } - if (!client.sandbox) client.sandbox = security.createSandbox(); // @ts-ignore - security.enterSandbox(client.sandbox); + if (client.sandbox) security.enterSandbox(client.sandbox); try { for (var i = 1; i < message.length; i++) { message[i] = get.parsedResult(message[i]); } } finally { // @ts-ignore - security.exitSandbox(client.sandbox); + if (client.sandbox) security.exitSandbox(); } } catch (e) { console.log(e); console.log("invalid message: " + messagestr); return; } - lib.message.server[message.shift()].apply(client, message); }); ws.on("close", function () { client.close(); @@ -609,10 +607,10 @@ export class LibInit { * @param {Function} func */ function Legacy(func) { + const { Marshal } = security.importSandbox(); //Remove all comments //移除所有注释 - let str = func - .toString() + let str = Marshal.decompileFunction(func) .replace(/((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|(?:\r?\n|[\s\S])[^/"'\\\s]*)/gm, "$2") .trim(); //获取第一个 { 后的所有字符 diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index 972c5e82d..176d24258 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -20,7 +20,6 @@ const SandboxSignal_GetMarshalledProxy = Symbol("GetMarshalledProxy"); const SandboxSignal_SetMarshalledProxy = Symbol("SetMarshalledProxy"); const SandboxSignal_GetWindow = Symbol("GetWindow"); const SandboxSignal_GetPromise = Symbol("GetPromise"); -const SandboxSignal_IsArray = Symbol("IsArray"); const SandboxSignal_EnterDomain = Symbol("EnterDomain"); const SandboxSignal_ExitDomain = Symbol("ExitDomain"); const SandboxSignal_ListDomain = Symbol("ListDomain"); @@ -31,6 +30,7 @@ const SandboxSignal_TrapDomain = Symbol("TrapDomain"); const SandboxSignal_DiapatchMonitor = Symbol("DiapatchMonitor"); const SandboxSignal_ListMonitor = Symbol("ListMonitor"); const SandboxSignal_ExposeInfo = Symbol("ExposeInfo"); +const SandboxSignal_TryFunctionRefs = Symbol("TryFunctionRefs"); /** * ```plain @@ -52,17 +52,17 @@ function isPrimitive(obj) { * ``` */ class AccessAction { - // static CALL = 0; // apply - // static NEW = 1; // construct - // static READ = 2; // get - // static WRITE = 3; // set + // static CALL = 0; // apply + // static NEW = 1; // construct + // static READ = 2; // get + // static WRITE = 3; // set // static DESCRIBE = 4; // getOwnPropertyDescriptor // static DEFINE = 5; // defineProperty - // static TRACE = 6; // getPrototypeOf - // static META = 7; // setPrototypeOf - // static SEAL = 8; // preventExtensions + // static TRACE = 6; // getPrototypeOf + // static META = 7; // setPrototypeOf + // static SEAL = 8; // preventExtensions // static EXISTS = 9; // has - // static LIST = 10; // ownKeys + // static LIST = 10; // ownKeys // static DELETE = 11; // delete /** ```Reflect.apply``` */ @@ -443,6 +443,36 @@ const MARSHALLED_LIST = Object.freeze([ "/cancelIdleCallback", "/queueMicrotask", "/MutationObserver", + // 根据狂神喵提供的问题 + // 我们对console进行迁移 + "/console/debug", + "/console/error", + "/console/info", + "/console/log", + "/console/warn", + "/console/dir", + "/console/dirxml", + "/console/table", + "/console/trace", + "/console/group", + "/console/groupCollapsed", + "/console/groupEnd", + "/console/clear", + "/console/count", + "/console/countReset", + "/console/assert", + "/console/profile", + "/console/profileEnd", + "/console/time", + "/console/timeLog", + "/console/timeEnd", + "/console/timeStamp", + "/console/context", + "/console/createTask", + "/console/memory", + // 另外补充这两个可能的函数哦 + "/alert", + "/confirm", ]); /** @@ -532,6 +562,8 @@ class Globals { if (!Globals.#globals.has(domain)) { const window = domain[SandboxExposer](SandboxSignal_GetWindow); const globals = [new WeakMap(), {}]; + // @ts-ignore + Globals.#globals.set(domain, globals); // 检查是否是顶级域 if (Globals.#topGlobals) { @@ -572,9 +604,6 @@ class Globals { globals[0].set(obj, key); globals[1][key] = obj; } - - // @ts-ignore - Globals.#globals.set(domain, globals); } } @@ -1057,6 +1086,8 @@ class DomainMonitors { /** @type {WeakMap} */ static #domainMonitors = new WeakMap(); + /** @type {WeakMap>} */ + #targetMonitorsMap = new WeakMap(); /** @type {WeakMap>>} */ #monitorsMap = new WeakMap(); @@ -1075,9 +1106,25 @@ class DomainMonitors { actions, allowDomains, disallowDomains, + targets, ] = Monitor[SandboxExposer2] (SandboxSignal_ExposeInfo, monitor); + // 如果指定了目标,使用目标 Monitor 集合 + // 以此对于特定对象的 Monitor 进行性能优化 + if (targets) { + for (const target of targets) { + let monitors = thiz.#targetMonitorsMap.get(target); + + if (!monitors) + thiz.#targetMonitorsMap.set(target, monitors = new Set()); + + monitors.add(monitor); + } + + return; + } + function addToActionMap(actionMap) { for (const action of actions) { let monitorMap = actionMap[action]; @@ -1133,9 +1180,24 @@ class DomainMonitors { actions, allowDomains, disallowDomains, + targets, ] = Monitor[SandboxExposer2] (SandboxSignal_ExposeInfo, monitor); + // 对于指定了目标的 Monitor 特殊处理 + if (targets) { + for (const target of targets) { + const monitors = thiz.#targetMonitorsMap.get(target); + + if (!monitors) + continue; + + monitors.delete(monitor); + } + + return; + } + function removeFromActionMap(actionMap) { for (const action of actions) { const monitorMap = actionMap[action]; @@ -1183,20 +1245,32 @@ class DomainMonitors { * @param {Domain} sourceDomain * @param {Domain} targetDomain * @param {number} action - * @returns {Set?} + * @param {Object} target + * @returns {Array?} */ - static #getMonitorsBy = function (sourceDomain, targetDomain, action) { + static #getMonitorsBy = function (sourceDomain, targetDomain, action, target) { const instance = DomainMonitors.#domainMonitors.get(sourceDomain); if (!instance) return null; + const targetMap = instance.#targetMonitorsMap.get(target); const actionMap = instance.#monitorsMap.get(targetDomain); + const actionMonitors = actionMap && actionMap[action]; - if (!actionMap || !(action in actionMap)) - return null; + let array = null; - return actionMap[action]; + if (targetMap) + array = [...targetMap]; // 优先执行指定目标的 Monitor + + if (actionMonitors) { + if (!array) + array = [...actionMonitors]; + else + array.push(...actionMonitors); + } + + return array; } /** @@ -1222,9 +1296,14 @@ class DomainMonitors { actions, allowDomains, disallowDomains, + targets, ] = Monitor[SandboxExposer2] (SandboxSignal_ExposeInfo, monitor); + // 指定了目标的 Monitor 不参与新运行域处理 + if (targets) + continue; + // 判断新增的 Domain 是否是 Monitor 监听的目标 if (allowDomains && !allowDomains.has(domain)) @@ -1355,14 +1434,16 @@ class DomainMonitors { Object.freeze(nameds); // 获取可能的 Monitor 集合 - const monitorMap = DomainMonitors.#getMonitorsBy(sourceDomain, targetDomain, action); + const monitorMap = DomainMonitors.#getMonitorsBy( + sourceDomain, targetDomain, action, args[0]); + const result = { preventDefault: false, stopPropagation: false, returnValue: undefined, }; - if (!monitorMap || monitorMap.size == 0) + if (!monitorMap || !monitorMap.length) return result; const access = { @@ -1386,6 +1467,9 @@ class DomainMonitors { setReturnValue(value) { result.returnValue = value; }, + throwDenied(message = null) { + throw new RangeError(message || "封送对象的源运行域禁止了此项操作"); + }, }); // 遍历并尝试分发监听事件 @@ -1450,7 +1534,7 @@ class DomainMonitors { * monitor.require("property", "value"); // 指定监听 value 属性 * monitor.filter((access, nameds) => nameds.value >= 0); // 过滤掉大于等于 0 的修改 * monitor.then((access, nameds, control) => { - * control.overrideParameter("value", 0); // 将要修改的新值改回 0 + * control.overrideParameter("value", 0); // 将要修改的新值改回 0 * }); * monitor.start(); // 启动Monitor * ``` @@ -1595,37 +1679,41 @@ class Monitor { /** * - * @typedef {"target" | "thisArg" | "arguments" | "newTarget" | "property" | "descriptor" | "receiver" | "prototype" | "value"} PropertyKey + * @typedef {"target" | "thisArg" | "arguments" + * | "newTarget" | "property" | "descriptor" + * | "receiver" | "prototype" | "value" + * } PropertyKey * * @typedef {{ - * domain: Domain, - * action: number, + * domain: Domain, + * action: number, * }} Access * * @typedef {{ - * target: Object, - * thisArg?: Object, - * arguments?: Array, - * newTarget?: Function, - * property?: string | symbol, - * descriptor?: { - * value?: any, - * writable?: boolean, - * get?: () => any, - * set?: (value: any) => void, - * enumerable?: boolean, - * configurable?: boolean, - * }, - * receiver?: Object, - * prototype?: Object, - * value?: any, + * target: Object, + * thisArg?: Object, + * arguments?: Array, + * newTarget?: Function, + * property?: string | symbol, + * descriptor?: { + * value?: any, + * writable?: boolean, + * get?: () => any, + * set?: (value: any) => void, + * enumerable?: boolean, + * configurable?: boolean, + * }, + * receiver?: Object, + * prototype?: Object, + * value?: any, * }} Nameds * * @typedef {{ - * preventDefault: () => void, - * stopPropagation: () => void, - * overrideParameter: (name: PropertyKey, value: any) => void, - * setReturnValue: (value: any) => void, + * preventDefault: () => void, + * stopPropagation: () => void, + * overrideParameter: (name: PropertyKey, value: any) => void, + * setReturnValue: (value: any) => void, + * throwDenied: (message?: string) => never, * }} Control * */ @@ -1774,7 +1862,8 @@ class Monitor { return [ thiz.#actions, thiz.#allowDomains, - thiz.#disallowDomains + thiz.#disallowDomains, + thiz.#checkInfo["target"], ]; } @@ -1895,8 +1984,8 @@ class Marshal { * ``` * * @typedef {[ - * Domain, - * Object, + * Domain, + * Object, * ]} Reverted * * @param {any} proxy @@ -2000,6 +2089,29 @@ class Marshal { return domain; } + /** + * ```plain + * 对于封送或未封送的函数执行转字符串操作 + * ``` + * + * @param {Function} func + */ + static decompileFunction(func) { + if (typeof func !== "function") + throw new TypeError("无效的函数对象"); + + if (Marshal.#marshalledProxies.has(func)) + [, func] = Marshal.#revertProxy(func); + + const refs = Sandbox[SandboxExposer2] + (SandboxSignal_TryFunctionRefs, func); + + if (refs) + return refs; + + return Function.prototype.toString.call(func); + } + /** * ```plain * 陷入某个运行域并执行代码 @@ -2073,6 +2185,41 @@ class Marshal { } /** + * ```plain + * 根据目标对象的特征复制一个基本对象 + * ``` + * + * @param {Object} src + * @returns {any} + */ + static #clonePureObject = function (src) { + let cloned; + + if (typeof src === "function") { + const descriptor = Reflect.getOwnPropertyDescriptor(src, "prototype"); + if (descriptor + && descriptor.value + && !descriptor.enumerable + && !descriptor.configurable + && descriptor.value.constructor === src) + cloned = function () { }; + else + cloned = () => { }; + } else if (Array.isArray(src)) + cloned = []; + else + cloned = {}; + + Reflect.setPrototypeOf(cloned, null); + cloned["target"] = src; + return cloned; + } + + /** + * ```plain + * 封送核心函数 + * ``` + * * @param {Object} obj * @param {Domain} targetDomain * @returns {Object} @@ -2135,9 +2282,15 @@ class Marshal { if (cached) return cached; + // 创建一个空白对象,防止JavaScript的一些奇怪错误 + const pure = Marshal.#clonePureObject(target); + // 设置属性方便调试 + pure.sourceDomain = sourceDomain; + pure.targetDomain = targetDomain; + // 创建封送代理 - const proxy = new Proxy(target, { - apply(target, thisArg, argArray) { + const proxy = new Proxy(pure, { + apply(_, thisArg, argArray) { const defaultApply = () => { const marshalledThis = Marshal.#marshal(thisArg, sourceDomain); const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); @@ -2169,7 +2322,7 @@ class Marshal { return defaultApply(); }, - construct(target, argArray, newTarget) { + construct(_, argArray, newTarget) { const marshalledArgs = Marshal.#marshalArray(argArray, sourceDomain); const marshalledNewTarget = Marshal.#marshal(newTarget, sourceDomain); @@ -2192,7 +2345,7 @@ class Marshal { return Marshal.#marshal(result, targetDomain); }); }, - defineProperty(target, property, attributes) { + defineProperty(_, property, attributes) { const isSourceDomain = sourceDomain === Domain.current; if (!isSourceDomain) { @@ -2247,7 +2400,7 @@ class Marshal { ? domainTrapAction() : Marshal.#trapDomain(sourceDomain, domainTrapAction); }, - deleteProperty(target, p) { + deleteProperty(_, p) { return Marshal.#trapDomain(sourceDomain, () => { const rule = ruleRef.rule; @@ -2265,7 +2418,7 @@ class Marshal { return Reflect.deleteProperty(...args); }); }, - get(target, p, receiver) { + get(_, p, receiver) { // 因为 get 的东西最多,所以对此追加注释 // 其他的拦截器都是与 get 类似 @@ -2304,7 +2457,7 @@ class Marshal { return Marshal.#marshal(result, targetDomain); }); }, - getOwnPropertyDescriptor(target, p) { + getOwnPropertyDescriptor(_, p) { const isSourceDomain = Domain.current === sourceDomain; const domainTrapAction = () => { @@ -2332,7 +2485,7 @@ class Marshal { return Marshal.#marshalObject(descriptor, targetDomain); }); }, - getPrototypeOf(target) { + getPrototypeOf(_) { return Marshal.#trapDomain(sourceDomain, () => { const rule = ruleRef.rule; @@ -2356,7 +2509,7 @@ class Marshal { return marshalledResult; }); }, - has(target, p) { + has(_, p) { const isSourceDomain = Domain.current === sourceDomain; const domainTrapAction = () => { const rule = ruleRef.rule; @@ -2380,10 +2533,10 @@ class Marshal { return Marshal.#trapDomain(sourceDomain, domainTrapAction); }, - isExtensible(target) { + isExtensible(_) { return Reflect.isExtensible(target); }, - ownKeys(target) { + ownKeys(_) { return Marshal.#trapDomain(sourceDomain, () => { const rule = ruleRef.rule; @@ -2410,7 +2563,7 @@ class Marshal { }); }, - preventExtensions(target) { + preventExtensions(_) { return Marshal.#trapDomain(sourceDomain, () => { const rule = ruleRef.rule; @@ -2428,7 +2581,7 @@ class Marshal { return Reflect.preventExtensions(...args); }); }, - set(target, p, newValue, receiver) { + set(_, p, newValue, receiver) { const marshalledNewValue = Marshal.#marshal(newValue, sourceDomain); const marshalledReceiver = Marshal.#marshal(receiver, sourceDomain); @@ -2450,7 +2603,7 @@ class Marshal { return Reflect.set(...args); }); }, - setPrototypeOf(target, v) { + setPrototypeOf(_, v) { const marshalledV = Marshal.#marshal(v, sourceDomain); if (Marshal.#marshalledProxies.has(marshalledV)) @@ -2610,30 +2763,30 @@ class Domain { // 实装这个要代理Object喵 // static #hasInstanceMarshalled = function (obj) { - // if (Marshal.isMarshalled(obj)) - // [, obj] = Marshal[SandboxExposer2] - // (SandboxSignal_UnpackProxy, obj); + // if (Marshal.isMarshalled(obj)) + // [, obj] = Marshal[SandboxExposer2] + // (SandboxSignal_UnpackProxy, obj); - // return Domain.#hasInstance.call(this, obj); + // return Domain.#hasInstance.call(this, obj); // } // 效率影响不确定,暂不实装 // static #marshalledThen = function (onfulfilled, onrejected) { - // if (Marshal.isMarshalled(this)) { - // const [domain, promise] = Marshal[SandboxExposer2] - // (SandboxSignal_UnpackProxy, this); + // if (Marshal.isMarshalled(this)) { + // const [domain, promise] = Marshal[SandboxExposer2] + // (SandboxSignal_UnpackProxy, this); - // const marshaller = value => { - // return this(trapMarshal(domain, Domain.current, value)); - // }; + // const marshaller = value => { + // return this(trapMarshal(domain, Domain.current, value)); + // }; - // return trapMarshal(domain, Domain.current, promise.then( - // marshaller.bind(onfulfilled), - // marshaller.bind(onrejected) - // )); - // } + // return trapMarshal(domain, Domain.current, promise.then( + // marshaller.bind(onfulfilled), + // marshaller.bind(onrejected) + // )); + // } - // return [[DefaultThen]].call(this, onfulfilled, onrejected); + // return [[DefaultThen]].call(this, onfulfilled, onrejected); // } /** @@ -2881,9 +3034,6 @@ class Domain { return Domain.#exitDomain(); case SandboxSignal_ListDomain: return Domain.#listDomain(); - case SandboxSignal_IsArray: - // @ts-ignore - return Domain.#isArray(...args); } } } @@ -2925,6 +3075,8 @@ class Sandbox { static #domainMap = new WeakMap(); /** @type {Array} */ static #executingScope = []; + /** @type {WeakMap} */ + static #functionRefCodes = new WeakMap(); /** @type {Object} */ #scope; @@ -3015,10 +3167,10 @@ class Sandbox { const code = argArray.slice(-1)[0]; const params = argArray.slice(0, -1); - new target(code); // 防止注入 const compiled = Sandbox.#compileCore(thiz, code, null, params, true); - compiled[Symbol.toStringTag] = `function (${params.join(", ")}) {\n${code}\n}`; + Sandbox.#functionRefCodes.set(compiled, + `function (${params.join(", ")}) {\n${code}\n}`); return compiled; } @@ -3486,6 +3638,18 @@ class Sandbox { return builtName; } + + /** + * @param {Symbol} signal + * @param {...any} args + * @returns + */ + static [SandboxExposer2](signal, ...args) { + switch (signal) { + case SandboxSignal_TryFunctionRefs: + return Sandbox.#functionRefCodes.get(args[0]); + } + } } function sealClass(clazz) { diff --git a/noname/util/security.js b/noname/util/security.js index 4ad2c20f1..f7c17c6fd 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -98,6 +98,8 @@ let ModAsyncGeneratorFunction; function enterSandbox(box) { if (!SANDBOX_ENABLED) return; + if (!(box instanceof Sandbox)) + throw new TypeError("无效的沙盒对象"); if (!Domain.isBelievable(Domain.topDomain)) throw "无法在沙盒里面访问"; @@ -117,7 +119,7 @@ function exitSandbox() { if (!Domain.isBelievable(Domain.topDomain)) throw "无法在沙盒里面访问"; if (!sandboxStack.length) - return; + throw new ReferenceError("无法弹出更多的沙盒"); sandboxStack.pop(); } @@ -422,6 +424,7 @@ async function initSecurity({ ...ioFuncs.map(n => game[n]).filter(Boolean), ...Object.values(game.promises), defaultEval, + localStorage.setItem, window.require, // @ts-ignore window.define, @@ -472,6 +475,8 @@ async function initSecurity({ writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性 // 禁止修改 game.promises 的函数 Marshal.setRule(game.promises, writeRule); + // 禁止修改 localStorage + Marshal.setRule(localStorage, writeRule); // 对于 game 当中访问特定函数我们通过 Monitor 进行拦截 new Monitor() @@ -481,29 +486,28 @@ async function initSecurity({ // 如果目标是 game 的 ioFuncs 包含的所有函数 .require("target", game) .require("property", ...ioFuncs) - .require("property", "ws") + .require("property", "ws", "sandbox") // 抛出异常 .then((access, nameds, control) => { - throw `禁止沙盒修改 \`game.${nameds.prototype}\` 属性`; + throw `有不信任的代码修改 \`game.${String(nameds.property)}\` 属性`; }) // 让 Monitor 开始工作 .start(); // 差点忘记启动了喵 - // 现在 parsex 已经禁止传递字符串,这段 Monitor 不需要了 // 监听原型、toStringTag的更改 - // const toStringTag = Symbol.toStringTag; - // new Monitor() - // .action(AccessAction.WRITE) - // .action(AccessAction.DEFINE) - // .action(AccessAction.META) - // .require("property", toStringTag) - // .then((access, nameds, control) => { - // // 阻止原型、toStringTag的更改 - // control.preventDefault(); - // control.stopPropagation(); - // control.setReturnValue(false); - // }) - // .start(); + const toStringTag = Symbol.toStringTag; + new Monitor() + .action(AccessAction.WRITE) + .action(AccessAction.DEFINE) + .action(AccessAction.META) + .require("property", toStringTag) + .then((access, nameds, control) => { + // 阻止原型、toStringTag的更改 + control.preventDefault(); + control.stopPropagation(); + control.setReturnValue(false); + }) + .start(); if (SANDBOX_AUTOTEST) { // 一个测试循环喵 @@ -646,12 +650,12 @@ function getIsolatedsFrom(item) { * ``` * * @returns {{ - * AccessAction: typeof import("./sandbox.js").AccessAction, - * Domain: typeof import("./sandbox.js").Domain, - * Marshal: typeof import("./sandbox.js").Marshal, - * Monitor: typeof import("./sandbox.js").Monitor, - * Rule: typeof import("./sandbox.js").Rule, - * Sandbox: typeof import("./sandbox.js").Sandbox, + * AccessAction: typeof import("./sandbox.js").AccessAction, + * Domain: typeof import("./sandbox.js").Domain, + * Marshal: typeof import("./sandbox.js").Marshal, + * Monitor: typeof import("./sandbox.js").Monitor, + * Rule: typeof import("./sandbox.js").Rule, + * Sandbox: typeof import("./sandbox.js").Sandbox, * }} */ function importSandbox() { @@ -885,11 +889,11 @@ function setupPolyfills(sandbox) { } // 测试暴露喵 -// Reflect.defineProperty(window, "sandbox", { -// get: () => defaultSandbox, -// set: () => { }, -// configurable: true, -// }); +Reflect.defineProperty(window, "sandbox", { + get: () => defaultSandbox, + set: () => { }, + configurable: true, +}); const exports = { enterSandbox, From ecf390cc1fcdec32249e96868894fbfcb60217c0 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Wed, 29 May 2024 02:57:54 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=80=9A=E8=BF=87windo?= =?UTF-8?q?w=E8=AE=BF=E9=97=AE=E9=A1=B6=E7=BA=A7=E5=9F=9F=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=8F=98=E9=87=8F=E5=A4=B1=E6=95=88=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/util/sandbox.js | 70 ++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index 176d24258..6962a78e3 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -3464,18 +3464,8 @@ class Sandbox { if (p === Symbol.unscopables) return undefined; - if (!(p in target)) { - // 暴露非内建的顶级全局变量 - if (thiz.#freeAccess - && !Globals.isBuiltinKey(p)) { - const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); - - if (p in topWindow) - return trapMarshal(Domain.topDomain, domain, topWindow[p]); - } - + if (!(p in target)) throw new domainWindow.ReferenceError(`${String(p)} is not defined`); - } return target[p]; }, @@ -3597,12 +3587,51 @@ class Sandbox { return sandbox; } + /** + * ```plain + * 创建一个被代理的 Window 对象 + * + * (为什么一定要指名道姓选window的东西喵) + * ``` + * + * @param {Sandbox} thiz + */ static #createScope = function (thiz) { let baseScope = thiz.#scope; - thiz.#scope = new thiz.#domainObject(); + const rawScope = new thiz.#domainObject(); - // 定义两个超级变量 - Reflect.defineProperty(thiz.#scope, "window", { + thiz.#scope = new Proxy(rawScope, { + get(target, p, receiver) { + if (p in target) + return Reflect.get(target, p, receiver); + + // 暴露非内建的顶级全局变量 + if (thiz.#freeAccess + && !Globals.isBuiltinKey(p)) { + const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); + + if (p in topWindow) + return trapMarshal(Domain.topDomain, thiz.#domain, topWindow[p]); + } + + return undefined; + }, + has(target, p) { + if (p in target) + return true; + + if (thiz.#freeAccess + && !Globals.isBuiltinKey(p)) { + const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow); + return p in topWindow; + } + + return false; + }, + }); + + // 定义三个超级变量 + Reflect.defineProperty(rawScope, "window", { get: (function () { // @ts-ignore return this; @@ -3610,7 +3639,15 @@ class Sandbox { enumerable: false, configurable: false, }); - Reflect.defineProperty(thiz.#scope, "document", { + Reflect.defineProperty(rawScope, "self", { + get: (function () { + // @ts-ignore + return this; + }).bind(thiz.#scope), + enumerable: false, + configurable: false, + }); + Reflect.defineProperty(rawScope, "document", { get: (function () { // @ts-ignore return this.#domainDocument; @@ -3625,8 +3662,9 @@ class Sandbox { // 继承之前的变量域 const descriptors = Object.getOwnPropertyDescriptors(baseScope); delete descriptors.window; + delete descriptors.self; delete descriptors.document; - Object.defineProperties(thiz.#scope, descriptors); + Object.defineProperties(rawScope, descriptors); } static #makeName = function (prefix, conflict) { From 69aebf9d07d8fa5aff1f027e3ce5e4633ede54b6 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Wed, 29 May 2024 12:58:24 +0800 Subject: [PATCH 14/16] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E5=A4=8Dbug=EF=BC=9B=E4=B8=BA=E6=97=81?= =?UTF-8?q?=E8=A7=82=E5=8A=A0=E5=85=A5=E6=B7=BB=E5=8A=A0=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/get/index.js | 4 +-- noname/library/index.js | 4 ++- noname/library/init/index.js | 1 + noname/util/sandbox.js | 16 +++++++++--- noname/util/security.js | 49 +++++++++++++++++++++++++----------- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/noname/get/index.js b/noname/get/index.js index c7ce0f832..cd8092f72 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -1566,9 +1566,9 @@ export class Get { } infoFuncOL(info) { let func; - console.log("[infoFuncOL] info:", info); + if ("sandbox" in window) console.log("[infoFuncOL] info:", info); const str = get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入 - console.log("[infoFuncOL] pured:", str); + if ("sandbox" in window) console.log("[infoFuncOL] pured:", str); try { // js内置的函数 if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { }; diff --git a/noname/library/index.js b/noname/library/index.js index 3b27266de..592d3d2e5 100644 --- a/noname/library/index.js +++ b/noname/library/index.js @@ -12039,7 +12039,9 @@ export class Library { } else if (game.phaseNumber && lib.configOL.observe) { lib.node.observing.push(this); this.send("reinit", lib.configOL, get.arenaState(), game.getState ? game.getState() : {}, game.ip, game.players[0].playerid, null, _status.cardtag); - game.broadcastAll(name => game.log("玩家 ", `#y${name}`, " 进入房间旁观"), config.nickname); + // 没有系统提示的接口喵? + game.log("玩家 ", `#y${config.nickname}`, " 进入房间观战"); + game.me.chat(`玩家 ${config.nickname} 进入房间观战`); if (!ui.removeObserve) { ui.removeObserve = ui.create.system( "移除旁观", diff --git a/noname/library/init/index.js b/noname/library/init/index.js index b5cfee990..af524c121 100644 --- a/noname/library/init/index.js +++ b/noname/library/init/index.js @@ -157,6 +157,7 @@ export class LibInit { console.log("invalid message: " + messagestr); return; } + lib.message.server[message.shift()].apply(client, message); }); ws.on("close", function () { client.close(); diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index 6962a78e3..e89a88d91 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -1,3 +1,11 @@ +// 给扩展开发者的的提示: +// 如果你在调试时启用了类似 `在出现异常时中断` 的选项, +// 如果你不想频繁进入此文件,那么你可以在调试窗口中 `右键-将此文件添加到忽略列表/black box script` 来屏蔽此文件 +// 另外由于封送的原因,如果当前模式启用了沙盒,会导致编译后的本地或远程代码创建的对象使用代理包装 +// 你在调试器中需要通过 `[[Target]].target` 查看原始对象,在此导致的不便对开发者表示歉意 + +// 最后为安全考虑,请遵守规范,尽量不要使用 `eval` 函数而是使用 `security.exec2` 来替代 + import { SANDBOX_EXPORT } from "./initRealms.js"; // 很重要的事情! @@ -410,9 +418,6 @@ const GLOBAL_PATHES = Object.freeze([ "/parseFloat", "/isNaN", "/isFinite", - "/alert", - "/confirm", - "/console", // 危险对象不传递 // "/Function", @@ -1595,6 +1600,7 @@ class Monitor { allow(...domains) { Monitor.#assertOperator(this); + // 参数检查 if (this.isStarted) throw new Error("Monitor 在启动期间不能修改"); if (!domains.length) @@ -1632,6 +1638,7 @@ class Monitor { disallow(...domains) { Monitor.#assertOperator(this); + // 参数检查 if (this.isStarted) throw new Error("Monitor 在启动期间不能修改"); if (!domains.length) @@ -1665,6 +1672,7 @@ class Monitor { action(...action) { Monitor.#assertOperator(this); + // 参数检查 if (this.isStarted) throw new Error("Monitor 在启动期间不能修改"); if (action.length == 0 @@ -3412,7 +3420,7 @@ class Sandbox { * @param {string} code 代码字符串 * @param {Object?} context 额外的执行上下文 * @param {Array?} paramList 参数名列表,以此来创建可以传递参数的函数 - * @param {boolean?} inheritScope 是否继承当前正在执行的scope而不是当前沙盒的scope + * @param {boolean?} inheritScope 是否继承当前正在执行的scope而不是当前沙盒的scope(为封装Function类型而提供的参数) * @param {"exists"|"extend"|"all"} writeContext 当执行的代码尝试为未声明的变量赋值时,应该 根据context与window的变量写入(默认行为)|默认行为并且新的变量写入context|全部写入context * @returns */ diff --git a/noname/util/security.js b/noname/util/security.js index f7c17c6fd..c2e713c38 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -1,10 +1,15 @@ +// 声明:沙盒维护的是服务器秩序,让服务器秩序不会因为非房主的玩家以及旁观者的影响,并在此基础上维护玩家设备不受危险代码攻击 +// 但沙盒不会也没有办法维护恶意服务器/房主对于游戏规则的破坏,请玩家尽量选择官方或其他安全的服务器,同时选择一个受信任的玩家作为房主 + // 是否强制所有模式下使用沙盒 -const SANDBOX_FORCED = true; +const SANDBOX_FORCED = false; // 是否启用自动测试 const SANDBOX_AUTOTEST = false; // 是否禁用自动测试延迟 // 这将放弃渲染,在游戏结束前无响应 const SANDBOX_AUTOTEST_NODELAY = false; +// 沙盒开发模式 +const SANDBOX_DEV = false; const WSURL_FOR_IP = /ws:\/\/(\d+.\d+.\d+.\d+):\d+\//; const TRUSTED_IPS = Object.freeze([ @@ -311,8 +316,20 @@ function _exec(x, scope = {}) { * 携带简单上下文的eval函数,并返回scope * eval代码的返回值将覆盖 `scope.return` 这个属性 * 另外任意因对未定义变量赋值导致全局变量赋值的行为将被转移到scope里面 + * (替代eval的对策函数,具体看下面的例子) * * 自动根据沙盒的启用状态使用不同的实现 + * + * 下面是 `security.exec2` 的使用示例: + * ``` + * @example + * ```javascript + * // 执行一段代码并获取赋值的多个变量 + * let { return: skill, filter, content } = security.exec2(` + * filter = (e, p) => e.source && e.source == p; + * content = async (e, t, p) => t.cancel(); + * return { filter, content }; + * `, { content: () => {}, lib, game, ui, get, ai, _status, }); // 提供默认的content,提供六个变量 * ``` * * @param {any} x @@ -511,13 +528,6 @@ async function initSecurity({ if (SANDBOX_AUTOTEST) { // 一个测试循环喵 - if (SANDBOX_AUTOTEST_NODELAY) { - game.resume = () => { }; - game.pause = () => { }; - } - game.delay = game.delayx = () => { }; - game.asyncDelay = game.asyncDelayx = async () => { }; - Reflect.defineProperty(lib.element.GameEvent.prototype, "animate", { get: () => undefined, set() { }, @@ -544,7 +554,16 @@ async function initSecurity({ }, SANDBOX_AUTOTEST_NODELAY ? 5000 : 1000); }; - lib.arenaReady.push(() => ui.click.auto()); + lib.arenaReady.push(() => setTimeout(() => { + if (SANDBOX_AUTOTEST_NODELAY) { + game.resume = () => { }; + game.pause = () => { }; + } + game.delay = game.delayx = () => { }; + game.asyncDelay = game.asyncDelayx = async () => { }; + + ui.auto.click(); + }, 1000)); } initialized = true; @@ -889,11 +908,13 @@ function setupPolyfills(sandbox) { } // 测试暴露喵 -Reflect.defineProperty(window, "sandbox", { - get: () => defaultSandbox, - set: () => { }, - configurable: true, -}); +if (SANDBOX_DEV) { + Reflect.defineProperty(window, "sandbox", { + get: () => defaultSandbox, + set: () => { }, + configurable: true, + }); +} const exports = { enterSandbox, From 19006f23c62a7b10787320f4572d8259c84c9bf5 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Wed, 29 May 2024 13:50:36 +0800 Subject: [PATCH 15/16] =?UTF-8?q?=E6=9C=80=E7=BB=88=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/util/sandbox.js | 1 + 1 file changed, 1 insertion(+) diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index e89a88d91..28d190510 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -3,6 +3,7 @@ // 如果你不想频繁进入此文件,那么你可以在调试窗口中 `右键-将此文件添加到忽略列表/black box script` 来屏蔽此文件 // 另外由于封送的原因,如果当前模式启用了沙盒,会导致编译后的本地或远程代码创建的对象使用代理包装 // 你在调试器中需要通过 `[[Target]].target` 查看原始对象,在此导致的不便对开发者表示歉意 +// 不过沙盒默认只在联机实际,实际影响已经降到最低,对于不接触联机模式的开发者来说,应该没有太大问题 // 最后为安全考虑,请遵守规范,尽量不要使用 `eval` 函数而是使用 `security.exec2` 来替代 From 55736093a66187d7950f2073191a961c08e108e2 Mon Sep 17 00:00:00 2001 From: IceCola <739201322@qq.com> Date: Wed, 29 May 2024 19:44:44 +0800 Subject: [PATCH 16/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B8=B8=E6=88=8F?= =?UTF-8?q?=E5=86=85=E6=8E=A7=E5=88=B6=E5=8F=B0bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/ui/create/menu/pages/otherMenu.js | 37 ++++++++++------- noname/util/sandbox.js | 52 ++++++++++++++++++++---- noname/util/security.js | 2 +- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/noname/ui/create/menu/pages/otherMenu.js b/noname/ui/create/menu/pages/otherMenu.js index 834d14335..da128c5e0 100644 --- a/noname/ui/create/menu/pages/otherMenu.js +++ b/noname/ui/create/menu/pages/otherMenu.js @@ -1229,6 +1229,7 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM return; } + // control.overrideParameter("target", window); }) .start(); @@ -1260,21 +1261,27 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM */ let fun if (security.isSandboxRequired()) { - fun = security.eval(` - const _status=window._status; - const lib=window.lib; - const game=window.game; - const ui=window.ui; - const get=window.get; - const ai=window.ai; - // const cheat=window.lib.cheat; // 不再允许使用 cheat,因为它是不允许访问的变量 - //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。 - const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/}; - return function(value){ - "use strict"; - return eval(reg.test(value)?('('+value+')'):value); - }; - `); + const reg = /^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/; + fun = function (value) { + const exp = reg.test(value) ? `(${value})` : value; + const expName = "_" + Math.random().toString().slice(2); + return security.exec(`return eval(${expName})`, { window: proxyWindow, [expName]: exp }); + }; + // security.exec(` + // const _status=window._status; + // const lib=window.lib; + // const game=window.game; + // const ui=window.ui; + // const get=window.get; + // const ai=window.ai; + // // const cheat=window.lib.cheat; // 不再允许使用 cheat,因为它是不允许访问的变量 + // //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。 + // const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/}; + // return function(value){ + // "use strict"; + // return eval(reg.test(value)?('('+value+')'):value); + // }; + // `, { window: proxyWindow }); } else { fun = (new Function('window', ` const _status=window._status; diff --git a/noname/util/sandbox.js b/noname/util/sandbox.js index 28d190510..de291f8c3 100644 --- a/noname/util/sandbox.js +++ b/noname/util/sandbox.js @@ -3101,9 +3101,11 @@ class Sandbox { /** @type {Document?} */ #domainDocument; /** @type {typeof Object} */ - #domainObject = Object; + #domainObject; /** @type {typeof Function} */ - #domainFunction = Function; + #domainFunction; + /** @type {typeof Function} */ + #domainEval; /** * ```plain @@ -3128,9 +3130,10 @@ class Sandbox { this.#domainDocument = null; // 默认不开放DOM,而且我们也缺少BrowserContext this.#domainObject = this.#domainWindow.Object; this.#domainFunction = this.#domainWindow.Function; + this.#domainEval = this.#domainWindow.eval; Sandbox.#domainMap.set(this.#domain, this); - Sandbox.#createScope(this); Sandbox.#initDomainFunctions(this, this.#domainWindow); + Sandbox.#createScope(this); } /** @@ -3209,6 +3212,31 @@ class Sandbox { rewriteCtor(defaultAsyncGeneratorFunction.prototype, new Proxy(defaultAsyncGeneratorFunction, handler)); } + /** + * ```plain + * 替代原本的eval函数,阻止访问原生的 window 对象 + * ``` + * + * @param {Window} trueWindow + * @param {(x: string) => any} _eval + * @param {Proxy} intercepter + * @param {Window} global + * @param {any} x + */ + static #wrappedEval = function (trueWindow, _eval, intercepter, global, x) { + const intercepterName = Sandbox.#makeName("_", trueWindow); + const evalName = Sandbox.#makeName("_", global); + const codeName = Sandbox.#makeName("_", global); + trueWindow[intercepterName] = intercepter; + global[evalName] = _eval; + global[codeName] = x; + const result = _eval(`with(${intercepterName}){with(window){${evalName}("use strict;"+${codeName})}}`); + delete global[codeName]; + delete global[evalName]; + delete trueWindow[intercepterName]; + return result; + } + /** * ```plain * 获取当前的scope @@ -3306,8 +3334,8 @@ class Sandbox { * ``` */ const builtins = { - Object: this.#domainObject, - Function: this.#domainFunction, + Object: this.#domainWindow.Object, + Function: this.#domainWindow.Function, Array: this.#domainWindow.Array, Math: this.#domainWindow.Math, Date: this.#domainWindow.Date, @@ -3332,7 +3360,7 @@ class Sandbox { Reflect: this.#domainWindow.Reflect, BigInt: this.#domainWindow.BigInt, JSON: this.#domainWindow.JSON, - eval: this.#domainWindow.eval, + // eval: this.#domainWindow.eval, // 我们另外定义 `eval` 函数 setTimeout: this.#domainWindow.setTimeout, clearTimeout: this.#domainWindow.clearTimeout, setInterval: this.#domainWindow.setInterval, @@ -3446,6 +3474,7 @@ class Sandbox { const writeContextAction = { exists: 0, extend: 1, all: 2 }[writeContext] || 0; let argumentList; + let wrappedEval; const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(()=>{"use strict";return(${applyName}(function(${parameters}){\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n},${contextName}.this,${argsName}))})()}}}`); @@ -3473,8 +3502,12 @@ class Sandbox { if (p === Symbol.unscopables) return undefined; - if (!(p in target)) + if (!(p in target)) { + if (p === "eval") + return wrappedEval; // 返回我们封装的 `eval` 函数 + throw new domainWindow.ReferenceError(`${String(p)} is not defined`); + } return target[p]; }, @@ -3487,9 +3520,12 @@ class Sandbox { }, }); + wrappedEval = Sandbox.#wrappedEval.bind(null, + thiz.#domainWindow, thiz.#domainEval, intercepter, scope); + // 构建陷入的沙盒闭包 // 同时对返回值进行封送 - return ((...args) => { + return ((/** @type {any} */ ...args) => { const prevDomain = Domain.current; const domainAction = () => { // 指定执行域 diff --git a/noname/util/security.js b/noname/util/security.js index c2e713c38..7159cdd1c 100644 --- a/noname/util/security.js +++ b/noname/util/security.js @@ -2,7 +2,7 @@ // 但沙盒不会也没有办法维护恶意服务器/房主对于游戏规则的破坏,请玩家尽量选择官方或其他安全的服务器,同时选择一个受信任的玩家作为房主 // 是否强制所有模式下使用沙盒 -const SANDBOX_FORCED = false; +const SANDBOX_FORCED = true; // 是否启用自动测试 const SANDBOX_AUTOTEST = false; // 是否禁用自动测试延迟