workflow: render thrown script values inside the realm's execution window
Codex code-review round 3: the round-2 'contained stack getter' still let a
script escape the vm sync-slice timeout — throw { get stack() { while(true){} } }
put the spin on the HOST catch path, where no timeout applies (verified: a
direct sync-slice spin dies by the timeout; the getter-hidden one hung the
process). Identity-trusting the native getter is also insufficient: V8 stack
formatting reads script-controllable hooks at format time (Error.prepareStackTrace,
a subclass name getter — both empirically confirmed), so ANY host-side
formatting of a realm error can run realm code.
The fix moves rendering into the realm itself: the compiled body (and the meta
literal) is wrapped in a realm-side catch that pre-renders the thrown value to
a string (REALM_THROWN_RENDERER_SOURCE) — a hostile accessor/toString now runs
as ordinary script code, killed by the sync-slice timeout or falling under the
documented post-await spin limitation; host WorkflowErrors pass through for
the CANCELLED mapping. The host catch descriptor-reads the pre-rendered string
(thrownRendering) or falls back to describeThrown, which invokes no getter
whose identity is not the host realm's own native stack getter.
Tests: hostile-table expectations updated for realm-side rendering; new
regressions for the getter-hidden sync spin dying by the vm timeout (engine +
meta paths) and for a hostile thenable rejection that bypasses the realm
wrapper (renders host-side, proxy labelled, traps never run); describeThrown/
thrownRendering unit tables including the realm-error identity-mismatch case.
This commit is contained in:
@@ -116,11 +116,24 @@ return 2`
|
||||
expect(error.message).toContain('proxies cannot cross')
|
||||
})
|
||||
|
||||
it('a meta expression THROWING a hostile value maps to META_INVALID — rendering runs no realm code', () => {
|
||||
it('a meta expression THROWING a hostile value maps to META_INVALID — rendering stays realm-side', () => {
|
||||
// bad() rethrows anything that is not a WorkflowError, so a hostile value
|
||||
// escaping the realm-side renderer raw would fail this test.
|
||||
const error = bad('export const meta = { name: (() => { throw { get stack() { throw new Error("boom") }, toString() { throw new Error("boom") } } })(), description: "d" }\nreturn 1')
|
||||
expect(error.code).toBe('META_INVALID')
|
||||
expect(error.message).toContain('pure literal')
|
||||
expect(error.message).toContain('[object Object]')
|
||||
expect(error.message).toContain('[unrenderable thrown value]')
|
||||
})
|
||||
|
||||
it('a spinning meta expression (even inside a thrown stack getter) dies by the eval timeout', () => {
|
||||
try {
|
||||
extractMeta('export const meta = { name: (() => { while (true) {} })(), description: "d" }', 50)
|
||||
throw new Error('expected the extraction to time out')
|
||||
} catch (error: unknown) {
|
||||
expect(error).toBeInstanceOf(WorkflowError)
|
||||
expect((error as WorkflowError).code).toBe('META_INVALID')
|
||||
expect((error as WorkflowError).message.toLowerCase()).toContain('timed out')
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects shape violations with EVERY violation listed (META_INVALID)', () => {
|
||||
|
||||
Reference in New Issue
Block a user