Runtime Isolation: createIsolatedContext() + strictObject + tolerantProxy #2

Closed
opened 2026-06-17 13:37:43 +00:00 by Artur · 1 comment
Owner

Goal

Create an isolated JS execution context per page. Page JS sees window, document, fetch, setTimeout — but NOT Bun, process, fs, require.

Implement two proxy layers:

  1. strictObject — unknown props throw MissingBrowserAPI
  2. tolerantProxy — catch-all for unknown APIs (logs + returns chainable proxy)

What to Build

2.1 tolerant-proxy.ts

export function createTolerantProxy(name: string): object {
  // Returns a Proxy that:
  // - GET any property -> another tolerantProxy (chainable)
  // - APPLY (call) -> NOOP, returns tolerantProxy
  // - CONSTRUCT (new) -> NOOP, returns tolerantProxy
  // - GET 'then'/'catch' -> undefined (prevents thenable)
  // - GET 'toString' -> () => ''
  // Each access logs via logMissingAPI()
}

2.2 diagnostics/api-log.ts

export interface APILogEntry {
  path: string;
  type: 'get' | 'set' | 'call' | 'construct';
  args?: unknown[];
  timestamp: number;
}

export class APILogger {
  private entries: APILogEntry[] = [];
  log(path: string, type: APILogEntry['type'], args?: unknown[]): void;
  getEntries(): readonly APILogEntry[];
  clear(): void;
}

2.3 runtime-isolation.ts

export interface IsolatedContext {
  window: Window & typeof globalThis;
  document: Document;
  globalThis: Window & typeof globalThis;
  apisUsed: string[];
  tolerantProxy: object;
}

export function createIsolatedContext(): IsolatedContext {
  // 1. Instantiate Happy DOM Window
  // 2. strictObject for window (implemented APIs)
  // 3. window.tolerantProxy = tolerantProxy (for unknown APIs)
  // 4. Bun/process/require/fs/module NOT exposed
  // 5. Exposed:
  //    Array, Object, String, Number, Boolean, Map, Set, Promise,
  //    RegExp, Date, Math, JSON, Error, URL, URLSearchParams,
  //    Headers, Request, Response, TextEncoder, TextDecoder, Blob,
  //    File, FormData, AbortController, AbortSignal, Crypto, crypto,
  //    Intl, performance, console (wrapper), setTimeout, setInterval,
  //    clearTimeout, clearInterval, requestAnimationFrame,
  //    cancelAnimationFrame, queueMicrotask, fetch, XMLHttpRequest,
  //    WebSocket
  // 6. window.globalThis = window, self = window, top = window, parent = window
}

2.4 strictObject + MissingBrowserAPI

export class MissingBrowserAPI extends Error {
  constructor(
    public readonly path: string,
    public readonly receiverType?: string,
    public readonly args?: unknown[]
  ) {
    super(`MissingBrowserAPI: ${path}`);
    this.name = 'MissingBrowserAPI';
  }
}

export function strictObject<T extends object>(name: string, impl: T): T {
  // Proxy with get/set traps
  // GET: if prop in target -> return target[prop], else throw MissingBrowserAPI
  // SET: if prop in target -> set, return true, else throw MissingBrowserAPI
}

Tests

Unit Tests

Test Verifies
tolerant-proxy.test.ts Proxy chains (a.b.c.d -> always tolerantProxy), then/catch undefined
tolerant-proxy.logging.test.ts logMissingAPI() called on every access
strictObject.implemented.test.ts Implemented props readable/writable
strictObject.missing.test.ts Missing props throw MissingBrowserAPI
strictObject.nested.test.ts Nested strictObjects work
api-logger.test.ts APILogger records, clear(), getEntries()
runtime-isolation.basics.test.ts Returns window, document, globalThis
runtime-isolation.bun-hidden.test.ts typeof Bun === 'undefined', typeof process === 'undefined'
runtime-isolation.allowed.test.ts Array, Object, Promise, setTimeout, fetch available
runtime-isolation.console.test.ts console.log/error/warn/info works
runtime-isolation.global-this.test.ts globalThis === window, self === window

Edge Cases

Test Verifies
tolerant-proxy.thenable.test.ts tolerantProxy.then is undefined
tolerant-proxy.construct.test.ts new tolerantProxy() does not throw
tolerant-proxy.apply.test.ts tolerantProxy() does not throw
runtime-isolation.sandbox-escape.test.ts this.constructor.constructor('return Bun') throws ReferenceError

Definition of Done

  • src/tolerant-proxy.ts implemented
  • src/diagnostics/api-log.ts implemented
  • src/runtime-isolation.ts implemented
  • strictObject + MissingBrowserAPI error class
  • All unit tests pass
  • 100% line coverage for all 3 files
