SandboxJS has a sandbox escape via TOCTOU bug on keys in property accesses
Description
SandboxJS is a JavaScript sandboxing library. Prior to 0.8.29, there is a sandbox escape vulnerability due to a mismatch between the key on which the validation is performed and the key used for accessing properties. Even though the key used in property accesses is annotated as string, this is never enforced. So, attackers can pass malicious objects that coerce to different string values when used, e.g., one for the time the key is sanitized using hasOwnProperty(key) and a different one for when the key is used for the actual property access. This vulnerability is fixed in 0.8.29.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@nyariv/sandboxjsnpm | < 0.8.29 | 0.8.29 |
Affected products
1Patches
167cb186c41c7fix(security): harden sandbox against code execution bypass (GHSA-jjpw-65fv-8g48, GHSA-66h4-qj4x-38xp, GHSA-58jh-xv4v-pcx4, GHSA-7x3h-rm86-3342)
10 files changed · +1110 −556
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "@nyariv/sandboxjs", - "version": "0.8.28", + "version": "0.8.29", "description": "Javascript sandboxing library.", "main": "dist/node/Sandbox.js", "module": "./build/Sandbox.js",
README.md+1 −0 modified@@ -85,6 +85,7 @@ console.log(Sandbox.audit(code)); ## Safe Globals +- `globalThis` - `Function` - `eval` - `console`
src/executor.ts+118 −84 modified@@ -1,6 +1,7 @@ import { LispItem, Lisp, IRegEx, LispFamily, ExtractLispOp, SwitchCase } from './parser.js'; import { CodeString, + hasOwnProperty, IAuditReport, IExecContext, IScope, @@ -209,38 +210,38 @@ export function assignCheck(obj: Prop, context: IExecContext, op = 'assign') { throw new SyntaxError(`Cannot ${op} value to a primitive.`); } if (obj.isConst) { - throw new TypeError(`Cannot set value to const variable '${obj.prop}'`); + throw new TypeError(`Assignment to constant variable.`); } if (obj.isGlobal) { - throw new SandboxError(`Cannot ${op} property '${obj.prop}' of a global object`); + throw new SandboxError(`Cannot ${op} property '${obj.prop.toString()}' of a global object`); } if (obj.context === null) { throw new TypeError('Cannot set properties of null'); } - if (typeof obj.context[obj.prop] === 'function' && !obj.context.hasOwnProperty(obj.prop)) { - throw new SandboxError(`Override prototype property '${obj.prop}' not allowed`); + if (typeof (obj.context as any)[obj.prop] === 'function' && !hasOwnProperty(obj.context, obj.prop)) { + throw new SandboxError(`Override prototype property '${obj.prop.toString()}' not allowed`); } if (op === 'delete') { - if (obj.context.hasOwnProperty(obj.prop)) { + if (hasOwnProperty(obj.context, obj.prop)) { context.changeSubscriptions .get(obj.context) - ?.forEach((cb) => cb({ type: 'delete', prop: obj.prop })); + ?.forEach((cb) => cb({ type: 'delete', prop: obj.prop.toString() })); context.changeSubscriptionsGlobal .get(obj.context) - ?.forEach((cb) => cb({ type: 'delete', prop: obj.prop })); + ?.forEach((cb) => cb({ type: 'delete', prop: obj.prop.toString() })); } - } else if (obj.context.hasOwnProperty(obj.prop)) { + } else if (hasOwnProperty(obj.context, obj.prop)) { context.setSubscriptions .get(obj.context) - ?.get(obj.prop) + ?.get(obj.prop.toString()) ?.forEach((cb) => cb({ type: 'replace', }) ); context.setSubscriptionsGlobal .get(obj.context) - ?.get(obj.prop) + ?.get(obj.prop.toString()) ?.forEach((cb) => cb({ type: 'replace', @@ -249,10 +250,10 @@ export function assignCheck(obj: Prop, context: IExecContext, op = 'assign') { } else { context.changeSubscriptions .get(obj.context) - ?.forEach((cb) => cb({ type: 'create', prop: obj.prop })); + ?.forEach((cb) => cb({ type: 'create', prop: obj.prop.toString() })); context.changeSubscriptionsGlobal .get(obj.context) - ?.forEach((cb) => cb({ type: 'create', prop: obj.prop })); + ?.forEach((cb) => cb({ type: 'create', prop: obj.prop.toString() })); } } const arrayChange = new Set([ @@ -301,120 +302,134 @@ export function addOps<Type extends LispFamily>(type: ExtractLispOp<Type>, cb: O ops.set(type, cb); } -addOps(LispType.Prop, (exec, done, ticks, a, b: string, obj, context, scope) => { +function isPropertyKey(val: unknown): val is PropertyKey { + return ['string', 'number', 'symbol'].includes(typeof val); +} + +function hasPossibleProperties(val: unknown): val is {} { + return val !== null && val !== undefined; +} + + +addOps(LispType.Prop, (exec, done, ticks, a: unknown, b: PropertyKey, obj, context, scope) => { if (a === null) { - throw new TypeError(`Cannot get property ${b} of null`); + throw new TypeError(`Cannot read properties of null (reading '${b?.toString()}')`); + } + + if (!isPropertyKey(b)) { + try { + b = `${b}`; + } catch (e) { + done(e); + return; + } } - const type = typeof a; - if (type === 'undefined' && obj === undefined) { + + if (a === undefined && obj === undefined && typeof b === 'string') { // is variable access const prop = scope.get(b); if (prop.context === context.ctx.sandboxGlobal) { if (context.ctx.options.audit) { context.ctx.auditReport?.globalsAccess.add(b); } - const rep = context.ctx.globalsWhitelist.has(context.ctx.sandboxGlobal[b]) - ? context.evals.get(context.ctx.sandboxGlobal[b]) - : undefined; - if (rep) { - done(undefined, rep); - return; - } } - if (prop.context && prop.context[b] === globalThis) { - done(undefined, context.ctx.globalScope.get('this')); + const val = prop.context ? (prop.context as any)[prop.prop] : undefined; + if (val === globalThis) { + done(undefined, new Prop({ + [prop.prop]: context.ctx.sandboxGlobal + }, prop.prop, prop.isConst, false, prop.isVariable)); return; } + const e = typeof val === 'function' && context.evals.get(val); + if (e) { + done(undefined, new Prop({ + [prop.prop]: e + }, prop.prop, prop.isConst, true, prop.isVariable)); + return; + } + done(undefined, prop); return; } else if (a === undefined) { - throw new SandboxError("Cannot get property '" + b + "' of undefined"); + throw new SandboxError(`Cannot read properties of undefined (reading '${b.toString()}')`); } - if (type !== 'object') { - if (type === 'number') { - a = new Number(a); - } else if (type === 'string') { - a = new String(a); - } else if (type === 'boolean') { - a = new Boolean(a); - } - } else if (typeof a.hasOwnProperty === 'undefined') { + if (!hasPossibleProperties(a)) { done(undefined, new Prop(undefined, b)); return; } - const isFunction = type === 'function'; - const prototypeAccess = isFunction || !(a.hasOwnProperty(b) || typeof b === 'number'); + const prototypeAccess = typeof a === 'function' || !hasOwnProperty(a, b); if (context.ctx.options.audit && prototypeAccess) { - if (typeof b === 'string') { - let prot = Object.getPrototypeOf(a); - do { - if (prot.hasOwnProperty(b)) { - if ( - context.ctx.auditReport && - !context.ctx.auditReport.prototypeAccess[prot.constructor.name] - ) { - context.ctx.auditReport.prototypeAccess[prot.constructor.name] = new Set(); - } - context.ctx.auditReport?.prototypeAccess[prot.constructor.name].add(b); + let prot: {} = Object.getPrototypeOf(a); + do { + if (hasOwnProperty(prot, b)) { + if ( + context.ctx.auditReport && + !context.ctx.auditReport.prototypeAccess[prot.constructor.name] + ) { + context.ctx.auditReport.prototypeAccess[prot.constructor.name] = new Set(); } - } while ((prot = Object.getPrototypeOf(prot))); - } + context.ctx.auditReport?.prototypeAccess[prot.constructor.name].add(b); + } + } while ((prot = Object.getPrototypeOf(prot))); } if (prototypeAccess) { - if (isFunction) { - if (!['name', 'length', 'constructor'].includes(b) && a.hasOwnProperty(b)) { + if (typeof a === 'function') { + if (hasOwnProperty(a, b)) { const whitelist = context.ctx.prototypeWhitelist.get(a.prototype); const replace = context.ctx.options.prototypeReplacements.get(a); if (replace) { done(undefined, new Prop(replace(a, true), b)); return; } if (!(whitelist && (!whitelist.size || whitelist.has(b)))) { - throw new SandboxError(`Static method or property access not permitted: ${a.name}.${b}`); + throw new SandboxError(`Static method or property access not permitted: ${a.name}.${b.toString()}`); } } } - if (b !== 'constructor') { - let prot = a; - while ((prot = Object.getPrototypeOf(prot))) { - if (prot.hasOwnProperty(b)) { - const whitelist = context.ctx.prototypeWhitelist.get(prot); - const replace = context.ctx.options.prototypeReplacements.get(prot.constuctor); - if (replace) { - done(undefined, new Prop(replace(a, false), b)); - return; - } - if (whitelist && (!whitelist.size || whitelist.has(b))) { - break; - } - throw new SandboxError( - `Method or property access not permitted: ${prot.constructor.name}.${b}` - ); + let prot: {} = a; + while ((prot = Object.getPrototypeOf(prot))) { + if (hasOwnProperty(prot, b)) { + const whitelist = context.ctx.prototypeWhitelist.get(prot); + const replace = context.ctx.options.prototypeReplacements.get(prot.constructor); + if (replace) { + done(undefined, new Prop(replace(a, false), b)); + return; + } + if (whitelist && (!whitelist.size || whitelist.has(b))) { + break; } + throw new SandboxError( + `Method or property access not permitted: ${prot.constructor.name}.${b.toString()}` + ); } } } - if (context.evals.has(a[b])) { - done(undefined, context.evals.get(a[b])); + const e = typeof a[b as keyof typeof a] === 'function' && context.evals.get(a[b as keyof typeof a]); + if (e) { + done(undefined, new Prop({ + [b]: e + }, b, false, true, false)); return; } - if (a[b] === globalThis) { - done(undefined, context.ctx.globalScope.get('this')); + if (a[b as keyof typeof a] === globalThis) { + done(undefined, new Prop({ + [b]: context.ctx.sandboxGlobal + }, b, false, false, false)); return; } const g = obj.isGlobal || - (isFunction && !sandboxedFunctions.has(a)) || + (typeof a === 'function' && !sandboxedFunctions.has(a)) || context.ctx.globalsWhitelist.has(a); - done(undefined, new Prop(a, b, false, g)); + done(undefined, new Prop(a, b, false, g, false)); }); addOps(LispType.Call, (exec, done, ticks, a, b: Lisp[], obj, context) => { @@ -515,7 +530,14 @@ addOps(LispType.Call, (exec, done, ticks, a, b: Lisp[], obj, context) => { } } obj.get(context); - done(undefined, obj.context[obj.prop](...vals)); + let ret = obj.context[obj.prop](...vals); + if (typeof ret === 'function') { + ret = context.evals.get(ret) || ret; + } + if (ret === globalThis) { + ret = context.ctx.sandboxGlobal; + } + done(undefined, ret); }); addOps(LispType.CreateObject, (exec, done, ticks, a, b: KeyVal[]) => { @@ -648,8 +670,20 @@ addOps(LispType.DecrementAfter, (exec, done, ticks, a, b, obj, context) => { done(undefined, obj.context[obj.prop]--); }); -addOps(LispType.Assign, (exec, done, ticks, a, b, obj, context) => { +addOps(LispType.Assign, (exec, done, ticks, a, b, obj, context, scope, bobj) => { assignCheck(obj, context); + obj.isGlobal = bobj?.isGlobal || false; + if (obj.isVariable) { + const s = scope.getWhereValScope(obj.prop); + if (s === null) { + throw new ReferenceError(`Cannot assign to undeclared variable '${obj.prop}'`); + } + if (obj.isGlobal) { + s.globals[obj.prop] = true; + } else { + delete s.globals[obj.prop]; + } + } done(undefined, (obj.context[obj.prop] = b)); }); @@ -754,7 +788,7 @@ addOps(LispType.Instanceof, (exec, done, ticks, a, b: { new (): unknown }) => addOps(LispType.In, (exec, done, ticks, a: string, b) => done(undefined, a in b)); addOps(LispType.Delete, (exec, done, ticks, a, b, obj, context, scope, bobj: Prop) => { - if (bobj.context === undefined) { + if (!(bobj instanceof Prop)) { done(undefined, true); return; } @@ -763,21 +797,21 @@ addOps(LispType.Delete, (exec, done, ticks, a, b, obj, context, scope, bobj: Pro done(undefined, false); return; } - done(undefined, delete bobj.context?.[bobj.prop]); + done(undefined, delete (bobj.context as any)?.[bobj.prop]); }); addOps(LispType.Return, (exec, done, ticks, a, b) => done(undefined, b)); -addOps(LispType.Var, (exec, done, ticks, a: string, b: LispItem, obj, context, scope) => { - done(undefined, scope.declare(a, VarType.var, b)); +addOps(LispType.Var, (exec, done, ticks, a: string, b: LispItem, obj, context, scope, bobj) => { + done(undefined, scope.declare(a, VarType.var, b, bobj?.isGlobal || false)); }); addOps(LispType.Let, (exec, done, ticks, a: string, b: LispItem, obj, context, scope, bobj) => { - done(undefined, scope.declare(a, VarType.let, b, bobj && bobj.isGlobal)); + done(undefined, scope.declare(a, VarType.let, b, bobj?.isGlobal || false)); }); -addOps(LispType.Const, (exec, done, ticks, a: string, b: LispItem, obj, context, scope) => { - done(undefined, scope.declare(a, VarType.const, b)); +addOps(LispType.Const, (exec, done, ticks, a: string, b: LispItem, obj, context, scope, bobj) => { + done(undefined, scope.declare(a, VarType.const, b, bobj?.isGlobal || false)); }); addOps(
src/parser.ts+11 −2 modified@@ -1,5 +1,5 @@ import unraw from './unraw.js'; -import { CodeString, isLisp, LispType } from './utils.js'; +import { CodeString, isLisp, LispType, reservedWords } from './utils.js'; export type DefineLisp< op extends LispType, @@ -1121,7 +1121,11 @@ setLispType(['dot', 'prop'] as const, (constants, type, part, res, expect, ctx) prop = matches[0]; index = prop.length + res[0].length; } else { - throw new SyntaxError('Hanging dot'); + throw new SyntaxError('Hanging dot'); + } + } else { + if (reservedWords.has(prop) && prop !== 'this') { + throw new SyntaxError(`Unexpected token '${prop}'`); } } ctx.lispTree = lispify( @@ -1231,6 +1235,11 @@ setLispType( !isReturn ? [/^}/] : [/^[,)}\]]/, semiColon] ); const func = isReturn ? 'return ' + f : f.toString(); + args.forEach((arg: string) => { + if (reservedWords.has(arg.replace(/^\.\.\./, ''))) { + throw new SyntaxError(`Unexpected token '${arg}'`); + } + }); ctx.lispTree = lispify( constants, part.substring(res[0].length + func.length + 1),
src/SandboxExec.ts+7 −0 modified@@ -9,6 +9,7 @@ import { IOptionParams, IOptions, IScope, + LocalScope, replacementCallback, SandboxGlobal, SubscriptionSubject, @@ -73,9 +74,13 @@ export default class SandboxExec { this.context = createContext(this, opt); } + static LocalScope = LocalScope + static get SAFE_GLOBALS(): IGlobals { return { + globalThis, Function, + eval, console: { debug: console.debug, error: console.error, @@ -166,6 +171,8 @@ export default class SandboxExec { map.set( Object, new Set([ + 'constructor', + 'name', 'entries', 'fromEntries', 'getOwnPropertyNames',
src/utils.ts+110 −69 modified@@ -30,7 +30,7 @@ export interface IOptions { audit: boolean; forbidFunctionCalls: boolean; forbidFunctionCreation: boolean; - prototypeReplacements: Map<new () => any, replacementCallback>; + prototypeReplacements: Map<Function, replacementCallback>; prototypeWhitelist: Map<any, Set<string>>; globals: IGlobals; executionQuota?: bigint; @@ -47,14 +47,14 @@ export interface IContext { globalScope: Scope; sandboxGlobal: ISandboxGlobal; globalsWhitelist: Set<any>; - prototypeWhitelist: Map<any, Set<string>>; + prototypeWhitelist: Map<any, Set<PropertyKey>>; options: IOptions; auditReport?: IAuditReport; } export interface IAuditReport { globalsAccess: Set<unknown>; - prototypeAccess: { [name: string]: Set<string> }; + prototypeAccess: { [name: string]: Set<PropertyKey> }; } export interface Ticks { @@ -87,7 +87,6 @@ interface SandboxGlobalConstructor { } export const SandboxGlobal = function SandboxGlobal(this: ISandboxGlobal, globals: IGlobals) { - if (globals === (globalThis as any)) return globalThis; for (const i in globals) { this[i] = globals[i]; } @@ -174,6 +173,11 @@ export function createExecContext( evals.set(setTimeout, evalContext.sandboxedSetTimeout(func)); evals.set(setInterval, evalContext.sandboxedSetInterval(func)); + for (const [key, value] of evals) { + sandbox.context.prototypeWhitelist.set(value.prototype, new Set()); + sandbox.context.prototypeWhitelist.set(key.prototype, new Set()); + } + } return execContext; } @@ -282,35 +286,46 @@ function keysOnly(obj: unknown): Record<string, true> { return ret; } -const reservedWords = new Set([ - 'instanceof', - 'typeof', - 'return', - 'throw', - 'try', +export const reservedWords = new Set([ + 'await', + 'break', + 'case', 'catch', - 'if', - 'finally', - 'else', - 'in', - 'of', - 'var', - 'let', + 'class', 'const', - 'for', + 'continue', + 'debugger', + 'default', 'delete', - 'false', - 'true', - 'while', 'do', - 'break', - 'continue', - 'new', + 'else', + 'enum', + 'export', + 'extends', + 'false', + 'finally', + 'for', 'function', - 'async', - 'await', + 'if', + 'implements', + 'import', + 'in', + 'instanceof', + 'let', + 'new', + 'null', + 'return', + 'super', 'switch', - 'case', + 'this', + 'throw', + 'true', + 'try', + 'typeof', + 'var', + 'void', + 'while', + 'with', ]); export const enum VarType { @@ -337,30 +352,16 @@ export class Scope { this.functionThis = functionThis; } - get(key: string, functionScope = false): Prop { - const functionThis = this.functionThis; - if (key === 'this' && functionThis !== undefined) { - return new Prop({ this: functionThis }, key, true, false, true); + get(key: string): Prop { + const isThis = key === 'this'; + const scope = this.getWhereValScope(key, isThis); + if (scope && isThis) { + return new Prop({ this: scope.functionThis }, key, true, false, true); } - if (reservedWords.has(key)) throw new SyntaxError("Unexepected token '" + key + "'"); - if (this.parent === null || !functionScope || functionThis !== undefined) { - if (this.globals.hasOwnProperty(key)) { - return new Prop(functionThis, key, false, true, true); - } - if (key in this.allVars && (!(key in {}) || this.allVars.hasOwnProperty(key))) { - return new Prop( - this.allVars, - key, - this.const.hasOwnProperty(key), - this.globals.hasOwnProperty(key), - true - ); - } - if (this.parent === null) { - return new Prop(undefined, key); - } + if (!scope) { + return new Prop(undefined, key); } - return this.parent.get(key, functionScope); + return new Prop(scope.allVars, key, key in scope.const, key in scope.globals, true); } set(key: string, val: unknown) { @@ -370,35 +371,71 @@ export class Scope { if (prop.context === undefined) { throw new ReferenceError(`Variable '${key}' was not declared.`); } + if (prop.context === null) { + throw new TypeError(`Cannot set properties of null, (setting '${key}')`); + } if (prop.isConst) { throw new TypeError(`Cannot assign to const variable '${key}'`); } if (prop.isGlobal) { throw new SandboxError(`Cannot override global variable '${key}'`); } - if (!(prop.context instanceof Object)) throw new SandboxError('Scope is not an object'); - prop.context[prop.prop] = val; + (prop.context as any)[prop.prop] = val; return prop; } + getWhereValScope(key: string, isThis = false): Scope|null { + if (isThis) { + if (this.functionThis !== undefined) { + return this; + } else { + return this.parent?.getWhereValScope(key, isThis) || null; + } + } + if (key in this.allVars) { + return this; + } + return this.parent?.getWhereValScope(key, isThis) || null; + } + + getWhereVarScope(key: string, localScope = false): Scope { + if (key in this.allVars) { + return this; + } + if (this.parent === null || localScope || this.functionThis !== undefined) { + return this; + } + return this.parent.getWhereVarScope(key, localScope); + } + declare(key: string, type: VarType, value: unknown = undefined, isGlobal = false): Prop { if (key === 'this') throw new SyntaxError('"this" cannot be declared'); if (reservedWords.has(key)) throw new SyntaxError("Unexepected token '" + key + "'"); - if (type === 'var' && this.functionThis === undefined && this.parent !== null) { - return this.parent.declare(key, type, value, isGlobal); - } else if ( - (this[type].hasOwnProperty(key) && type !== 'const' && !this.globals.hasOwnProperty(key)) || - !(key in this.allVars) - ) { - if (isGlobal) { - this.globals[key] = true; + const existingScope = this.getWhereVarScope(key, type !== VarType.var); + if (type === VarType.var) { + if(existingScope.var[key]) { + existingScope.allVars[key] = value; + if (!isGlobal) { + delete existingScope.globals[key]; + } else { + existingScope.globals[key] = true; + } + return new Prop(existingScope.allVars, key, false, existingScope.globals[key], true); + } else if (key in existingScope.allVars) { + throw new SyntaxError(`Identifier '${key}' has already been declared`); } - this[type][key] = true; - this.allVars[key] = value; - } else { - throw new SandboxError(`Identifier '${key}' has already been declared`); } - return new Prop(this.allVars, key, this.const.hasOwnProperty(key), isGlobal); + if (key in existingScope.allVars) { + throw new SyntaxError(`Identifier '${key}' has already been declared`); + } + + if (isGlobal) { + existingScope.globals[key] = true; + } + existingScope[type][key] = true; + existingScope.allVars[key] = value; + + return new Prop(this.allVars, key, type === VarType.const, isGlobal, true); } } @@ -518,19 +555,23 @@ export const enum LispType { export class Prop { constructor( - public context: Unknown, - public prop: string, + public context: unknown, + public prop: PropertyKey, public isConst = false, public isGlobal = false, public isVariable = false ) {} get<T = unknown>(context: IExecContext): T { const ctx = this.context; - if (ctx === undefined) throw new ReferenceError(`${this.prop} is not defined`); + if (ctx === undefined) throw new ReferenceError(`${this.prop.toString()} is not defined`); if (ctx === null) - throw new TypeError(`Cannot read properties of null, (reading '${this.prop}')`); - context.getSubscriptions.forEach((cb) => cb(ctx, this.prop)); + throw new TypeError(`Cannot read properties of null, (reading '${this.prop.toString()}')`); + context.getSubscriptions.forEach((cb) => cb(ctx, this.prop.toString())); return (ctx as any)[this.prop] as T; } } + +export function hasOwnProperty(obj: unknown, prop: PropertyKey): boolean { + return Object.prototype.hasOwnProperty.call(obj, prop); +} \ No newline at end of file
test/code.spec.ts+33 −19 modified@@ -1,5 +1,6 @@ 'use strict'; import Sandbox from '../src/Sandbox.js'; +import { LocalScope } from '../src/utils.js'; import tests from './tests.json'; const sandbox = new Sandbox(); @@ -11,7 +12,7 @@ async function run(test, state, isAsync) { try { const c = `${test.code.includes(';') || test.code.startsWith('throw') ? '' : 'return '}${test.code}`; let fn = isAsync ? sandbox.compileAsync(c, true) : sandbox.compile(c, true); - ret = await fn(state, {}).run(); + ret = await fn(state, new LocalScope()).run(); } catch (e) { ret = e; } @@ -39,7 +40,7 @@ async function run(test, state, isAsync) { } function getState() { - return { + const a = { type: 'Sandbox', test: [ (a, b) => { @@ -49,30 +50,43 @@ function getState() { test2: 1, a: { b: { c: 2 } }, }; + + Object.setPrototypeOf(a, LocalScope.prototype); + + return a; } -describe('Positive Tests', () => { + +// Derive unique categories from tests +const categories = [...new Set(tests.map((test) => test.category))].sort(); + +describe('Sandbox Tests', () => { + // return describe('Sync', () => { - let state = getState(); - (async () => { - for (let test of tests) { - it(test.code.substring(0, 100), async () => { - await run(test, state, false); + categories.forEach((category) => { + const categoryTests = tests.filter((test) => test.category === category) + + describe(category, () => { + categoryTests.forEach((test) => { + let state = getState(); + it(test.code.substring(0, 100), async () => { + await run(test, state, false); + }); }); - } - })(); + }); + }); }); - describe('Async', () => { - let state = getState(); - (async () => { - for (let test of tests) { - if (test.code.includes('async')) { + categories.forEach((category) => { + const categoryTests = tests.filter((test) => test.category === category); + + describe(category, () => { + categoryTests.forEach((test) => { + let state = getState(); it(test.code.substring(0, 100), async () => { await run(test, state, true); }); - } - } - })(); + }); + }); + }); }); - });
test/script.js+34 −29 modified@@ -1,5 +1,5 @@ import Sandbox from '../dist/Sandbox.js'; - +const LocalScope = Sandbox.LocalScope; window['Sandbox'] = Sandbox; const exec = async () => { const tests = await testsPromise; @@ -14,33 +14,6 @@ const exec = async () => { let sandbox = new Sandbox({ prototypeWhitelist, globals, executionQuota: 100000n }); window.sandbox = sandbox; - let state = { - type: 'eval', - test: [ - (a, b) => { - return 1; - }, - ], - test2: 1, - a: { b: { c: 2 } }, - Object, - Math, - Date, - Array, - lodash, - undefined, - }; - - let state2 = { - type: 'Sandbox', - test: [ - (a, b) => { - return 1; - }, - ], - test2: 1, - a: { b: { c: 2 } }, - }; let validate = (value, compare) => { if (compare === 'error') { @@ -75,6 +48,38 @@ const exec = async () => { let totalExecuteNative = 0; let totalExecuteSandbox = 0; for (let test of tests) { + let state = { + type: 'eval', + test: [ + (a, b) => { + return 1; + }, + ], + test2: 1, + a: { b: { c: 2 } }, + Object, + Math, + Date, + Array, + lodash, + undefined, + NaN, + Error + }; + + let state2 = { + type: 'Sandbox', + test: [ + (a, b) => { + return 1; + }, + ], + test2: 1, + a: { b: { c: 2 } }, + }; + + Object.setPrototypeOf(state2, LocalScope.prototype); + // return; bypassed = false; let tr = document.createElement('tr'); @@ -101,7 +106,7 @@ const exec = async () => { let fn = isAsync ? sandbox.compileAsync(c) : sandbox.compile(c); totalCompileSandbox += performance.now() - time; time = performance.now(); - ret = await fn(state2, {}).run(); + ret = await fn(state2, new LocalScope()).run(); totalExecuteSandbox += performance.now() - time; } catch (e) { console.log('sandbox error', e);
test/tests.json+789 −339 modified@@ -2,1502 +2,1952 @@ { "code": "`${type}`", "evalExpect": "eval", - "safeExpect": "Sandbox" + "safeExpect": "Sandbox", + "category": "Data Types" + }, + { + "code": "globalThis.constructor.name", + "evalExpect": "Window", + "safeExpect": "SandboxGlobal", + "category": "Security" + }, + { + "code": "this.constructor.name", + "evalExpect": "Window", + "safeExpect": "SandboxGlobal", + "category": "Security" + }, + { + "code": "eval.name", + "evalExpect": "eval", + "safeExpect": "sandboxEval", + "category": "Security" + }, + { + "code": "Function.name", + "evalExpect": "Function", + "safeExpect": "SandboxFunction", + "category": "Security" }, { "code": "test2", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Data Types" }, { "code": "a.b.c", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Objects & Arrays" }, { "code": "1+1", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Arithmetic Operators" }, { "code": "[test2, 2]", - "evalExpect": [1, 2], - "safeExpect": [1, 2] + "evalExpect": [ + 1, + 2 + ], + "safeExpect": [ + 1, + 2 + ], + "category": "Objects & Arrays" }, { "code": "{\"aa\": test[0](), b: test2 * 3}", - "evalExpect": { "aa": 1, "b": 3 }, - "safeExpect": { "aa": 1, "b": 3 } + "evalExpect": { + "aa": 1, + "b": 3 + }, + "safeExpect": { + "aa": 1, + "b": 3 + }, + "category": "Objects & Arrays" }, { "code": "test.b = test2 - test[0]() - test[0]()", "evalExpect": -1, - "safeExpect": -1 + "safeExpect": -1, + "category": "Assignment Operators" }, { "code": "1 * 2 + 3 * (4 + 5) * 6", "evalExpect": 164, - "safeExpect": 164 + "safeExpect": 164, + "category": "Arithmetic Operators" }, { "code": "(test2 * (2 + 3 * (4 + 5))) * 6", "evalExpect": 174, - "safeExpect": 174 + "safeExpect": 174, + "category": "Arithmetic Operators" }, { "code": "1+2*4/5-6+7/8 % 9+10-11-12/13*14", "evalExpect": -16.448076923076925, - "safeExpect": -16.448076923076925 + "safeExpect": -16.448076923076925, + "category": "Arithmetic Operators" }, { "code": "2.2204460492503130808472633361816E-16", - "evalExpect": 2.2204460492503130808472633361816e-16, - "safeExpect": 2.2204460492503130808472633361816e-16 + "evalExpect": 2.220446049250313e-16, + "safeExpect": 2.220446049250313e-16, + "category": "Data Types" }, { "code": "test[test2] ? true : false ? 'not ok' : 'ok'", "evalExpect": "ok", - "safeExpect": "ok" + "safeExpect": "ok", + "category": "Conditionals" }, { "code": "let ok = true; false ? ok = false : ok; return ok", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Conditionals" }, { "code": "\"test2\"", "evalExpect": "test2", - "safeExpect": "test2" + "safeExpect": "test2", + "category": "Data Types" }, { "code": "`test2 is ${`also ${test2}`}`", "evalExpect": "test2 is also 1", - "safeExpect": "test2 is also 1" + "safeExpect": "test2 is also 1", + "category": "Data Types" }, { "code": "\"\\\\\"", "evalExpect": "\\", - "safeExpect": "\\" + "safeExpect": "\\", + "category": "Data Types" }, { "code": "{\"\\\\\":\"\\\\\"}", - "evalExpect": { "\\": "\\" }, - "safeExpect": { "\\": "\\" } + "evalExpect": { + "\\": "\\" + }, + "safeExpect": { + "\\": "\\" + }, + "category": "Objects & Arrays" }, { "code": "`\\\\$$\\${${`\\\\\\`${'ok'}`}\\\\}`", "evalExpect": "\\$$${\\`ok\\}", - "safeExpect": "\\$$${\\`ok\\}" + "safeExpect": "\\$$${\\`ok\\}", + "category": "Data Types" }, { "code": "[\"\\\\\", \"\\xd9\", \"\\n\", \"\\r\", \"\\u2028\", \"\\u2029\"]", - "evalExpect": ["\\", "Ù", "\n", "\r", "\u2028", "\u2029"], - "safeExpect": ["\\", "Ù", "\n", "\r", "\u2028", "\u2029"] + "evalExpect": [ + "\\", + "Ù", + "\n", + "\r", + "\u2028", + "\u2029" + ], + "safeExpect": [ + "\\", + "Ù", + "\n", + "\r", + "\u2028", + "\u2029" + ], + "category": "Data Types" }, { "code": "bypassed=1", "evalExpect": "error", - "safeExpect": "/bypassed is not defined/" + "safeExpect": "/bypassed is not defined/", + "category": "Security" }, { "code": "`${`${bypassed=1}`}`", "evalExpect": "error", - "safeExpect": "/bypassed is not defined/" + "safeExpect": "/bypassed is not defined/", + "category": "Security" }, { "code": "[].filter.constructor(\"return 'ok'\")()", "evalExpect": "ok", - "safeExpect": "ok" + "safeExpect": "ok", + "category": "Security" }, { "code": "[].filter.constructor('return bypassed')()", "evalExpect": false, - "safeExpect": "/bypassed is not defined/" + "safeExpect": "/bypassed is not defined/", + "category": "Security" }, { "code": "[].filter.constructor('return bypassed=1')()", "evalExpect": "bypassed", - "safeExpect": "/bypassed is not defined/" + "safeExpect": "/bypassed is not defined/", + "category": "Security" + }, + { + "code": "[].filter.constructor.apply(null,['return bypassed=1'])()", + "evalExpect": "bypassed", + "safeExpect": "/bypassed is not defined/", + "category": "Security" }, { "code": "[].filter.constructor('return this.bypassed=1')()", "evalExpect": "bypassed", - "safeExpect": 1 + "safeExpect": 1, + "category": "Security" }, { "code": "[].filter.constructor('return this.constructor.name')()", "evalExpect": "Window", - "safeExpect": "SandboxGlobal" + "safeExpect": "SandboxGlobal", + "category": "Security" + }, + { + "code": "[].filter.constructor.call(this,'bypassed = 1')()", + "evalExpect": "bypassed", + "safeExpect": "/bypassed is not defined/", + "category": "Security" }, { "code": "[+!+[]]+[]", "evalExpect": "1", - "safeExpect": "1" + "safeExpect": "1", + "category": "Security" }, { "code": "[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((+(+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(+![]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+[+[]])+(+[![]]+[+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])])[+!+[]+[+[]]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(+![]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[+!+[]]]+[+!+[]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+!+[]]]+([][[]]+[])[+[]]+([][[]]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(+![]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]])()([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(+![]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]])()(([]+[])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+[]])[+[]]+[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(+![]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+[+[]])))()", "evalExpect": "bypassed", "safeExpect": "/bypassed is not defined/", - "ignoreTime": true + "ignoreTime": true, + "category": "Security" }, { - "code": "Function.name", + "code": "Object.entries(this).find(a => a[0] === \"Function\")?.at(1)(\"bypassed=1\")()", + "evalExpect": "bypassed", + "safeExpect": "/bypassed is not defined/", + "category": "Security" + }, + { + "code": "Object.values(this).find(a => {if(a?.name?.endsWith(\"Function\")) return a.call(\"\", \"return bypassed = 1\")()})", + "evalExpect": "bypassed", + "safeExpect": "/bypassed is not defined/", + "category": "Security" + }, + { + "code": "(async () => (await (async () => Function('return Object.values(this).at(Object.values(this).findIndex(a => {if(a?.name?.endsWith(\"Function\")) return true}))')())()).name)()", "evalExpect": "Function", - "safeExpect": "SandboxFunction" + "safeExpect": "SandboxFunction", + "category": "Security" }, { "code": "[].constructor.constructor.constructor.name", "evalExpect": "Function", - "safeExpect": "SandboxFunction" + "safeExpect": "SandboxFunction", + "category": "Security" }, { "code": "(()=>{}).constructor.name", "evalExpect": "Function", - "safeExpect": "SandboxFunction" + "safeExpect": "SandboxFunction", + "category": "Security" }, { "code": "(async ()=>{}).constructor.name", "evalExpect": "AsyncFunction", - "safeExpect": "SandboxAsyncFunction" + "safeExpect": "SandboxAsyncFunction", + "category": "Security" }, { "code": "[].anything = 1", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Security" }, { "code": "[].filter = 1", "evalExpect": 1, - "safeExpect": "/Override prototype property 'filter' not allowed/" + "safeExpect": "/Override prototype property 'filter' not allowed/", + "category": "Security" }, { "code": "[].constructor.prototype.flatMap = 1", "evalExpect": 1, - "safeExpect": "/Cannot assign property 'flatMap' of a global object/" + "safeExpect": "/Cannot assign property 'flatMap' of a global object/", + "category": "Security" }, { "code": "[].__proto__.flatMap = 1", "evalExpect": 1, - "safeExpect": "/Method or property access not permitted/" + "safeExpect": "/Method or property access not permitted/", + "category": "Security" + }, + { + "code": "let p; p = [].constructor.prototype; p.flatMap = 2; return [].flatMap;", + "evalExpect": 2, + "safeExpect": "/Cannot assign property 'flatMap' of a global object/", + "category": "Security" }, { "code": "Object.anything = 1", "evalExpect": "error", - "safeExpect": "/Cannot assign property 'anything' of a global object/" + "safeExpect": "/Cannot assign property 'anything' of a global object/", + "category": "Security" }, { "code": "Object.freeze = 1", "evalExpect": "error", - "safeExpect": "/Static method or property access not permitted/" + "safeExpect": "/Static method or property access not permitted/", + "category": "Security" }, { "code": "{}.constructor.anything1 = 1", "evalExpect": 1, - "safeExpect": "/Cannot assign property 'anything1' of a global object/" + "safeExpect": "/Cannot assign property 'anything1' of a global object/", + "category": "Security" }, { "code": "{}.constructor.freeze = 1", "evalExpect": 1, - "safeExpect": "/Static method or property access not permitted/" + "safeExpect": "/Static method or property access not permitted/", + "category": "Security" }, { "code": "{}.constructor.constructor.anything = 1", "evalExpect": 1, - "safeExpect": "/Cannot assign property 'anything' of a global object/" + "safeExpect": "/Cannot assign property 'anything' of a global object/", + "category": "Security" }, { "code": "(() => {}).anything = 1", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Security" }, { "code": "Object.name", "evalExpect": "error", - "safeExpect": "Object" + "safeExpect": "Object", + "category": "Security" }, { "code": "Object.keys({a:1})", - "evalExpect": ["a"], - "safeExpect": ["a"] + "evalExpect": [ + "a" + ], + "safeExpect": [ + "a" + ], + "category": "Objects & Arrays" }, { "code": "Object.assign(Object, {}) || 'ok'", "evalExpect": "error", - "safeExpect": "/Static method or property access not permitted/" + "safeExpect": "/Static method or property access not permitted/", + "category": "Security" }, { "code": "({}).__defineGetter__('a', () => 1 ) || 'ok'", - "evalExpect": "error", - "safeExpect": "/Method or property access not permitted/" + "evalExpect": "ok", + "safeExpect": "/Method or property access not permitted/", + "category": "Security" }, { "code": "(() => {}).__defineGetter__('a', () => 1 ) || 'ok'", - "evalExpect": "error", - "safeExpect": "/Method or property access not permitted/" + "evalExpect": "ok", + "safeExpect": "/Method or property access not permitted/", + "category": "Security" }, { "code": "({}).toString.__defineGetter__('a', () => 1 ) || 'ok'", - "evalExpect": "error", - "safeExpect": "/Method or property access not permitted/" + "evalExpect": "ok", + "safeExpect": "/Method or property access not permitted/", + "category": "Security" }, { "code": "!test2", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Logical Operators" }, { "code": "!!test2", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Logical Operators" }, { "code": "!({}).a?.a", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Logical Operators" }, { "code": "!({a: {a: 1}}).a?.a", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Logical Operators" }, { "code": "!({a: {a: 0}}).a?.a", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Logical Operators" }, { "code": "!({}).a ? true : false", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Logical Operators" }, { "code": "test2++", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Assignment Operators" }, { "code": "++test2", - "evalExpect": 3, - "safeExpect": 3 + "evalExpect": 2, + "safeExpect": 2, + "category": "Assignment Operators" }, { "code": "test2 = 1", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Assignment Operators" }, { "code": "test2 += 1", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Assignment Operators" }, { "code": "test2 -= 1", - "evalExpect": 1, - "safeExpect": 1 + "evalExpect": 0, + "safeExpect": 0, + "category": "Assignment Operators" }, { "code": "test2 *= 2", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Assignment Operators" }, { "code": "test2 /= 2", - "evalExpect": 1, - "safeExpect": 1 + "evalExpect": 0.5, + "safeExpect": 0.5, + "category": "Assignment Operators" }, { "code": "~test2", "evalExpect": -2, - "safeExpect": -2 + "safeExpect": -2, + "category": "Bitwise Operators" }, { "code": "test2 **= 0", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Arithmetic Operators" }, { "code": "2 ** 3", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Arithmetic Operators" }, { "code": "10 ** 0", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Arithmetic Operators" }, { "code": "2 ** 10", "evalExpect": 1024, - "safeExpect": 1024 + "safeExpect": 1024, + "category": "Arithmetic Operators" }, { "code": "3 ** 2 ** 2", "evalExpect": 81, - "safeExpect": 81 + "safeExpect": 81, + "category": "Arithmetic Operators" }, { "code": "let test3 = 8; test3 >>>= 2; return test3", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Bitwise Operators" }, { "code": "let test4 = -8; test4 >>>= 2; return test4", "evalExpect": 1073741822, - "safeExpect": 1073741822 + "safeExpect": 1073741822, + "category": "Bitwise Operators" }, { "code": "let test5 = 16; test5 >>>= 1; return test5", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Bitwise Operators" }, { "code": "let test6 = -1; test6 >>>= 0; return test6", "evalExpect": 4294967295, - "safeExpect": 4294967295 + "safeExpect": 4294967295, + "category": "Bitwise Operators" }, { "code": "test2 %= 1", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Arithmetic Operators" }, { "code": "test2 ^= 1", - "evalExpect": 1, - "safeExpect": 1 + "evalExpect": 0, + "safeExpect": 0, + "category": "Bitwise Operators" }, { "code": "test2 &= 3", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Bitwise Operators" }, { "code": "test2 |= 2", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Bitwise Operators" }, { - "code": "test2 == '3'", + "code": "test2 == '1'", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Comparison Operators" }, { - "code": "test2 === '3'", + "code": "test2 === '1'", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Comparison Operators" }, { - "code": "test2 != '3'", + "code": "test2 != '1'", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Comparison Operators" }, { - "code": "test2 !== '3'", + "code": "test2 !== '1'", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Comparison Operators" }, { - "code": "test2 < 3", + "code": "test2 < 1", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Comparison Operators" }, { - "code": "test2 > 3", + "code": "test2 > 1", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Comparison Operators" }, { - "code": "test2 >= 3", + "code": "test2 >= 1", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Comparison Operators" }, { - "code": "test2 <= 3", + "code": "test2 <= 1", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Comparison Operators" }, { "code": "test2 && true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Logical Operators" }, { - "code": "test2 !== '3' && false", + "code": "test2 !== '1' && false", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Operator Precedence" }, { "code": "true && true || false", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "true || true && false", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "1 && 2 == 1", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Operator Precedence" }, { "code": "1 + 2 === 1 + 2 === 1 && 2", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Operator Precedence" }, { "code": "true === 1 && 2", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Operator Precedence" }, { "code": "-1 < 1 && 2 > 1", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "test2 || false", - "evalExpect": 3, - "safeExpect": 3 + "evalExpect": 1, + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "test2 & 1", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Bitwise Operators" }, { "code": "test2 | 4", - "evalExpect": 7, - "safeExpect": 7 + "evalExpect": 5, + "safeExpect": 5, + "category": "Bitwise Operators" }, { "code": "+'1'", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Arithmetic Operators" }, { "code": "-'1'", "evalExpect": -1, - "safeExpect": -1 + "safeExpect": -1, + "category": "Arithmetic Operators" }, { "code": "typeof '1'", "evalExpect": "string", - "safeExpect": "string" + "safeExpect": "string", + "category": "Other Operators" }, { "code": "typeof x === 'undefined'", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Other Operators" }, { "code": "{} instanceof Object", "evalExpect": "error", - "safeExpect": true + "safeExpect": true, + "category": "Other Operators" }, { "code": "{} instanceof undefined", "evalExpect": "error", - "safeExpect": "error" + "safeExpect": "error", + "category": "Other Operators" }, { "code": "var i = 1; return i + 1", "evalExpect": "error", - "safeExpect": 2 + "safeExpect": 2, + "category": "Arithmetic Operators" }, { "code": "let j = 1; return j + 1", "evalExpect": "error", - "safeExpect": 2 + "safeExpect": 2, + "category": "Arithmetic Operators" }, { "code": "const k = 1; return k + 1", "evalExpect": "error", - "safeExpect": 2 + "safeExpect": 2, + "category": "Arithmetic Operators" }, { "code": "const l = 1; return l = 2", "evalExpect": "error", - "safeExpect": "/Cannot set value to const variable/" + "safeExpect": "/Assignment to constant variable/", + "category": "Assignment Operators" }, { "code": "[1, ...[2, [test2, 4]], 5]", - "evalExpect": [1, 2, [3, 4], 5], - "safeExpect": [1, 2, [3, 4], 5] + "evalExpect": [ + 1, + 2, + [ + 1, + 4 + ], + 5 + ], + "safeExpect": [ + 1, + 2, + [ + 1, + 4 + ], + 5 + ], + "category": "Objects & Arrays" }, { "code": "let a = null; let b = [a?.a]; return b[0] === undefined && b.length", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Complex Expressions" }, { "code": "{a: 1, ...{b: 2, c: {d: test2,}}, e: 5}", - "evalExpect": { "a": 1, "b": 2, "c": { "d": 3 }, "e": 5 }, - "safeExpect": { "a": 1, "b": 2, "c": { "d": 3 }, "e": 5 } + "evalExpect": { + "a": 1, + "b": 2, + "c": { + "d": 1 + }, + "e": 5 + }, + "safeExpect": { + "a": 1, + "b": 2, + "c": { + "d": 1 + }, + "e": 5 + }, + "category": "Complex Expressions" }, { "code": "{a: 1,b: 2, /*,*/}", - "evalExpect": { "a": 1, "b": 2 }, - "safeExpect": { "a": 1, "b": 2 } + "evalExpect": { + "a": 1, + "b": 2 + }, + "safeExpect": { + "a": 1, + "b": 2 + }, + "category": "Complex Expressions" }, { "code": "'a' in {a: 1}", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Other Operators" }, { "code": "1,2", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Other Operators" }, { "code": "test2 = 1,(() => 2)(),test2", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Complex Expressions" }, { "code": "delete 1", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Assignment Operators" }, { "code": "let a = {b: 1}; return delete a.b", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Assignment Operators" }, { "code": "let b = {a: 1}; return delete b", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Assignment Operators" }, { "code": "throw new Error('test')", "evalExpect": "error", - "safeExpect": "/test/" + "safeExpect": "/test/", + "category": "Error Handling" }, { "code": "((a) => {return a + 1})(1)", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Functions" }, { "code": "(() => '1' + (() => '22')())()", "evalExpect": "122", - "safeExpect": "122" + "safeExpect": "122", + "category": "Functions" }, { "code": "(a => a + 1)(1)", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Functions" }, { "code": "function f(a) { return a + 1 } return f(2);", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Functions" }, { "code": "(function () { return 1 })()", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Functions" }, { "code": "let list = [0, 1]; return list.sort((a, b) => (a < b) ? 1 : -1)", - "evalExpect": [1, 0], - "safeExpect": [1, 0] + "evalExpect": [ + 1, + 0 + ], + "safeExpect": [ + 1, + 0 + ], + "category": "Functions" }, { "code": "let y = {a: 1, b(x) {return this.a + x}}; return y.b(2)", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Functions" }, { "code": "let y = {a: '2', b() {return this.a = '1'}}; y.b(); return y.a", "evalExpect": "1", - "safeExpect": "1" + "safeExpect": "1", + "category": "Functions" }, { "code": "[0,1].filter((...args) => args[1])", - "evalExpect": [1], - "safeExpect": [1] + "evalExpect": [ + 1 + ], + "safeExpect": [ + 1 + ], + "category": "Functions" }, { "code": "Math.pow(...[2, 2])", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Functions" }, { "code": "let x; for(let i = 0; i < 2; i++){ x = i }; return x;", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Loops" }, { "code": "let x; for(let i = 0; i < 2; i++){ x = i; break; }; return x;", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Loops" }, { "code": "let x; for(let i = 0; i < 2; i++){ x = i; continue; x++ }; return x;", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Loops" }, { "code": "break;", "evalExpect": "error", - "safeExpect": "error" + "safeExpect": "error", + "category": "Loops" }, { "code": "continue;", "evalExpect": "error", - "safeExpect": "error" + "safeExpect": "error", + "category": "Loops" }, { "code": "let sum = 0; for (let i = 0; i < 5; i++) { if (i === 2) continue; sum += i; }; return sum;", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Loops" }, { "code": "let sum = 0; for (let i = 0; i < 10; i++) { if (i === 3) break; sum += i; }; return sum;", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Loops" }, { "code": "let sum = 0; for (let i = 0; i < 5; i++) { if (i > 0) { if (i === 2) continue; } sum += i; }; return sum;", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Loops" }, { "code": "let sum = 0; for (let i = 0; i < 5; i++) { if (i === 2) { continue; } sum += i; }; return sum;", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Loops" }, { "code": "let x = 0; while (x < 5) { x++; if (x === 3) continue; if (x === 4) break; }; return x;", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Loops" }, { "code": "let sum = 0; for (let i = 0; i < 5; i++) { sum += i === 2 ? continue : i; }; return sum;", "evalExpect": "error", - "safeExpect": "error" + "safeExpect": "error", + "category": "Loops" }, { "code": "let x = 2; while(--x){ }; return x;", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Loops" }, { "code": "let x = 1; do {x++} while(x < 1); return x;", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Loops" }, { "code": "for(let i of [1,2]){ return i };", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Loops" }, { "code": "let arr = [1,2]; for(let i of arr){ return i };", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Loops" }, { "code": "for(let i in [1,2]){ return i };", "evalExpect": "0", - "safeExpect": "0" + "safeExpect": "0", + "category": "Loops" }, { "code": "const a = () => {return 1}; const b = () => {return 2}; return (() => a() + b())()", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Complex Expressions" }, { "code": "let i = 1; {let j = 1; i += j;}; return i", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Loops" }, { "code": "if (true) { return true; } else return false", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Conditionals" }, { "code": "let a = false; if (a) { return false; } else return true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Conditionals" }, { "code": "let a = null; if (a?.a) { return false; } else return true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Conditionals" }, { "code": "if (false) return true; else if (false) {return true} else return false", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Conditionals" }, { "code": "if (false) { return true; } else return false", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Conditionals" }, { "code": "if (false) return true; return false", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Conditionals" }, { "code": "if (true) {\n if (false)\n if (true)\n if (false)\n return 1\n else if (true)\n return 2\n else\n return 3\n else\n return 4\n else if (true)\n if (false)\n return 5\n else if (true)\n return 6\n else\n return 7\n else\n return 8\n} else if (true)\n return 9;", "evalExpect": 6, - "safeExpect": 6 + "safeExpect": 6, + "category": "Conditionals" }, { "code": "try {a.x.a} catch {return 1}; return 2", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Error Handling" }, { "code": "void 2 == '2'", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Other Operators" }, { - "code": "void (2 == '2')" + "code": "void (2 == '2')", + "category": "Other Operators" }, { "code": "(async () => 1)()", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Async/Await" }, { "code": "let i = 0; (async () => i += 1)(); return i;", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Async/Await" }, { "code": "(async () => await 1)()", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Async/Await" }, { "code": "(async () => await (async () => 1)())()", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Async/Await" }, { "code": "let p = (async () => 1)(); return (async () => 'i = ' + await p)()", "evalExpect": "i = 1", - "safeExpect": "i = 1" + "safeExpect": "i = 1", + "category": "Async/Await" }, { "code": "new Date(0).toISOString()", "evalExpect": "1970-01-01T00:00:00.000Z", - "safeExpect": "1970-01-01T00:00:00.000Z" + "safeExpect": "1970-01-01T00:00:00.000Z", + "category": "Other Operators" }, { "code": "/* 2 */ 1", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Comments" }, { "code": "1 // 2", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Comments" }, { "code": "/* 2 */ (() => /* 3 */ 1)() // 4", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Comments" }, { "code": "/a/.test('a')", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Data Types" }, { "code": "/a/i.test('A')", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Data Types" }, { "code": "let reg = /a/g; reg.exec('aaa'); return reg.exec('aaa').index", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Data Types" }, { "code": "let a = 1; let b = 2; switch(1) {case a: b = 1; case b: return b; default: return 0;}", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Switch" }, { "code": "let b = 1; switch(1) {case 1: b = 2; break; case 2: b = 3; default: b = 4}; return b", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Switch" }, { "code": "let b = 1; switch(3) {case 1: b = 2; break; case 2: b = 3; default: b = 4}; return b", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Switch" }, { "code": "let b = 1; switch(1) {case 1:b = 2; case 2: b = 3; default: b = 4}; return b", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Switch" }, { "code": "let a = 1; switch(a) {case 1: return 2}; return 1", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Switch" }, { - "code": "({}).a?.toString()" + "code": "({}).a?.toString()", + "category": "Complex Expressions" }, { "code": "({}).a?.toSring() + ({}).b?.toString()", "evalExpect": "NaN", - "safeExpect": "NaN" - }, - { - "code": "({}).a?.toString().toString()" + "safeExpect": "NaN", + "category": "Complex Expressions" }, { "code": "({})['b']?.toString() === undefined", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Complex Expressions" }, { "code": "({}).c?.()() ? 1 : 2", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Complex Expressions" }, { "code": "(1n + 0x1n).toString()", "evalExpect": "2", - "safeExpect": "2" + "safeExpect": "2", + "category": "Data Types" }, { "code": "0b1010", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Data Types" }, { "code": "0B1111", "evalExpect": 15, - "safeExpect": 15 + "safeExpect": 15, + "category": "Data Types" }, { "code": "0b1010n.toString()", "evalExpect": "10", - "safeExpect": "10" + "safeExpect": "10", + "category": "Data Types" }, { "code": "0b1_000", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Data Types" + }, + { + "code": "1_000", + "evalExpect": 1000, + "safeExpect": 1000, + "category": "Data Types" }, { "code": "0b0", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Data Types" }, { "code": "0o17", "evalExpect": 15, - "safeExpect": 15 + "safeExpect": 15, + "category": "Data Types" }, { "code": "0O77", "evalExpect": 63, - "safeExpect": 63 + "safeExpect": 63, + "category": "Data Types" }, { "code": "0o17n.toString()", "evalExpect": "15", - "safeExpect": "15" + "safeExpect": "15", + "category": "Data Types" }, { "code": "0o7_777", "evalExpect": 4095, - "safeExpect": 4095 + "safeExpect": 4095, + "category": "Data Types" }, { "code": "0o0", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Data Types" }, { "code": "0b1010 + 0o17", "evalExpect": 25, - "safeExpect": 25 + "safeExpect": 25, + "category": "Data Types" }, { "code": "(0b1010n + 0o17n).toString()", "evalExpect": "25", - "safeExpect": "25" + "safeExpect": "25", + "category": "Data Types" }, { - "code": "function plus(n,u){return n+u};function minus(n,u){return n-u};var a=plus(1,10);return minus(a,5);", + "code": "function plus(n,u){return n+u};function minus(n,u){return n-u};var added=plus(1,10);return minus(added,5);", "evalExpect": 6, - "safeExpect": 6 + "safeExpect": 6, + "category": "Complex Expressions" }, { "code": "function LinkedListNode(e){this.value=e,this.next=null}function reverse(e){let n,t,r=e;for(;r;)t=r.next,r.next=n,n=r,r=t;return n}function reverse(e){if(!e||!e.next)return e;let n=reverse(e.next);return e.next.next=e,e.next=null,n} let l1 = new LinkedListNode(1); l1.next = new LinkedListNode(2); return reverse(l1);", - "evalExpect": { "value": 2, "next": { "value": 1, "next": null } }, - "safeExpect": { "value": 2, "next": { "value": 1, "next": null } } + "evalExpect": { + "value": 2, + "next": { + "value": 1, + "next": null + } + }, + "safeExpect": { + "value": 2, + "next": { + "value": 1, + "next": null + } + }, + "category": "Complex Expressions" }, { "code": "let c = 0; for (let i = 0; i < 10; i++) {c++} return c", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Loops" }, { "code": "null ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "undefined ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "0 ?? 'default'", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Logical Operators" }, { "code": "'' ?? 'default'", "evalExpect": "", - "safeExpect": "" + "safeExpect": "", + "category": "Logical Operators" }, { "code": "false ?? 'default'", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Logical Operators" }, { "code": "NaN ?? 'default'", "evalExpect": "NaN", - "safeExpect": "NaN" + "safeExpect": "NaN", + "category": "Logical Operators" }, { "code": "null ?? null ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "1 ?? 2 ?? 3", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "null ?? 2 ?? 3", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Logical Operators" }, { "code": "({a: 1}).a ?? 'default'", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "({}).a ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "({}).a?.b ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "null?.a ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "({a: {b: 1}}).a?.b ?? 'default'", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "({a: null}).a?.b ?? 'default'", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "null ?? 1 || 2", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "0 || null ?? 1", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "(null ?? false) || 'fallback'", "evalExpect": "fallback", - "safeExpect": "fallback" + "safeExpect": "fallback", + "category": "Logical Operators" }, { "code": "null ?? 0 || 'fallback'", - "evalExpect": "fallback", - "safeExpect": "fallback" + "evalExpect": "error", + "safeExpect": "fallback", + "category": "Logical Operators" }, { "code": "true && null ?? 'default'", - "evalExpect": "default", - "safeExpect": "default" + "evalExpect": "error", + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "let x = 0; (1 ?? (x = 1)); return x", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Logical Operators" }, { "code": "let x = 0; (null ?? (x = 1)); return x", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "let x = 0; (undefined ?? (x = 1)); return x", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "let x = 0; (0 ?? (x = 1)); return x", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Logical Operators" }, { "code": "let x = 0; ('' ?? (x = 1)); return x", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Logical Operators" }, { "code": "let x = 0; (false ?? (x = 1)); return x", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Logical Operators" }, { "code": "({a: null ?? 'default'}).a", "evalExpect": "default", - "safeExpect": "default" + "safeExpect": "default", + "category": "Logical Operators" }, { "code": "[null ?? 1, 2 ?? 3][0]", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Logical Operators" }, { "code": "[null ?? 1, undefined ?? 3][1]", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Logical Operators" }, { "code": "let outputs = {}; return (outputs['classify']?.intent ?? null)", "evalExpect": null, - "safeExpect": null + "safeExpect": null, + "category": "Logical Operators" }, { "code": "let outputs = {classify: {intent: 'greeting'}}; return (outputs['classify']?.intent ?? null)", "evalExpect": "greeting", - "safeExpect": "greeting" + "safeExpect": "greeting", + "category": "Logical Operators" }, { "code": "let x = 5; x <<= 1; return x", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Assignment Operators" }, { "code": "let x = 8; x >>= 1; return x", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Assignment Operators" }, { "code": "outer: for(let i = 0; i < 3; i++) { for(let j = 0; j < 3; j++) { if(i === 1 && j === 1) break outer; } } return 'done'", "evalExpect": "done", - "safeExpect": "done" + "safeExpect": "done", + "category": "Loops" }, { "code": "const f = function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); }; return f(5)", "evalExpect": 120, - "safeExpect": 120 + "safeExpect": 120, + "category": "Complex Expressions" }, { "code": "!5 > 3", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Operator Precedence" }, { "code": "!5 < 3", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "!0 === true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "!false && true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "!true || true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "!false && false || true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "5 > 3 > 1", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Operator Precedence" }, { "code": "1 < 2 < 3", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "5 > 3 === true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Operator Precedence" }, { "code": "1 | 2 && 3", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Operator Precedence" }, { "code": "true && 1 | 2", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Operator Precedence" }, { "code": "4 & 5 || 0", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Operator Precedence" }, { "code": "2 + 3 << 1", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Operator Precedence" }, { "code": "8 >> 1 + 1", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Operator Precedence" }, { "code": "1 << 2 * 2", "evalExpect": 16, - "safeExpect": 16 + "safeExpect": 16, + "category": "Operator Precedence" }, { "code": "5 & 3 | 2", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Operator Precedence" }, { "code": "8 | 4 & 2", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Operator Precedence" }, { "code": "15 ^ 3 & 7", "evalExpect": 12, - "safeExpect": 12 + "safeExpect": 12, + "category": "Operator Precedence" }, { "code": "typeof 5 + \"2\"", "evalExpect": "number2", - "safeExpect": "number2" + "safeExpect": "number2", + "category": "Other Operators" }, { "code": "typeof (5 + 2)", "evalExpect": "number", - "safeExpect": "number" + "safeExpect": "number", + "category": "Other Operators" }, { "code": "typeof 5 === \"number\"", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Other Operators" }, { "code": "let obj = {a: 1}; delete obj.a; return obj.a === undefined", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Assignment Operators" }, { "code": "delete {a: 1}.a", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Assignment Operators" }, { "code": "void 0 === undefined", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Other Operators" }, { "code": "null?.a + 5", "evalExpect": "NaN", - "safeExpect": "NaN" + "safeExpect": "NaN", + "category": "Arithmetic Operators" }, { "code": "({}).a ?? 10 + 5", "evalExpect": 15, - "safeExpect": 15 + "safeExpect": 15, + "category": "Arithmetic Operators" }, { "code": "null ?? 5 ? \"yes\" : \"no\"", "evalExpect": "yes", - "safeExpect": "yes" + "safeExpect": "yes", + "category": "Logical Operators" }, { "code": "null ?? 0 ? \"yes\" : \"no\"", "evalExpect": "no", - "safeExpect": "no" + "safeExpect": "no", + "category": "Logical Operators" }, { "code": "let x = 5; return x++ + 2", "evalExpect": 7, - "safeExpect": 7 + "safeExpect": 7, + "category": "Arithmetic Operators" }, { "code": "let y = 5; return ++y + 2", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Arithmetic Operators" }, { "code": "+-5", "evalExpect": -5, - "safeExpect": -5 + "safeExpect": -5, + "category": "Arithmetic Operators" }, { "code": "~-1", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Arithmetic Operators" }, { "code": "!~0", "evalExpect": false, - "safeExpect": false + "safeExpect": false, + "category": "Logical Operators" }, { "code": "8 >> 1 >> 1", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Bitwise Operators" }, { "code": "1 << 2 << 1", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Bitwise Operators" }, { "code": "16 >>> 2 >> 1", "evalExpect": 2, - "safeExpect": 2 + "safeExpect": 2, + "category": "Bitwise Operators" }, { "code": "(1, 2) + (3, 4)", "evalExpect": 6, - "safeExpect": 6 + "safeExpect": 6, + "category": "Arithmetic Operators" }, { "code": "return f(); function f() { return 1; }", "evalExpect": 1, - "safeExpect": 1 + "safeExpect": 1, + "category": "Functions" }, { "code": "return add(2, 3); function add(a, b) { return a + b; }", "evalExpect": 5, - "safeExpect": 5 + "safeExpect": 5, + "category": "Functions" }, { "code": "let x = f(); function f() { return 42; } return x;", "evalExpect": 42, - "safeExpect": 42 + "safeExpect": 42, + "category": "Functions" }, { "code": "try { throw new Error('test'); } catch { return 'caught'; }", "evalExpect": "caught", - "safeExpect": "caught" + "safeExpect": "caught", + "category": "Error Handling" }, { "code": "let x = 10; x <<= 2; return x;", "evalExpect": 40, - "safeExpect": 40 + "safeExpect": 40, + "category": "Assignment Operators" }, { "code": "let x = 16; x >>= 2; return x;", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Assignment Operators" }, { "code": "void 5 + 10", "evalExpect": "NaN", - "safeExpect": "NaN" + "safeExpect": "NaN", + "category": "Other Operators" }, { "code": "({a: 10}).a?.toString() + 5", "evalExpect": "105", - "safeExpect": "105" + "safeExpect": "105", + "category": "Complex Expressions" }, { "code": "let z = 5; return --z * 2", "evalExpect": 8, - "safeExpect": 8 + "safeExpect": 8, + "category": "Arithmetic Operators" }, { "code": "let z = 5; return z-- * 2", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Arithmetic Operators" }, { "code": "typeof void 0", "evalExpect": "undefined", - "safeExpect": "undefined" + "safeExpect": "undefined", + "category": "Other Operators" }, { "code": "1 + 2 + 3 + 4", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Arithmetic Operators" }, { "code": "10 - 5 - 2", "evalExpect": 3, - "safeExpect": 3 + "safeExpect": 3, + "category": "Arithmetic Operators" }, { "code": "2 * 3 * 4", "evalExpect": 24, - "safeExpect": 24 + "safeExpect": 24, + "category": "Arithmetic Operators" }, { "code": "100 / 5 / 2", "evalExpect": 10, - "safeExpect": 10 + "safeExpect": 10, + "category": "Arithmetic Operators" }, { "code": "17 % 5 % 2", "evalExpect": 0, - "safeExpect": 0 + "safeExpect": 0, + "category": "Arithmetic Operators" }, { "code": "1 << 1 << 1", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Bitwise Operators" }, { "code": "16 >> 1 >> 1", "evalExpect": 4, - "safeExpect": 4 + "safeExpect": 4, + "category": "Bitwise Operators" }, { "code": "(1 == 1) == true", "evalExpect": true, - "safeExpect": true + "safeExpect": true, + "category": "Assignment Operators" ... [truncated]
TODO.md+6 −13 modified@@ -8,8 +8,13 @@ ### Bugs & Issues Remaining -🐛 **1 bug/limitation found during testing**: +🐛 **Bugs/limitations found during testing**: 1. **Finally blocks** - ❌ NOT IMPLEMENTED - Parsed but not executed (returns undefined) +2. **String literal functions** - ❌ NOT IMPLEMENTED - Tagged template functions not supported +3. **Computed properties** - ❌ NOT IMPLEMENTED - Object/class computed property names not parsed +4. **Unicode identifier escapes** - ❌ NOT IMPLEMENTED - `\uXXXX` in variable names not supported +5. **Labelled loops and switches** - ❌ NOT IMPLEMENTED - Labels for break/continue not supported, but will not give sytnax error +6. **Spec compliant eval** - ❌ NOT FULLY IMPLEMENTED - exact eval implementation is not supported, like last statement result as return, otherwise behaves as argument-less Function invocation --- @@ -417,18 +422,6 @@ All items in this section now have test coverage! - ❌ **Async generators** - Test: `async function* gen() { yield Promise.resolve(1); } const g = gen(); return (await g.next()).value` → `1` -#### String & Number Features -- ❌ **Numeric separators** - - Test: `1_000_000` → `1000000` - -- ❌ **Binary literals (0b)** - ❌ **TESTED AND NOT SUPPORTED** - - Test: `0b1010` → Expected `10` but got parser error: "Unexpected token after number: b: 0b1010" - - Also tested: `0B1111` (uppercase) - same error - -- ❌ **Octal literals (0o)** - ❌ **TESTED AND NOT SUPPORTED** - - Test: `0o17` → Expected `15` but got parser error - - Also tested: `0O77` (uppercase) - same error - #### Other Modern Features - ✅ **Optional catch binding** - ✅ **TESTED AND WORKING** - Test: `try { throw new Error(); } catch { return 1; }` → `1` ✅
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-7x3h-rm86-3342ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25641ghsaADVISORY
- github.com/nyariv/SandboxJS/blob/6103d7147c4666fe48cfda58a4d5f37005b43754/src/executor.tsghsax_refsource_MISCWEB
- github.com/nyariv/SandboxJS/commit/67cb186c41c78c51464f70405504e8ef0a6e43c3ghsax_refsource_MISCWEB
- github.com/nyariv/SandboxJS/security/advisories/GHSA-7x3h-rm86-3342ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.