diff --git a/libs/ast/src/__tests__/rules.spec.ts b/libs/ast/src/__tests__/rules.spec.ts index 89bb367..e91e9a8 100644 --- a/libs/ast/src/__tests__/rules.spec.ts +++ b/libs/ast/src/__tests__/rules.spec.ts @@ -233,6 +233,33 @@ describe('Validation Rules', () => { expect(result.valid).toBe(true); }); + it('should detect duplicate toString where last occurrence wins', async () => { + const rule = new DisallowedIdentifierRule({ disallowed: ['constructor'] }); + const validator = new JSAstValidator([rule]); + + const result = await validator.validate('obj[{toString: () => "safe", toString: () => "constructor"}]', { + rules: { 'disallowed-identifier': true }, + }); + expect(result.valid).toBe(false); + expect(result.issues[0].code).toBe('DISALLOWED_IDENTIFIER'); + expect(result.issues[0].data?.['identifier']).toBe('constructor'); + }); + + it('should detect multi-return getter with disallowed identifier in later return', async () => { + const rule = new DisallowedIdentifierRule({ disallowed: ['constructor'] }); + const validator = new JSAstValidator([rule]); + + const result = await validator.validate( + "obj[{get toString(){ if(true) return () => 'x'; return () => 'constructor' }}]", + { + rules: { 'disallowed-identifier': true }, + }, + ); + expect(result.valid).toBe(false); + expect(result.issues[0].code).toBe('DISALLOWED_IDENTIFIER'); + expect(result.issues[0].data?.['identifier']).toBe('constructor'); + }); + it('should allow safe template literal', async () => { const rule = new DisallowedIdentifierRule({ disallowed: ['constructor'] }); const validator = new JSAstValidator([rule]); diff --git a/libs/ast/src/rules/coercion-utils.ts b/libs/ast/src/rules/coercion-utils.ts index 5463725..08a775d 100644 --- a/libs/ast/src/rules/coercion-utils.ts +++ b/libs/ast/src/rules/coercion-utils.ts @@ -16,14 +16,84 @@ export function extractReturnLiteralString(block: any): string | null { const body = block.body; if (!Array.isArray(body)) return null; + let returnCount = 0; + let returnArg: any = null; for (const stmt of body) { - if (stmt.type === 'ReturnStatement' && stmt.argument) { - if (stmt.argument.type === 'Literal' && typeof stmt.argument.value === 'string') { - return stmt.argument.value; + if (stmt.type === 'ReturnStatement') { + returnCount++; + if (returnCount === 1 && stmt.argument) { + returnArg = stmt.argument; } - return null; } } + + if (returnCount !== 1 || !returnArg) return null; + if (returnArg.type === 'Literal' && typeof returnArg.value === 'string') { + return returnArg.value; + } + return null; +} + +/** + * Resolve a single coercion property (toString or valueOf) to its string value. + * + * Handles: + * - ArrowFunctionExpression with expression body: `() => 'x'` + * - ArrowFunctionExpression with block body: `() => { return 'x' }` + * - FunctionExpression / method shorthand: `function() { return 'x' }` + * - Getter returning a function: `get toString() { return () => 'x' }` + */ +function resolveCoercionProperty(prop: any): string | null { + const value = prop.value; + if (!value) return null; + + // ArrowFunctionExpression with expression body: () => 'x' + if (value.type === 'ArrowFunctionExpression') { + if (value.expression && value.body) { + if (value.body.type === 'Literal' && typeof value.body.value === 'string') { + return value.body.value; + } + } else if (value.body && value.body.type === 'BlockStatement') { + const result = extractReturnLiteralString(value.body); + if (result !== null) return result; + } + } + + // FunctionExpression or method shorthand: function() { return 'x' } + if (value.type === 'FunctionExpression') { + if (value.body && value.body.type === 'BlockStatement') { + const result = extractReturnLiteralString(value.body); + if (result !== null) return result; + } + } + + // Getter: { get toString() { return () => 'x' } } + // The getter returns a function; JS calls the getter then calls the returned function. + if (prop.kind === 'get' && value.type === 'FunctionExpression') { + if (value.body && value.body.type === 'BlockStatement') { + for (const stmt of value.body.body) { + if (stmt.type === 'ReturnStatement' && stmt.argument) { + const ret = stmt.argument; + if (ret.type === 'ArrowFunctionExpression') { + if (ret.expression && ret.body?.type === 'Literal' && typeof ret.body.value === 'string') { + return ret.body.value; + } + if (ret.body?.type === 'BlockStatement') { + const inner = extractReturnLiteralString(ret.body); + if (inner !== null) return inner; + } + } + if (ret.type === 'FunctionExpression') { + if (ret.body?.type === 'BlockStatement') { + const inner = extractReturnLiteralString(ret.body); + if (inner !== null) return inner; + } + } + } + } + } + } + return null; } @@ -31,12 +101,16 @@ export function extractReturnLiteralString(block: any): string | null { * Try to statically determine the coerced string value of an ObjectExpression * that defines a `toString` or `valueOf` method returning a string literal. * + * Respects ECMAScript ToPrimitive string-hint precedence: toString is resolved + * first; valueOf is used only as a fallback. + * * Covers: * - `{ toString: () => 'x' }` (ArrowFunctionExpression, expression body) * - `{ toString: () => { return 'x' } }` (ArrowFunctionExpression, block body) * - `{ toString() { return 'x' } }` (method shorthand / FunctionExpression) * - `{ toString: function() { return 'x' } }` (FunctionExpression) - * - Same patterns with `valueOf` + * - `{ get toString() { return () => 'x' } }` (Getter returning function) + * - Same patterns with `valueOf` (lower priority) * * Returns the resolved string or `null` if it cannot be determined. */ @@ -44,10 +118,13 @@ export function tryGetObjectCoercedString(node: any): string | null { if (node.type !== 'ObjectExpression') return null; if (!node.properties || node.properties.length === 0) return null; + // Collect toString and valueOf properties without resolving yet + let toStringProp: any = null; + let valueOfProp: any = null; + for (const prop of node.properties) { if (prop.type !== 'Property') continue; - // Get the property key name let keyName: string | null = null; if (prop.key.type === 'Identifier') { keyName = prop.key.name; @@ -55,62 +132,23 @@ export function tryGetObjectCoercedString(node: any): string | null { keyName = prop.key.value; } - if (keyName !== 'toString' && keyName !== 'valueOf') continue; - - const value = prop.value; - if (!value) continue; - - // ArrowFunctionExpression with expression body: () => 'x' - if (value.type === 'ArrowFunctionExpression') { - if (value.expression && value.body) { - // expression body — the body IS the expression - if (value.body.type === 'Literal' && typeof value.body.value === 'string') { - return value.body.value; - } - } else if (value.body && value.body.type === 'BlockStatement') { - // block body — look for return statement - const result = extractReturnLiteralString(value.body); - if (result !== null) return result; - } + if (keyName === 'toString') { + toStringProp = prop; + } else if (keyName === 'valueOf') { + valueOfProp = prop; } + } - // FunctionExpression or method shorthand: function() { return 'x' } - if (value.type === 'FunctionExpression') { - if (value.body && value.body.type === 'BlockStatement') { - const result = extractReturnLiteralString(value.body); - if (result !== null) return result; - } - } + // Resolve toString first (ToPrimitive string-hint precedence) + if (toStringProp) { + const result = resolveCoercionProperty(toStringProp); + if (result !== null) return result; + } - // Getter: { get toString() { return () => 'x' } } - // The getter returns a function; JS calls the getter then calls the returned function. - if (prop.kind === 'get' && value.type === 'FunctionExpression') { - if (value.body && value.body.type === 'BlockStatement') { - for (const stmt of value.body.body) { - if (stmt.type === 'ReturnStatement' && stmt.argument) { - const ret = stmt.argument; - // Getter returns an arrow: get toString() { return () => 'x' } - if (ret.type === 'ArrowFunctionExpression') { - if (ret.expression && ret.body?.type === 'Literal' && typeof ret.body.value === 'string') { - return ret.body.value; - } - if (ret.body?.type === 'BlockStatement') { - const inner = extractReturnLiteralString(ret.body); - if (inner !== null) return inner; - } - } - // Getter returns a function expression: get toString() { return function() { return 'x' } } - if (ret.type === 'FunctionExpression') { - if (ret.body?.type === 'BlockStatement') { - const inner = extractReturnLiteralString(ret.body); - if (inner !== null) return inner; - } - } - break; - } - } - } - } + // Fall back to valueOf + if (valueOfProp) { + const result = resolveCoercionProperty(valueOfProp); + if (result !== null) return result; } return null; diff --git a/libs/core/src/__tests__/enclave.proto-escape-via-array-coercion.spec.ts b/libs/core/src/__tests__/enclave.proto-escape-via-array-coercion.spec.ts index a54c5b0..d9a7973 100644 --- a/libs/core/src/__tests__/enclave.proto-escape-via-array-coercion.spec.ts +++ b/libs/core/src/__tests__/enclave.proto-escape-via-array-coercion.spec.ts @@ -65,39 +65,54 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { it('ATK-ARRCOERCE-01: PoC 1 with default config', async () => { const enclave = new Enclave({ timeout: 5000 }); - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-02: PoC 1 with STRICT security level', async () => { const enclave = new Enclave({ securityLevel: 'STRICT', timeout: 5000 }); - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-03: PoC 1 with SECURE security level', async () => { const enclave = new Enclave({ securityLevel: 'SECURE', timeout: 5000 }); - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-04: PoC 1 with explicit memoryLimit', async () => { const enclave = new Enclave({ timeout: 5000, memoryLimit: 2 * 1024 * 1024 }); - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-05: PoC 1 with memoryLimit=0 (no __host_memory_track__)', async () => { const enclave = new Enclave({ timeout: 5000, memoryLimit: 0 }); - // When memoryLimit is 0, __host_memory_track__ should not be available. - // The exploit should fail either at validation or when accessing the missing global. - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + // When memoryLimit is 0, __host_memory_track__ should not be available. + // The exploit should fail either at validation or when accessing the missing global. + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); }); @@ -127,38 +142,53 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { it('ATK-ARRCOERCE-06: PoC 2 with default config', async () => { const enclave = new Enclave({ timeout: 5000 }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-07: PoC 2 with STRICT security level', async () => { const enclave = new Enclave({ securityLevel: 'STRICT', timeout: 5000 }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-08: PoC 2 with SECURE security level', async () => { const enclave = new Enclave({ securityLevel: 'SECURE', timeout: 5000 }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-09: PoC 2 with memoryLimit=0', async () => { // PoC 2 works regardless of memoryLimit since it uses __safe_console const enclave = new Enclave({ timeout: 5000, memoryLimit: 0 }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-10: PoC 2 with PERMISSIVE security level', async () => { const enclave = new Enclave({ securityLevel: 'PERMISSIVE', timeout: 5000 }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); }); @@ -166,118 +196,133 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { describe('Section 3: Building block isolation', () => { it("ATK-ARRCOERCE-11: {}[['__proto__']] alone should not yield Object.prototype", async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}[['__proto__']]; - return { type: typeof proto, isNull: proto === null, isUndefined: proto === undefined }; - `; - const result = await enclave.run<{ type: string; isNull: boolean; isUndefined: boolean }>(code); - assertNoEscape(result); - if (result.success && result.value) { - // proto should be null (shadowed) or access should be blocked - expect(result.value.type).not.toBe('function'); + try { + const code = ` + const proto = {}[['__proto__']]; + return { type: typeof proto, isNull: proto === null, isUndefined: proto === undefined }; + `; + const result = await enclave.run<{ type: string; isNull: boolean; isUndefined: boolean }>(code); + assertNoEscape(result); + if (result.success && result.value) { + // proto should be null (shadowed) or access should be blocked + expect(result.value.type).not.toBe('function'); + } + } finally { + enclave.dispose(); } - enclave.dispose(); }, 15000); it("ATK-ARRCOERCE-12: op[['constructor']] chain should be blocked", async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const op = {}[['__proto__']]; - if (op === null || op === undefined) return 'blocked_at_proto'; - const ho = op[['constructor']]; - return { type: typeof ho, name: ho?.name }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const op = {}[['__proto__']]; + if (op === null || op === undefined) return 'blocked_at_proto'; + const ho = op[['constructor']]; + return { type: typeof ho, name: ho?.name }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-13: getOwnPropertyDescriptors(this) via variable should be blocked', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - return { keys: Object.keys(glob).slice(0, 5) }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); + return { keys: Object.keys(glob).slice(0, 5) }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-14: host function .constructor access from descriptor .value', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - // Try to get Function constructor from any host function descriptor - const keys = Object.keys(glob); - for (const k of keys) { - const desc = glob[k]; - if (desc && typeof desc.value === 'function') { - try { - const F = desc.value[['constructor']]; - if (typeof F === 'function') { - const fn = F('return "ESCAPED"'); - if (typeof fn === 'function') { - return { escaped: true, via: k, result: fn() }; + try { + const code = ` + try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); + // Try to get Function constructor from any host function descriptor + const keys = Object.keys(glob); + for (const k of keys) { + const desc = glob[k]; + if (desc && typeof desc.value === 'function') { + try { + const F = desc.value[['constructor']]; + if (typeof F === 'function') { + const fn = F('return "ESCAPED"'); + if (typeof fn === 'function') { + return { escaped: true, via: k, result: fn() }; + } } - } - } catch (e) { /* try next */ } + } catch (e) { /* try next */ } + } } + return 'no_exploitable_descriptor'; + } catch (e) { + return { blocked: true, error: e.message }; } - return 'no_exploitable_descriptor'; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-15: Symbol.for(nodejs.util.inspect.custom) inspect callback leak', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const sym = Symbol.for('nodejs.util.inspect.custom'); - let leaked = false; - const obj = { - [sym]: (depth, option, inspect) => { - if (inspect && typeof inspect === 'function') { - leaked = true; - try { - const F = inspect[['constructor']][['constructor']]; - if (typeof F === 'function') { - return F('return "ESCAPED"')(); - } - } catch (e) { /* blocked */ } + try { + const code = ` + try { + const sym = Symbol.for('nodejs.util.inspect.custom'); + let leaked = false; + const obj = { + [sym]: (depth, option, inspect) => { + if (inspect && typeof inspect === 'function') { + leaked = true; + try { + const F = inspect[['constructor']][['constructor']]; + if (typeof F === 'function') { + return F('return "ESCAPED"')(); + } + } catch (e) { /* blocked */ } + } + return 'safe'; } - return 'safe'; - } - }; - console.log(obj); - return { leaked }; - } catch (e) { - return { blocked: true, error: e.message }; + }; + console.log(obj); + return { leaked }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run<{ leaked?: boolean; blocked?: boolean }>(code); + assertNoEscape(result); + if (result.success && result.value && 'leaked' in result.value) { + expect(result.value.leaked).not.toBe(true); } - `; - const result = await enclave.run<{ leaked?: boolean; blocked?: boolean }>(code); - assertNoEscape(result); - if (result.success && result.value && 'leaked' in result.value) { - expect(result.value.leaked).not.toBe(true); + } finally { + enclave.dispose(); } - enclave.dispose(); }, 15000); }); @@ -285,118 +330,136 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { describe('Section 4: Array coercion variants', () => { it("ATK-ARRCOERCE-16: nested arrays [[['__proto__']]]", async () => { const enclave = new Enclave({ timeout: 5000 }); - // [[['__proto__']]] coerces to '__proto__' as well - const code = ` - const proto = {}[[['__proto__']]]; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[['constructor']]; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + // [[['__proto__']]] coerces to '__proto__' as well + const code = ` + const proto = {}[[['__proto__']]]; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[['constructor']]; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it("ATK-ARRCOERCE-17: toString override [{toString: () => 'constructor'}]", async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const key = [{toString: () => 'constructor'}]; - const proto = {}[['__proto__']]; - if (!proto) return 'blocked_at_proto'; - const ctor = proto[key]; - return { type: typeof ctor, name: ctor?.name }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const key = [{toString: () => 'constructor'}]; + const proto = {}[['__proto__']]; + if (!proto) return 'blocked_at_proto'; + const ctor = proto[key]; + return { type: typeof ctor, name: ctor?.name }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it("ATK-ARRCOERCE-18: array join ['__prot','o__'].join('')", async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const key = ['__prot','o__'].join(''); - const proto = {}[key]; - if (!proto) return 'blocked_at_proto'; - const ctor = proto[['constructor']]; - return { type: typeof ctor }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const key = ['__prot','o__'].join(''); + const proto = {}[key]; + if (!proto) return 'blocked_at_proto'; + const ctor = proto[['constructor']]; + return { type: typeof ctor }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it("ATK-ARRCOERCE-19: [['prototype']] access", async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const F = function(){}; - const proto = F[['prototype']]; - if (!proto) return 'blocked'; - const ctor = proto[['constructor']]; - return { type: typeof ctor, name: ctor?.name }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const F = function(){}; + const proto = F[['prototype']]; + if (!proto) return 'blocked'; + const ctor = proto[['constructor']]; + return { type: typeof ctor, name: ctor?.name }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-20: variable-stored arrays for property access', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const protoKey = ['__proto__']; - const ctorKey = ['constructor']; - const proto = {}[protoKey]; - if (!proto) return 'blocked_at_proto'; - const ctor = proto[ctorKey]; - return { type: typeof ctor }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const protoKey = ['__proto__']; + const ctorKey = ['constructor']; + const proto = {}[protoKey]; + if (!proto) return 'blocked_at_proto'; + const ctor = proto[ctorKey]; + return { type: typeof ctor }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-21: building inspect symbol string via array join', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const symName = ['nodejs', 'util', 'inspect', 'custom'].join('.'); - const sym = Symbol.for(symName); - let leaked = false; - const obj = { - [sym]: (depth, option, inspect) => { - if (inspect && typeof inspect === 'function') { - leaked = true; + try { + const code = ` + try { + const symName = ['nodejs', 'util', 'inspect', 'custom'].join('.'); + const sym = Symbol.for(symName); + let leaked = false; + const obj = { + [sym]: (depth, option, inspect) => { + if (inspect && typeof inspect === 'function') { + leaked = true; + } + return 'safe'; } - return 'safe'; - } - }; - console.log(obj); - return { leaked }; - } catch (e) { - return { blocked: true, error: e.message }; + }; + console.log(obj); + return { leaked }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run<{ leaked?: boolean; blocked?: boolean }>(code); + assertNoEscape(result); + if (result.success && result.value && 'leaked' in result.value) { + expect(result.value.leaked).not.toBe(true); } - `; - const result = await enclave.run<{ leaked?: boolean; blocked?: boolean }>(code); - assertNoEscape(result); - if (result.success && result.value && 'leaked' in result.value) { - expect(result.value.leaked).not.toBe(true); + } finally { + enclave.dispose(); } - enclave.dispose(); }, 15000); }); @@ -404,161 +467,121 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { describe('Section 5: Full chain variants', () => { it('ATK-ARRCOERCE-22: PoC1 using getOwnPropertyDescriptor (singular)', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const desc = ho.getOwnPropertyDescriptor(this, '__host_memory_track__'); - if (!desc || typeof desc.value !== 'function') return 'no_memory_track'; - const F = desc.value[['constructor']]; - const fn = F('return process'); - const proc = fn(); - return { - res: proc.getBuiltinModule('child_process') - .execSync('id').toString().split('\\n'), - }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const desc = ho.getOwnPropertyDescriptor(this, '__host_memory_track__'); + if (!desc || typeof desc.value !== 'function') return 'no_memory_track'; + const F = desc.value[['constructor']]; + const fn = F('return process'); + const proc = fn(); + return { + res: proc.getBuiltinModule('child_process') + .execSync('id').toString().split('\\n'), + }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-23: PoC1 using Object.keys(this) + property access', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const keys = ho.keys(this); - // Find __host_memory_track__ in the keys - let trackFn = null; - for (const k of keys) { - if (k.indexOf('memory') !== -1 || k.indexOf('track') !== -1) { - const val = this[k]; - if (typeof val === 'function') { - trackFn = val; - break; + try { + const code = ` + try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const keys = ho.keys(this); + // Find __host_memory_track__ in the keys + let trackFn = null; + for (const k of keys) { + if (k.indexOf('memory') !== -1 || k.indexOf('track') !== -1) { + const val = this[k]; + if (typeof val === 'function') { + trackFn = val; + break; + } } } + if (!trackFn) return 'no_track_fn'; + const F = trackFn[['constructor']]; + const fn = F('return process'); + const proc = fn(); + return { + res: proc.getBuiltinModule('child_process') + .execSync('id').toString().split('\\n'), + }; + } catch (e) { + return { blocked: true, error: e.message }; } - if (!trackFn) return 'no_track_fn'; - const F = trackFn[['constructor']]; - const fn = F('return process'); - const proc = fn(); - return { - res: proc.getBuiltinModule('child_process') - .execSync('id').toString().split('\\n'), - }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-24: PoC2 using console.warn instead of console.log', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - const sym = glob[['Symbol']].value.for('nodejs.util.inspect.custom'); - - let result; - const obj = { - [sym]: (depth, option, inspect) => { - result = inspect[['constructor']] - [['constructor']]('return process')() - .getBuiltinModule('child_process') - .execSync('id') - .toString() - .split('\\n'); - } - }; - glob.__safe_console.value.warn(obj); - return result; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); - }, 15000); + try { + const code = ` + try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); + const sym = glob[['Symbol']].value.for('nodejs.util.inspect.custom'); - it('ATK-ARRCOERCE-25: PoC2 using console.error instead of console.log', async () => { - const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - const sym = glob[['Symbol']].value.for('nodejs.util.inspect.custom'); - - let result; - const obj = { - [sym]: (depth, option, inspect) => { - result = inspect[['constructor']] - [['constructor']]('return process')() - .getBuiltinModule('child_process') - .execSync('id') - .toString() - .split('\\n'); - } - }; - glob.__safe_console.value.error(obj); - return result; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + let result; + const obj = { + [sym]: (depth, option, inspect) => { + result = inspect[['constructor']] + [['constructor']]('return process')() + .getBuiltinModule('child_process') + .execSync('id') + .toString() + .split('\\n'); + } + }; + glob.__safe_console.value.warn(obj); + return result; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); - it('ATK-ARRCOERCE-26: combined PoC1+PoC2 fallback chain', async () => { + it('ATK-ARRCOERCE-25: PoC2 using console.error instead of console.log', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[['__proto__']]; - if (!op) return 'blocked_at_proto'; - const ho = op[['constructor']]; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - - // Attempt PoC1 first (via __host_memory_track__) - try { - const trackDesc = glob.__host_memory_track__; - if (trackDesc && typeof trackDesc.value === 'function') { - const F = trackDesc.value[['constructor']]; - const fn = F('return process'); - const proc = fn(); - return { - via: 'poc1', - res: proc.getBuiltinModule('child_process') - .execSync('id').toString().split('\\n'), - }; - } - } catch (e) { /* fallback to PoC2 */ } - - // Attempt PoC2 (via inspect.custom) + try { + const code = ` try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); const sym = glob[['Symbol']].value.for('nodejs.util.inspect.custom'); + let result; const obj = { [sym]: (depth, option, inspect) => { @@ -570,18 +593,73 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { .split('\\n'); } }; - glob.__safe_console.value.log(obj); - if (result) return { via: 'poc2', res: result }; - } catch (e) { /* both failed */ } + glob.__safe_console.value.error(obj); + return result; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } + }, 15000); - return 'both_blocked'; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + it('ATK-ARRCOERCE-26: combined PoC1+PoC2 fallback chain', async () => { + const enclave = new Enclave({ timeout: 5000 }); + try { + const code = ` + try { + const op = {}[['__proto__']]; + if (!op) return 'blocked_at_proto'; + const ho = op[['constructor']]; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); + + // Attempt PoC1 first (via __host_memory_track__) + try { + const trackDesc = glob.__host_memory_track__; + if (trackDesc && typeof trackDesc.value === 'function') { + const F = trackDesc.value[['constructor']]; + const fn = F('return process'); + const proc = fn(); + return { + via: 'poc1', + res: proc.getBuiltinModule('child_process') + .execSync('id').toString().split('\\n'), + }; + } + } catch (e) { /* fallback to PoC2 */ } + + // Attempt PoC2 (via inspect.custom) + try { + const sym = glob[['Symbol']].value.for('nodejs.util.inspect.custom'); + let result; + const obj = { + [sym]: (depth, option, inspect) => { + result = inspect[['constructor']] + [['constructor']]('return process')() + .getBuiltinModule('child_process') + .execSync('id') + .toString() + .split('\\n'); + } + }; + glob.__safe_console.value.log(obj); + if (result) return { via: 'poc2', res: result }; + } catch (e) { /* both failed */ } + + return 'both_blocked'; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); }); @@ -624,30 +702,42 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { it('ATK-ARRCOERCE-27: PoC1 with doubleVm enabled (default)', async () => { const enclave = new Enclave({ timeout: 5000, doubleVm: { enabled: true } }); - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-28: PoC2 with doubleVm enabled (default)', async () => { const enclave = new Enclave({ timeout: 5000, doubleVm: { enabled: true } }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-29: PoC1 with doubleVm explicitly disabled', async () => { const enclave = new Enclave({ timeout: 5000, doubleVm: { enabled: false } }); - const result = await enclave.run(POC1_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC1_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-30: PoC2 with doubleVm explicitly disabled', async () => { const enclave = new Enclave({ timeout: 5000, doubleVm: { enabled: false } }); - const result = await enclave.run(POC2_CODE); - assertNoEscape(result); - enclave.dispose(); + try { + const result = await enclave.run(POC2_CODE); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); }); @@ -655,125 +745,149 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { describe('Section 7: Object toString/valueOf coercion bypass', () => { it('ATK-ARRCOERCE-31: {toString: () => "__proto__"} direct key access', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}[{toString: () => '__proto__'}]; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[{toString: () => 'constructor'}]; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[{toString: () => '__proto__'}]; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[{toString: () => 'constructor'}]; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-32: method shorthand toString() { return "constructor" }', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}[{toString() { return '__proto__' }}]; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[{toString() { return 'constructor' }}]; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[{toString() { return '__proto__' }}]; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[{toString() { return 'constructor' }}]; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-33: valueOf-based coercion', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}[{valueOf: () => '__proto__'}]; - if (proto === null || proto === undefined) return 'blocked'; - return { type: typeof proto }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[{valueOf: () => '__proto__'}]; + if (proto === null || proto === undefined) return 'blocked'; + return { type: typeof proto }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-34: full escape chain via toString coercion', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[{toString: () => '__proto__'}]; - if (!op) return 'blocked_at_proto'; - const ho = op[{toString: () => 'constructor'}]; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - return { - res: glob.__host_memory_track__.value[{toString: () => 'constructor'}]('return process')() - .getBuiltinModule('child_process') - .execSync('id') - .toString() - .split('\\n'), - }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const op = {}[{toString: () => '__proto__'}]; + if (!op) return 'blocked_at_proto'; + const ho = op[{toString: () => 'constructor'}]; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); + return { + res: glob.__host_memory_track__.value[{toString: () => 'constructor'}]('return process')() + .getBuiltinModule('child_process') + .execSync('id') + .toString() + .split('\\n'), + }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-35: mixed array + object coercion', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const proto = {}[[{toString: () => '__proto__'}]]; - if (!proto) return 'blocked_at_proto'; - const ctor = proto[{toString: () => 'constructor'}]; - return { type: typeof ctor }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const proto = {}[[{toString: () => '__proto__'}]]; + if (!proto) return 'blocked_at_proto'; + const ctor = proto[{toString: () => 'constructor'}]; + return { type: typeof ctor }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-36: FunctionExpression syntax variant', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const proto = {}[{toString: function() { return '__proto__' }}]; - if (!proto) return 'blocked_at_proto'; - const ctor = proto[{toString: function() { return 'constructor' }}]; - return { type: typeof ctor }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const proto = {}[{toString: function() { return '__proto__' }}]; + if (!proto) return 'blocked_at_proto'; + const ctor = proto[{toString: function() { return 'constructor' }}]; + return { type: typeof ctor }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-37: toString coercion with STRICT security level', async () => { const enclave = new Enclave({ securityLevel: 'STRICT', timeout: 5000 }); - const code = ` - const proto = {}[{toString: () => '__proto__'}]; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[{toString: () => 'constructor'}]; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[{toString: () => '__proto__'}]; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[{toString: () => 'constructor'}]; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-38: toString coercion with PERMISSIVE security level', async () => { const enclave = new Enclave({ securityLevel: 'PERMISSIVE', timeout: 5000 }); - const code = ` - const proto = {}[{toString: () => '__proto__'}]; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[{toString: () => 'constructor'}]; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[{toString: () => '__proto__'}]; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[{toString: () => 'constructor'}]; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); }); @@ -781,167 +895,185 @@ describe('ATK-ARRCOERCE: Prototype Escape via Array Coercion', () => { describe('Section 8: Computed-key bypass vectors', () => { it('ATK-ARRCOERCE-39: template literal `__proto__` as computed key', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}\`__proto__\`; - if (proto === null || proto === undefined) return 'blocked'; - return { type: typeof proto }; - ` - .replace('{}`', '[`') - .replace('`]', '`]') - .replace('{}`__proto__`', '[`__proto__`]'); - // Use string building to avoid template literal parsing issues in test - const safeCode = - 'const proto = {}[`__proto__`]; if (proto === null || proto === undefined) return "blocked"; return { type: typeof proto };'; - const result = await enclave.run(safeCode); - assertNoEscape(result); - enclave.dispose(); + try { + const code = + 'const proto = {}[`__proto__`]; if (proto === null || proto === undefined) return "blocked"; return { type: typeof proto };'; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-40: conditional expression true ? "__proto__" : "x"', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}[true ? '__proto__' : 'x']; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[true ? 'constructor' : 'y']; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[true ? '__proto__' : 'x']; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[true ? 'constructor' : 'y']; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-41: sequence expression (0, "__proto__")', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}[(0, '__proto__')]; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[(0, 'constructor')]; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}[(0, '__proto__')]; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[(0, 'constructor')]; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-42: assignment expression x = "__proto__"', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - let x; - const proto = {}[x = '__proto__']; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto[x = 'constructor']; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + let x; + const proto = {}[x = '__proto__']; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto[x = 'constructor']; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-43: logical OR "" || "__proto__"', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - const proto = {}['' || '__proto__']; - if (proto === null || proto === undefined) return 'blocked'; - const ctor = proto['' || 'constructor']; - return { type: typeof ctor }; - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + const proto = {}['' || '__proto__']; + if (proto === null || proto === undefined) return 'blocked'; + const ctor = proto['' || 'constructor']; + return { type: typeof ctor }; + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-44: full escape chain using template literal coercion', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = [ - 'try {', - ' const op = {}[`__proto__`];', - ' if (!op) return "blocked_at_proto";', - ' const ho = op[`constructor`];', - ' if (!ho) return "blocked_at_constructor";', - ' const glob = ho.getOwnPropertyDescriptors(this);', - ' return {', - ' res: glob.__host_memory_track__.value[`constructor`]("return process")()', - ' .getBuiltinModule("child_process")', - ' .execSync("id")', - ' .toString()', - ' .split("\\n"),', - ' };', - '} catch (e) {', - ' return { blocked: true, error: e.message };', - '}', - ].join('\n'); - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = [ + 'try {', + ' const op = {}[`__proto__`];', + ' if (!op) return "blocked_at_proto";', + ' const ho = op[`constructor`];', + ' if (!ho) return "blocked_at_constructor";', + ' const glob = ho.getOwnPropertyDescriptors(this);', + ' return {', + ' res: glob.__host_memory_track__.value[`constructor`]("return process")()', + ' .getBuiltinModule("child_process")', + ' .execSync("id")', + ' .toString()', + ' .split("\\n"),', + ' };', + '} catch (e) {', + ' return { blocked: true, error: e.message };', + '}', + ].join('\n'); + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-45: full escape chain using conditional coercion', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const op = {}[true ? '__proto__' : 'x']; - if (!op) return 'blocked_at_proto'; - const ho = op[true ? 'constructor' : 'y']; - if (!ho) return 'blocked_at_constructor'; - const glob = ho.getOwnPropertyDescriptors(this); - return { - res: glob.__host_memory_track__.value[true ? 'constructor' : 'z']('return process')() - .getBuiltinModule('child_process') - .execSync('id') - .toString() - .split('\\n'), - }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const op = {}[true ? '__proto__' : 'x']; + if (!op) return 'blocked_at_proto'; + const ho = op[true ? 'constructor' : 'y']; + if (!ho) return 'blocked_at_constructor'; + const glob = ho.getOwnPropertyDescriptors(this); + return { + res: glob.__host_memory_track__.value[true ? 'constructor' : 'z']('return process')() + .getBuiltinModule('child_process') + .execSync('id') + .toString() + .split('\\n'), + }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-46: getter-based toString coercion', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = ` - try { - const proto = {}[{get toString(){ return () => '__proto__' }}]; - if (!proto) return 'blocked_at_proto'; - const ctor = proto[{get toString(){ return () => 'constructor' }}]; - return { type: typeof ctor }; - } catch (e) { - return { blocked: true, error: e.message }; - } - `; - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = ` + try { + const proto = {}[{get toString(){ return () => '__proto__' }}]; + if (!proto) return 'blocked_at_proto'; + const ctor = proto[{get toString(){ return () => 'constructor' }}]; + return { type: typeof ctor }; + } catch (e) { + return { blocked: true, error: e.message }; + } + `; + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); it('ATK-ARRCOERCE-47: mixed vectors (template + conditional + sequence)', async () => { const enclave = new Enclave({ timeout: 5000 }); - const code = [ - 'try {', - ' const op = {}[`__proto__`];', - ' if (!op) return "blocked_at_proto";', - ' const ho = op[true ? "constructor" : "x"];', - ' if (!ho) return "blocked_at_constructor";', - ' const glob = ho.getOwnPropertyDescriptors(this);', - ' const trackDesc = glob.__host_memory_track__;', - ' if (!trackDesc || typeof trackDesc.value !== "function") return "no_memory_track";', - ' const F = trackDesc.value[(0, "constructor")];', - ' const fn = F("return process");', - ' const proc = fn();', - ' return {', - ' res: proc.getBuiltinModule("child_process")', - ' .execSync("id").toString().split("\\n"),', - ' };', - '} catch (e) {', - ' return { blocked: true, error: e.message };', - '}', - ].join('\n'); - const result = await enclave.run(code); - assertNoEscape(result); - enclave.dispose(); + try { + const code = [ + 'try {', + ' const op = {}[`__proto__`];', + ' if (!op) return "blocked_at_proto";', + ' const ho = op[true ? "constructor" : "x"];', + ' if (!ho) return "blocked_at_constructor";', + ' const glob = ho.getOwnPropertyDescriptors(this);', + ' const trackDesc = glob.__host_memory_track__;', + ' if (!trackDesc || typeof trackDesc.value !== "function") return "no_memory_track";', + ' const F = trackDesc.value[(0, "constructor")];', + ' const fn = F("return process");', + ' const proc = fn();', + ' return {', + ' res: proc.getBuiltinModule("child_process")', + ' .execSync("id").toString().split("\\n"),', + ' };', + '} catch (e) {', + ' return { blocked: true, error: e.message };', + '}', + ].join('\n'); + const result = await enclave.run(code); + assertNoEscape(result); + } finally { + enclave.dispose(); + } }, 15000); }); });