Merge branch 'codex/code-mode-typed-results' into codex/code-mode-complete-result-card

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml
This commit is contained in:
Tianyi Cui
2026-07-23 03:19:13 +08:00
9 changed files with 273 additions and 82 deletions

View File

@@ -23,7 +23,7 @@ Every field is validated and defaulted; `maxOutputBytes` is a safe integer of at
- **The port assumes a hostile peer** — model code can reach `parentPort` and forge traffic, so every inbound message is shape-validated and REBUILT before anything reads it (`null`, primitives, junk types, and malformed payloads drop without a throw; forged extra fields never ride along), the host answers each call id at most once, resolves binding names as OWN properties only (a forged `constructor` cannot walk a prototype chain), drops post-settlement replies, and validates every binding resolution and completion as lossless JSON. Forged `log`/`done` messages cannot bypass the outer cap: the host repeats validation and accounts every admitted log plus the completion or diagnostic. Worker-side namespaces are null-prototype with `defineProperty`, so `__proto__`-shaped binding names are ordinary keys.
- **Binding rejection classes are request data** — an optional namespace descriptor names the constructor global and the own property that receives the failed member name. The worker materializes and injects that real class, so `instanceof` works without hardcoding `tools` or `ToolCallError`; declarations with invalid or colliding globals fail before a worker spawns.
- **Two independent budgets, because the peer is hostile** — `computeMs` meters the worker's MEASURED busy time (`worker.performance.eventLoopUtilization()` polling): a hot loop cannot hide behind a pending decoy dispatch, and a program awaiting a slow tool accrues nothing. `maxWallMs` backstops what busy time cannot see (awaiting a promise nobody resolves). Both funnel into `worker.terminate()`, which ends hot synchronous loops too; heap overflow surfaces as the worker's OOM exit (`kind: 'worker-exit'`).
- **Intermediate binding values are complete JSON** — binding arguments and resolutions undergo iterative lossless-JSON validation, including a native-constructor identity check captured before program execution so user-authored functions cannot impersonate plain-container prototypes. Values flatten into a bounded-depth pre-order wire value for structured clone and rebuild iteratively on the other side. They have no byte, JavaScript call-stack, or nested structured-clone depth cap. They never enter the outer-output ledger or model context; provider/executor acquisition bounds and process/worker memory remain the limits.
- **Intermediate binding values are complete JSON** — binding arguments and resolutions undergo iterative lossless-JSON validation, including a native-constructor identity check captured before program execution so user-authored functions cannot impersonate plain-container prototypes. The worker also captures every structural and metering intrinsic used by this JSON boundary and bypasses mutable collection prototypes for private traversal state, so model mutations of global helpers cannot alter validation, wire transport, or byte accounting. Values flatten into a bounded-depth pre-order wire value for structured clone and rebuild iteratively on the other side. They have no byte, JavaScript call-stack, or nested structured-clone depth cap. They never enter the outer-output ledger or model context; provider/executor acquisition bounds and process/worker memory remain the limits.
- **Logs stream eagerly into one outer ledger** — console/stdout/stderr text crosses the port in emission order, so a timed-out or killed program still shows what it printed. The worker charges exact JSON-string bytes and preflights completion values and exception diagnostics against the remaining combined budget before posting them; a thrown million-byte stack therefore becomes the fixed `output-limit` diagnostic at the worker boundary. Native writes that bypass the patched stream slots arrive on pipes independent of the completion port, so the host repeats the ledger for those bytes and hostile forged traffic; settlement continues bounded pipe capture until worker termination completes before materializing the result. `maxOutputBytes` accounts the JSON serialization of the outer `logs` array plus the completion value or failure-message payload; fixed `CodeRunResult` field names, braces, the bounded error-kind tag, and later presentation whitespace are outside that variable-payload ledger. At or below the cap the exact value returns; a lossy completion is `invalid-output`, and a combined overflow is `output-limit` rather than a substituted inspected string. The failure retains the fitting captured prefix and later follows the normal outer `run_code` spill policy.
- **Empty environment** — the worker gets `env: {}` and `execArgv: []`: no ambient credentials (stronger than the scrubbed-env rule for spawned commands) and no inherited loader flags.
- **Dispose to quiescence** — teardown fails in-flight runs as `abort` and AWAITS each worker's exit before resolving.