## Goal Create an isolated JS execution context per page. Page JS sees window, document, fetch, setTimeout — but NOT Bun, process, fs, require. Implement two proxy layers: 1. **strictObject** — unknown props throw MissingBrowserAPI 2. **tolerantProxy** — catch-all for unknown APIs (logs + returns chainable proxy) ## What to Build ### 2.1 tolerant-proxy.ts ``` export function createTolerantProxy(name: string): object { // Returns a Proxy that: // - GET any property -> another tolerantProxy (chainable) // - APPLY (call) -> NOOP, returns tolerantProxy // - CONSTRUCT (new) -> NOOP, returns tolerantProxy // - GET 'then'/'catch' -> undefined (prevents thenable) // - GET 'toString' -> () => '' // Each access logs via logMissingAPI() } ``` ### 2.2 diagnostics/api-log.ts ``` export interface APILogEntry { path: string; type: 'get' | 'set' | 'call' | 'construct'; args?: unknown[]; timestamp: number; } export class APILogger { private entries: APILogEntry[] = []; log(path: string, type: APILogEntry['type'], args?: unknown[]): void; getEntries(): readonly APILogEntry[]; clear(): void; } ``` ### 2.3 runtime-isolation.ts ``` export interface IsolatedContext { window: Window & typeof globalThis; document: Document; globalThis: Window & typeof globalThis; apisUsed: string[]; tolerantProxy: object; } export function createIsolatedContext(): IsolatedContext { // 1. Instantiate Happy DOM Window // 2. strictObject for window (implemented APIs) // 3. window.tolerantProxy = tolerantProxy (for unknown APIs) // 4. Bun/process/require/fs/module NOT exposed // 5. Exposed: // Array, Object, String, Number, Boolean, Map, Set, Promise, // RegExp, Date, Math, JSON, Error, URL, URLSearchParams, // Headers, Request, Response, TextEncoder, TextDecoder, Blob, // File, FormData, AbortController, AbortSignal, Crypto, crypto, // Intl, performance, console (wrapper), setTimeout, setInterval, // clearTimeout, clearInterval, requestAnimationFrame, // cancelAnimationFrame, queueMicrotask, fetch, XMLHttpRequest, // WebSocket // 6. window.globalThis = window, self = window, top = window, parent = window } ``` ### 2.4 strictObject + MissingBrowserAPI ``` export class MissingBrowserAPI extends Error { constructor( public readonly path: string, public readonly receiverType?: string, public readonly args?: unknown[] ) { super(`MissingBrowserAPI: ${path}`); this.name = 'MissingBrowserAPI'; } } export function strictObject<T extends object>(name: string, impl: T): T { // Proxy with get/set traps // GET: if prop in target -> return target[prop], else throw MissingBrowserAPI // SET: if prop in target -> set, return true, else throw MissingBrowserAPI } ``` ## Tests ### Unit Tests | Test | Verifies | |------|----------| | tolerant-proxy.test.ts | Proxy chains (a.b.c.d -> always tolerantProxy), then/catch undefined | | tolerant-proxy.logging.test.ts | logMissingAPI() called on every access | | strictObject.implemented.test.ts | Implemented props readable/writable | | strictObject.missing.test.ts | Missing props throw MissingBrowserAPI | | strictObject.nested.test.ts | Nested strictObjects work | | api-logger.test.ts | APILogger records, clear(), getEntries() | | runtime-isolation.basics.test.ts | Returns window, document, globalThis | | runtime-isolation.bun-hidden.test.ts | typeof Bun === 'undefined', typeof process === 'undefined' | | runtime-isolation.allowed.test.ts | Array, Object, Promise, setTimeout, fetch available | | runtime-isolation.console.test.ts | console.log/error/warn/info works | | runtime-isolation.global-this.test.ts | globalThis === window, self === window | ### Edge Cases | Test | Verifies | |------|----------| | tolerant-proxy.thenable.test.ts | tolerantProxy.then is undefined | | tolerant-proxy.construct.test.ts | `new tolerantProxy()` does not throw | | tolerant-proxy.apply.test.ts | `tolerantProxy()` does not throw | | runtime-isolation.sandbox-escape.test.ts | `this.constructor.constructor('return Bun')` throws ReferenceError | ## Definition of Done - [ ] src/tolerant-proxy.ts implemented - [ ] src/diagnostics/api-log.ts implemented - [ ] src/runtime-isolation.ts implemented - [ ] strictObject + MissingBrowserAPI error class - [ ] All unit tests pass - [ ] 100% line coverage for all 3 files
Author
Owner

Runtime Isolation — createIsolatedContext() + strictObject + tolerantProxy. Implementiert in execution-realm.ts, tolerant-proxy.ts. Tests: tolerant-proxy.test.ts (21/21).

Runtime Isolation — createIsolatedContext() + strictObject + tolerantProxy. ✅ Implementiert in execution-realm.ts, tolerant-proxy.ts. Tests: tolerant-proxy.test.ts (21/21).
Artur closed this issue 2026-06-18 06:28:02 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
glow-all/true-headless-browser#2
No description provided.