fix(code-runtime): reject forged container prototypes

This commit is contained in:
Tianyi Cui
2026-07-23 02:01:04 +08:00
parent 8379101e3b
commit e9e450a902
7 changed files with 77 additions and 12 deletions
@@ -623,6 +623,40 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
}))
})
it('rejects intrinsic-looking exotic objects as arguments and completions', async () => {
const { runtime } = await setup()
let calls = 0
const forgeObject = `
const prototype = Object.create(null);
const SpoofedObject = function Object() {};
SpoofedObject.prototype = prototype;
Object.defineProperty(prototype, 'constructor', { value: SpoofedObject });
const forged = Object.assign(Object.create(prototype), { value: 1 });
Function.prototype.toString = () => 'function Object() { [native code] }';
`
const argument = await runtime.run({
program: `${forgeObject}
try { await tools.never(forged) } catch (error) {
return { typed: error instanceof ToolCallError, name: error.name, toolName: error.toolName, message: error.message };
}
`,
bindings: tools({ never: async () => { calls += 1; return null } }),
})
expect(calls).toBe(0)
expect(argument.value).toEqual({
typed: true,
name: 'ToolCallError',
toolName: 'never',
message: 'binding arguments must be lossless JSON',
})
const completion = await runtime.run({ program: `${forgeObject}\nreturn forged`, bindings: [] })
expect(completion).toEqual({
logs: [],
error: { kind: 'invalid-output', message: 'program completion must be lossless JSON' },
})
})
it('rejects forged lossy binding arguments again at the host boundary', async () => {
const { runtime } = await setup()
let calls = 0
@@ -97,6 +97,19 @@ describe('snapshotCodeJsonValue', () => {
Object.setPrototypeOf(forgedPrototype, null)
const forgedArray = [1]
Object.setPrototypeOf(forgedArray, forgedPrototype)
const spoofedObjectPrototype = Object.create(null) as Record<string, unknown>
const SpoofedObject = function Object() {}
SpoofedObject.prototype = spoofedObjectPrototype
Object.defineProperty(spoofedObjectPrototype, 'constructor', { value: SpoofedObject })
const spoofedObject = Object.create(spoofedObjectPrototype) as Record<string, unknown>
spoofedObject.value = 1
const spoofedArrayPrototype: unknown[] = []
Object.setPrototypeOf(spoofedArrayPrototype, Object.prototype)
const SpoofedArray = function Array() {}
SpoofedArray.prototype = spoofedArrayPrototype
Object.defineProperty(spoofedArrayPrototype, 'constructor', { value: SpoofedArray })
const spoofedArray = [1]
Object.setPrototypeOf(spoofedArray, spoofedArrayPrototype)
for (const value of [
new ExoticObject(),
@@ -110,11 +123,15 @@ describe('snapshotCodeJsonValue', () => {
symbolObject,
customPrototypeObject,
forgedArray,
spoofedObject,
spoofedArray,
cyclic,
[undefined],
{ value: undefined },
]) {
expect(snapshotCodeJsonValue(value)).toBeUndefined()
const canonical = snapshotJsonValue(value)
expect(canonical).toBeUndefined()
expect(snapshotCodeJsonValue(value)).toEqual(canonical)
}
})