View File

@@ -2,17 +2,62 @@
import type { CodeJsonValue } from '@deepseek-ai/dsh-code-runtime'
/** Control characters with a two-byte short JSON escape instead of `\u00XX`. */
const SHORT_ESCAPE_CODES = new Set([0x08, 0x09, 0x0a, 0x0c, 0x0d])
type IntrinsicCallable = (this: unknown, ...args: unknown[]) => unknown
const intrinsicReflectApply = Reflect.apply as (
target: IntrinsicCallable,
thisArgument: unknown,
argumentsList: readonly unknown[],
) => unknown
const intrinsicArrayIsArray = Array.isArray
const IntrinsicBuffer = Buffer
const intrinsicBufferByteLength = Reflect.get(Buffer, 'byteLength') as IntrinsicCallable
const intrinsicObjectDefineProperty = Object.defineProperty
const intrinsicObjectKeys = Object.keys
const intrinsicString = String
const intrinsicStringCharCodeAt = Reflect.get(String.prototype, 'charCodeAt') as IntrinsicCallable
const intrinsicStringCodePointAt = Reflect.get(String.prototype, 'codePointAt') as IntrinsicCallable
const intrinsicStringSlice = Reflect.get(String.prototype, 'slice') as IntrinsicCallable
/** UTF-8 byte length through the module-captured Node intrinsic. */
function byteLength(text: string): number {
return intrinsicReflectApply(intrinsicBufferByteLength, IntrinsicBuffer, [text, 'utf8']) as number
}
/** Append without consulting a model-mutated `Array.prototype`. */
function append<T>(target: T[], value: T): void {
intrinsicObjectDefineProperty(target, target.length, {
value,
enumerable: true,
configurable: true,
writable: true,
})
}
/** Pop without consulting a model-mutated `Array.prototype`. */
function takeLast<T>(target: T[]): T | undefined {
if (target.length === 0) return undefined
const index = target.length - 1
const value = target[index]
intrinsicObjectDefineProperty(target, 'length', { value: index })
return value
}
/** One code-point-aligned character from a string. */
function characterAt(text: string, index: number): string {
const codePoint = intrinsicReflectApply(intrinsicStringCodePointAt, text, [index]) as number
const width = codePoint > 0xffff ? 2 : 1
return intrinsicReflectApply(intrinsicStringSlice, text, [index, index + width]) as string
}
/** Serialized bytes contributed by one complete Unicode code point inside JSON quotes. */
function serializedCharacterBytes(character: string): number {
if (character.length === 2) return 4
if (character === '"' || character === '\\') return 2
const code = character.charCodeAt(0)
const code = intrinsicReflectApply(intrinsicStringCharCodeAt, character, [0]) as number
if (code >= 0xd800 && code <= 0xdfff) return 6
if (code < 0x20) return SHORT_ESCAPE_CODES.has(code) ? 2 : 6
return Buffer.byteLength(character, 'utf8')
if (code < 0x20) return code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d ? 2 : 6
return byteLength(character)
}
/**
@@ -24,9 +69,11 @@ function serializedCharacterBytes(character: string): number {
export function jsonStringBytesUpTo(text: string, maxBytes: number): number | undefined {
if (maxBytes < 2) return undefined
let bytes = 2
for (const character of text) {
for (let index = 0; index < text.length;) {
const character = characterAt(text, index)
bytes += serializedCharacterBytes(character)
if (bytes > maxBytes) return undefined
index += character.length
}
return bytes
}
@@ -49,7 +96,7 @@ export function jsonValueBytesUpTo(value: CodeJsonValue, maxBytes: number): numb
return bytes <= maxBytes
}
const tasks: Task[] = [{ kind: 'value', value }]
for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {
for (let task = takeLast(tasks); task !== undefined; task = takeLast(tasks)) {
if (task.kind === 'value') {
const current = task.value
if (current === null) {
@@ -59,16 +106,16 @@ export function jsonValueBytesUpTo(value: CodeJsonValue, maxBytes: number): numb
if (stringBytes === undefined) return undefined
bytes += stringBytes
} else if (typeof current === 'number') {
if (!add(Buffer.byteLength(String(current), 'utf8'))) return undefined
if (!add(byteLength(intrinsicString(current)))) return undefined
} else if (typeof current === 'boolean') {
if (!add(current ? 4 : 5)) return undefined
} else if (Array.isArray(current)) {
} else if (intrinsicArrayIsArray(current)) {
if (!add(2)) return undefined
if (current.length > 0) tasks.push({ kind: 'array', value: current, index: 0 })
if (current.length > 0) append(tasks, { kind: 'array', value: current, index: 0 })
} else {
if (!add(2)) return undefined
const keys = Object.keys(current)
if (keys.length > 0) tasks.push({ kind: 'object', value: current, keys, index: 0 })
const keys = intrinsicObjectKeys(current)
if (keys.length > 0) append(tasks, { kind: 'object', value: current, keys, index: 0 })
}
continue
}
@@ -77,8 +124,8 @@ export function jsonValueBytesUpTo(value: CodeJsonValue, maxBytes: number): numb
if (task.kind === 'array') {
const item = task.value[task.index]
if (item === undefined) return undefined
if (task.index + 1 < task.value.length) tasks.push({ ...task, index: task.index + 1 })
tasks.push({ kind: 'value', value: item })
if (task.index + 1 < task.value.length) append(tasks, { ...task, index: task.index + 1 })
append(tasks, { kind: 'value', value: item })
continue
}
@@ -90,8 +137,8 @@ export function jsonValueBytesUpTo(value: CodeJsonValue, maxBytes: number): numb
if (!add(keyBytes + 1)) return undefined
const item = task.value[key]
if (item === undefined) return undefined
if (task.index + 1 < task.keys.length) tasks.push({ ...task, index: task.index + 1 })
tasks.push({ kind: 'value', value: item })
if (task.index + 1 < task.keys.length) append(tasks, { ...task, index: task.index + 1 })
append(tasks, { kind: 'value', value: item })
}
return bytes
}
@@ -108,11 +155,13 @@ export function truncateJsonStringBytes(text: string, maxBytes: number): string
if (maxBytes < 2) return ''
let bytes = 2
let end = 0
for (const character of text) {
for (let index = 0; index < text.length;) {
const character = characterAt(text, index)
const cost = serializedCharacterBytes(character)
if (bytes + cost > maxBytes) break
bytes += cost
end += character.length
index += character.length
}
return end === text.length ? text : text.slice(0, end)
return end === text.length ? text : intrinsicReflectApply(intrinsicStringSlice, text, [0, end]) as string
}

View File

@@ -11,10 +11,60 @@ const intrinsicReflectApply = Reflect.get(Reflect, 'apply') as (
thisArgument: unknown,
argumentsList: readonly unknown[],
) => unknown
const IntrinsicError = Error
const IntrinsicSet = Set
const intrinsicArrayIsArray = Array.isArray
const intrinsicNumberIsFinite = Number.isFinite
const intrinsicNumberIsSafeInteger = Number.isSafeInteger
const intrinsicObjectDefineProperty = Object.defineProperty
const intrinsicObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor
const intrinsicObjectGetPrototypeOf = Object.getPrototypeOf
const intrinsicObjectHasOwn = Object.hasOwn
const intrinsicObjectIs = Object.is
const intrinsicObjectKeys = Object.keys
const intrinsicObjectPropertyIsEnumerable = Reflect.get(Object.prototype, 'propertyIsEnumerable') as IntrinsicCallable
const intrinsicReflectOwnKeys = Reflect.ownKeys
const intrinsicSetAdd = Reflect.get(Set.prototype, 'add') as IntrinsicCallable
const intrinsicSetDelete = Reflect.get(Set.prototype, 'delete') as IntrinsicCallable
const intrinsicSetHas = Reflect.get(Set.prototype, 'has') as IntrinsicCallable
/** Append without consulting a model-mutated `Array.prototype`. */
function append<T>(target: T[], value: T): void {
intrinsicObjectDefineProperty(target, target.length, {
value,
enumerable: true,
configurable: true,
writable: true,
})
}
/** Pop without consulting a model-mutated `Array.prototype`. */
function takeLast<T>(target: T[]): T | undefined {
if (target.length === 0) return undefined
const index = target.length - 1
const value = target[index]
intrinsicObjectDefineProperty(target, 'length', { value: index })
return value
}
/** Whether one captured-intrinsic Set contains a value. */
function setHas<T>(target: Set<T>, value: T): boolean {
return intrinsicReflectApply(intrinsicSetHas, target, [value]) as boolean
}
/** Add to one captured-intrinsic Set. */
function setAdd<T>(target: Set<T>, value: T): void {
intrinsicReflectApply(intrinsicSetAdd, target, [value])
}
/** Delete from one captured-intrinsic Set. */
function setDelete<T>(target: Set<T>, value: T): void {
intrinsicReflectApply(intrinsicSetDelete, target, [value])
}
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): boolean {
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor')
const descriptor = intrinsicObjectGetOwnPropertyDescriptor(prototype, 'constructor')
const constructor: unknown = descriptor?.value
if (typeof constructor !== 'function') return false
try {
@@ -28,14 +78,14 @@ function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): b
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
function isIntrinsicObjectPrototype(value: object): boolean {
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object')
return intrinsicObjectGetPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object')
}
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
function hasPlainArrayPrototype(value: unknown[]): boolean {
const prototype: unknown = Object.getPrototypeOf(value)
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, 'Array')) return false
const objectPrototype: unknown = Object.getPrototypeOf(prototype)
const prototype: unknown = intrinsicObjectGetPrototypeOf(value)
if (!intrinsicArrayIsArray(prototype) || !hasIntrinsicConstructor(prototype, 'Array')) return false
const objectPrototype: unknown = intrinsicObjectGetPrototypeOf(prototype)
return typeof objectPrototype === 'object'
&& objectPrototype !== null
&& isIntrinsicObjectPrototype(objectPrototype)
@@ -43,15 +93,18 @@ function hasPlainArrayPrototype(value: unknown[]): boolean {
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
function hasPlainObjectPrototype(value: object): boolean {
const prototype: unknown = Object.getPrototypeOf(value)
const prototype: unknown = intrinsicObjectGetPrototypeOf(value)
return prototype === null
|| typeof prototype === 'object' && isIntrinsicObjectPrototype(prototype)
}
/** Return every JSON-visible object key, or reject own data JSON would discard. */
function enumerableStringKeys(value: object): string[] | undefined {
const keys = Reflect.ownKeys(value)
if (keys.some(key => typeof key !== 'string' || !Object.prototype.propertyIsEnumerable.call(value, key))) return undefined
const keys = intrinsicReflectOwnKeys(value)
for (let index = 0; index < keys.length; index++) {
const key = keys[index]
if (typeof key !== 'string' || !intrinsicReflectApply(intrinsicObjectPropertyIsEnumerable, value, [key])) return undefined
}
return keys as string[]
}
@@ -76,15 +129,20 @@ type SnapshotTask =
* @returns a detached lossless-JSON snapshot, or `undefined` when invalid.
*/
export function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined {
const active = new Set<object>()
const active = new IntrinsicSet<object>()
let root: CodeJsonValue | undefined
const assign = (destination: SnapshotDestination, item: CodeJsonValue): void => {
if (destination.kind === 'root') {
root = item
} else if (destination.kind === 'array') {
destination.target[destination.index] = item
intrinsicObjectDefineProperty(destination.target, destination.index, {
value: item,
enumerable: true,
configurable: true,
writable: true,
})
} else {
Object.defineProperty(destination.target, destination.key, {
intrinsicObjectDefineProperty(destination.target, destination.key, {
value: item,
enumerable: true,
configurable: true,
@@ -94,14 +152,14 @@ export function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined
}
const tasks: SnapshotTask[] = [{ kind: 'visit', value, destination: { kind: 'root' } }]
for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {
for (let task = takeLast(tasks); task !== undefined; task = takeLast(tasks)) {
if (task.kind === 'leave') {
active.delete(task.source)
setDelete(active, task.source)
continue
}
if (task.kind === 'array-item') {
if (!Object.hasOwn(task.source, task.index)) return undefined
tasks.push({
if (!intrinsicObjectHasOwn(task.source, task.index)) return undefined
append(tasks, {
kind: 'visit',
value: task.source[task.index],
destination: { kind: 'array', target: task.target, index: task.index },
@@ -109,7 +167,7 @@ export function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined
continue
}
if (task.kind === 'object-property') {
tasks.push({
append(tasks, {
kind: 'visit',
value: task.source[task.key],
destination: { kind: 'object', target: task.target, key: task.key },
@@ -127,23 +185,23 @@ export function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined
continue
}
if (typeof candidate === 'number') {
if (!Number.isFinite(candidate) || Object.is(candidate, -0)) return undefined
if (!intrinsicNumberIsFinite(candidate) || intrinsicObjectIs(candidate, -0)) return undefined
assign(task.destination, candidate)
continue
}
if (typeof candidate !== 'object') return undefined
if (active.has(candidate)) return undefined
if (setHas(active, candidate)) return undefined
if (Array.isArray(candidate)) {
if (intrinsicArrayIsArray(candidate)) {
if (!hasPlainArrayPrototype(candidate)) return undefined
const length = candidate.length
if (Reflect.ownKeys(candidate).length !== length + 1) return undefined
if (intrinsicReflectOwnKeys(candidate).length !== length + 1) return undefined
const target: CodeJsonValue[] = []
assign(task.destination, target)
active.add(candidate)
tasks.push({ kind: 'leave', source: candidate })
setAdd(active, candidate)
append(tasks, { kind: 'leave', source: candidate })
for (let index = length - 1; index >= 0; index--) {
tasks.push({ kind: 'array-item', source: candidate, index, target })
append(tasks, { kind: 'array-item', source: candidate, index, target })
}
continue
}
@@ -153,13 +211,13 @@ export function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined
if (keys === undefined) return undefined
const target: Record<string, CodeJsonValue> = {}
assign(task.destination, target)
active.add(candidate)
tasks.push({ kind: 'leave', source: candidate })
setAdd(active, candidate)
append(tasks, { kind: 'leave', source: candidate })
for (let index = keys.length - 1; index >= 0; index--) {
const key = keys[index]
/* v8 ignore next -- the loop is bounded by the captured key count. */
if (key === undefined) return undefined
tasks.push({ kind: 'object-property', source: candidate as Record<string, unknown>, key, target })
append(tasks, { kind: 'object-property', source: candidate as Record<string, unknown>, key, target })
}
}
return root
@@ -192,29 +250,29 @@ export type WorkerJsonWire = WorkerJsonToken[]
export function encodeWorkerJson(value: CodeJsonValue): WorkerJsonWire {
const wire: WorkerJsonWire = []
const pending: CodeJsonValue[] = [value]
for (let current = pending.pop(); current !== undefined; current = pending.pop()) {
for (let current = takeLast(pending); current !== undefined; current = takeLast(pending)) {
if (current === null || typeof current === 'boolean' || typeof current === 'number' || typeof current === 'string') {
wire.push(current)
append(wire, current)
continue
}
if (Array.isArray(current)) {
wire.push({ kind: 'array', length: current.length })
if (intrinsicArrayIsArray(current)) {
append(wire, { kind: 'array', length: current.length })
for (let index = current.length - 1; index >= 0; index--) {
const item = current[index]
if (item === undefined) throw new Error('cannot encode a sparse JSON array')
pending.push(item)
if (item === undefined) throw new IntrinsicError('cannot encode a sparse JSON array')
append(pending, item)
}
continue
}
const keys = Object.keys(current)
wire.push({ kind: 'object', keys })
const keys = intrinsicObjectKeys(current)
append(wire, { kind: 'object', keys })
for (let index = keys.length - 1; index >= 0; index--) {
const key = keys[index]
/* v8 ignore next -- the loop is bounded by the captured key count. */
if (key === undefined) throw new Error('cannot encode a missing JSON object key')
if (key === undefined) throw new IntrinsicError('cannot encode a missing JSON object key')
const item = current[key]
if (item === undefined) throw new Error('cannot encode an undefined JSON object property')
pending.push(item)
if (item === undefined) throw new IntrinsicError('cannot encode an undefined JSON object property')
append(pending, item)
}
}
return wire
@@ -226,36 +284,46 @@ type DecodeFrame =
/** Whether an array contains exactly its dense indexed slots and `length`. */
function isDenseArray(value: unknown[]): boolean {
if (!hasPlainArrayPrototype(value) || Reflect.ownKeys(value).length !== value.length + 1) return false
if (!hasPlainArrayPrototype(value) || intrinsicReflectOwnKeys(value).length !== value.length + 1) return false
for (let index = 0; index < value.length; index++) {
if (!Object.hasOwn(value, index)) return false
if (!intrinsicObjectHasOwn(value, index)) return false
}
return true
}
/** Whether one exact string-key list contains a key, without consulting its prototype. */
function keysContain(keys: string[], expected: string): boolean {
for (let index = 0; index < keys.length; index++) {
if (keys[index] === expected) return true
}
return false
}
/** Return one exact container marker, or reject any extra/missing fields. */
function containerToken(value: object): ArrayWireToken | ObjectWireToken | undefined {
if (Array.isArray(value) || !hasPlainObjectPrototype(value)) return undefined
if (intrinsicArrayIsArray(value) || !hasPlainObjectPrototype(value)) return undefined
const keys = enumerableStringKeys(value)
if (keys === undefined) return undefined
const token = value as Record<string, unknown>
if (token.kind === 'array') {
if (keys.length !== 2 || !keys.includes('kind') || !keys.includes('length')) return undefined
if (keys.length !== 2 || !keysContain(keys, 'kind') || !keysContain(keys, 'length')) return undefined
const length = token.length
return typeof length === 'number' && Number.isSafeInteger(length) && length >= 0
return typeof length === 'number' && intrinsicNumberIsSafeInteger(length) && length >= 0
? { kind: 'array', length }
: undefined
}
if (token.kind === 'object') {
if (keys.length !== 2 || !keys.includes('kind') || !keys.includes('keys')) return undefined
if (keys.length !== 2 || !keysContain(keys, 'kind') || !keysContain(keys, 'keys')) return undefined
const objectKeys = token.keys
if (!Array.isArray(objectKeys) || !isDenseArray(objectKeys)) return undefined
const unique = new Set<string>()
if (!intrinsicArrayIsArray(objectKeys) || !isDenseArray(objectKeys)) return undefined
const unique = new IntrinsicSet<string>()
const normalizedKeys: string[] = []
for (const key of objectKeys as unknown[]) {
if (typeof key !== 'string' || unique.has(key)) return undefined
unique.add(key)
normalizedKeys.push(key)
const objectKeyValues = objectKeys as unknown[]
for (let index = 0; index < objectKeyValues.length; index++) {
const key = objectKeyValues[index]
if (typeof key !== 'string' || setHas(unique, key)) return undefined
setAdd(unique, key)
append(normalizedKeys, key)
}
return { kind: 'object', keys: normalizedKeys }
}
@@ -271,14 +339,14 @@ function containerToken(value: object): ArrayWireToken | ObjectWireToken | undef
*/
export function decodeWorkerJson(input: unknown): CodeJsonValue | undefined {
try {
if (!Array.isArray(input) || !isDenseArray(input) || input.length === 0) return undefined
if (!intrinsicArrayIsArray(input) || !isDenseArray(input) || input.length === 0) return undefined
const wire = input as unknown[]
const frames: DecodeFrame[] = []
let root: CodeJsonValue | undefined
let rootAssigned = false
const attach = (value: CodeJsonValue): boolean => {
const parent = frames.at(-1)
const parent = frames[frames.length - 1]
if (!parent) {
if (rootAssigned) return false
root = value
@@ -288,12 +356,12 @@ export function decodeWorkerJson(input: unknown): CodeJsonValue | undefined {
/* v8 ignore next -- completed frames are popped before another token can attach. */
if (parent.index >= (parent.kind === 'array' ? parent.length : parent.keys.length)) return false
if (parent.kind === 'array') {
parent.target.push(value)
append(parent.target, value)
} else {
const key = parent.keys[parent.index]
/* v8 ignore next -- object frames are built from validated keys and their exact length. */
if (key === undefined) return false
Object.defineProperty(parent.target, key, {
intrinsicObjectDefineProperty(parent.target, key, {
value,
enumerable: true,
configurable: true,
@@ -311,7 +379,7 @@ export function decodeWorkerJson(input: unknown): CodeJsonValue | undefined {
if (token === null || typeof token === 'boolean' || typeof token === 'string') {
value = token
} else if (typeof token === 'number') {
if (!Number.isFinite(token) || Object.is(token, -0)) return undefined
if (!intrinsicNumberIsFinite(token) || intrinsicObjectIs(token, -0)) return undefined
value = token
} else {
if (typeof token !== 'object') return undefined
@@ -331,13 +399,13 @@ export function decodeWorkerJson(input: unknown): CodeJsonValue | undefined {
}
}
if (!attach(value)) return undefined
if (frame) frames.push(frame)
if (frame) append(frames, frame)
while (frames.length > 0) {
const current = frames.at(-1)
const current = frames[frames.length - 1]
/* v8 ignore next -- the loop condition guarantees a final frame. */
if (current === undefined) break
if (current.index < (current.kind === 'array' ? current.length : current.keys.length)) break
frames.pop()
takeLast(frames)
}
}
return frames.length === 0 ? root : undefined

View File

@@ -68,4 +68,42 @@ describe('jsonValueBytesUpTo', () => {
expect(jsonValueBytesUpTo(value, 10_004)).toBe(10_004)
expect(jsonValueBytesUpTo(value, 10_003)).toBeUndefined()
})
it('uses module-captured intrinsics after model-visible globals are mutated', () => {
const value: CodeJsonValue = { payload: ['€', 42] }
const bytes = Buffer.byteLength(JSON.stringify(value), 'utf8')
const arrayIsArrayDescriptor = Object.getOwnPropertyDescriptor(Array, 'isArray')!
const arrayPopDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, 'pop')!
const arrayPushDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, 'push')!
const byteLengthDescriptor = Object.getOwnPropertyDescriptor(Buffer, 'byteLength')!
const objectKeysDescriptor = Object.getOwnPropertyDescriptor(Object, 'keys')!
const charCodeAtDescriptor = Object.getOwnPropertyDescriptor(String.prototype, 'charCodeAt')!
const codePointAtDescriptor = Object.getOwnPropertyDescriptor(String.prototype, 'codePointAt')!
const sliceDescriptor = Object.getOwnPropertyDescriptor(String.prototype, 'slice')!
let measured: number | undefined
let prefix = ''
try {
Array.isArray = (_value: unknown): _value is never[] => false
Array.prototype.pop = () => { throw new Error('mutated pop') }
Array.prototype.push = () => { throw new Error('mutated push') }
Buffer.byteLength = () => 0
Object.keys = () => []
String.prototype.charCodeAt = () => { throw new Error('mutated charCodeAt') }
String.prototype.codePointAt = () => { throw new Error('mutated codePointAt') }
String.prototype.slice = () => { throw new Error('mutated slice') }
measured = jsonValueBytesUpTo(value, bytes)
prefix = truncateJsonStringBytes('€x', 5)
} finally {
Object.defineProperty(Array, 'isArray', arrayIsArrayDescriptor)
Object.defineProperty(Array.prototype, 'pop', arrayPopDescriptor)
Object.defineProperty(Array.prototype, 'push', arrayPushDescriptor)
Object.defineProperty(Buffer, 'byteLength', byteLengthDescriptor)
Object.defineProperty(Object, 'keys', objectKeysDescriptor)
Object.defineProperty(String.prototype, 'charCodeAt', charCodeAtDescriptor)
Object.defineProperty(String.prototype, 'codePointAt', codePointAtDescriptor)
Object.defineProperty(String.prototype, 'slice', sliceDescriptor)
}
expect(measured).toBe(bytes)
expect(prefix).toBe('€')
})
})

View File

@@ -657,6 +657,41 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
})
})
it('preserves binding and completion JSON after model code mutates boundary globals', async () => {
const { runtime } = await setup()
const result = await runtime.run({
program: `
const arrayPrototype = Array.prototype;
const objectPrototype = Object.prototype;
const setPrototype = Set.prototype;
const stringPrototype = String.prototype;
Array.isArray = () => false;
arrayPrototype.at = arrayPrototype.includes = arrayPrototype.pop = arrayPrototype.push = () => { throw new Error('mutated array method') };
Object.defineProperty = Object.getOwnPropertyDescriptor = Object.getPrototypeOf = Object.keys = () => { throw new Error('mutated object method') };
Object.hasOwn = () => false;
Object.is = () => true;
objectPrototype.propertyIsEnumerable = () => false;
Number.isFinite = Number.isSafeInteger = () => false;
Reflect.apply = Reflect.ownKeys = () => { throw new Error('mutated reflect method') };
setPrototype.add = setPrototype.delete = setPrototype.has = () => { throw new Error('mutated set method') };
stringPrototype.charCodeAt = stringPrototype.codePointAt = stringPrototype.slice = () => { throw new Error('mutated string method') };
Buffer.byteLength = () => 0;
Function.prototype.toString = () => 'mutated';
globalThis.Array = globalThis.Buffer = globalThis.Function = globalThis.Number = globalThis.Object = globalThis.Reflect = globalThis.Set = globalThis.String = undefined;
const echoed = await tools.echo({ request: ['€', 1] });
return { echoed, completion: { ok: true, amount: 42 } };
`,
bindings: tools({ echo: async args => args }),
})
expect(result).toEqual({
logs: [],
value: {
echoed: { request: ['€', 1] },
completion: { ok: true, amount: 42 },
},
})
})
it('rejects forged lossy binding arguments again at the host boundary', async () => {
const { runtime } = await setup()
let calls = 0

View File

@@ -230,6 +230,7 @@ describe('flat worker JSON wire', () => {
[foreignMarker],
[hiddenMarker],
[{ kind: 'unknown' }],
[{ kind: 'array', bogus: 0 }],
[{ kind: 'array' }],
[{ kind: 'array', length: '1' }],
[{ kind: 'array', length: -1 }],