UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

255 lines (254 loc) • 14.5 kB
import assert from "node:assert/strict"; import { thrownText } from "../core/thrown-value.js"; const META = "export const meta = { name: 'conformance', description: 'sandbox escape probes' }\n"; const WITNESS_FN = "__probeWitness"; const WITNESS_PRELUDE = `function ${WITNESS_FN}(v) {\n` + ` if (v === null || v === undefined) return null\n` + ` var t = typeof v\n` + ` if (t === 'function') return { hostCapabilityReached: true, via: 'function' }\n` + ` if (t !== 'object') return null\n` + ` var names = ['pid', 'env', 'version', 'mainModule', 'binding']\n` + ` var hit = []\n` + ` for (var i = 0; i < names.length; i++) { try { if (names[i] in v) hit.push(names[i]) } catch (err) { } }\n` + ` return hit.length > 0 ? { hostCapabilityReached: true, via: 'object', keys: hit } : null\n` + `}\n`; const ESCAPE_BODIES = [ `return ${WITNESS_FN}(agent.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`, `return ${WITNESS_FN}(log.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`, `return ${WITNESS_FN}(budget.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`, `return ${WITNESS_FN}(({}).constructor.constructor("return typeof process !== 'undefined' ? process : null")())`, `return ${WITNESS_FN}(args.constructor.constructor("return typeof require !== 'undefined' ? require : null")())`, `const r = await agent({ objective: 'x' }); return ${WITNESS_FN}((r ?? {}).constructor.constructor("return typeof require !== 'undefined' ? require : null")())`, `return ${WITNESS_FN}(typeof process !== 'undefined' ? process : null)`, `return ${WITNESS_FN}(typeof require !== 'undefined' ? require : null)`, `return ${WITNESS_FN}((typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null)`, ]; const ERROR_PATH_BODIES = [ `try { await agent({ objective: 'x' }); return null } catch (e) { return ${WITNESS_FN}(e.constructor.constructor("return typeof process !== 'undefined' ? process : null")()) }`, `try { await parallel([]); return null } catch (e) { return ${WITNESS_FN}(e.constructor.constructor("return typeof require !== 'undefined' ? require : null")()) }`, `try { await agent({}); return null } catch (e) { return ${WITNESS_FN}((e && e.constructor && e.constructor.constructor) ? e.constructor.constructor("return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null")() : null) }`, ]; const POLLUTION_BODIES = [ `return JSON.parse('{"__proto__":{"polluted":1}}')`, `const o = {}; Reflect.defineProperty(o, "__proto__", { value: { polluted: 1 }, enumerable: true, configurable: true }); return o`, `return { nested: JSON.parse('{"__proto__":{"polluted":1}}') }`, `return { constructor: { prototype: { polluted: 1 } } }`, `return [JSON.parse('{"__proto__":{"polluted":1}}')]`, ]; const SIGNAL_BODIES = [ `return ${WITNESS_FN}((typeof signal !== 'undefined' && signal) ? signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null)`, `return ${WITNESS_FN}((typeof input !== 'undefined' && input && input.signal) ? input.signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null)`, `try { return ${WITNESS_FN}((typeof signal !== 'undefined' && signal) ? (signal.addEventListener ? signal.addEventListener.constructor("return typeof process !== 'undefined' ? process : null")() : null) : null) } catch (e) { return null }`, ]; function benignPrimitives() { return { agent: async () => ({ status: "completed", result: "ok" }), parallel: async (thunks) => Promise.all(thunks.map((t) => t())), pipeline: async (items) => items, phase: async (_title, body) => (body === undefined ? undefined : body()), log: () => { }, budget: { total: null, spent: () => 0, remaining: () => Infinity }, }; } function throwingPrimitives() { const boom = () => { throw new Error("host-side primitive failure"); }; return { agent: async () => boom(), parallel: async () => boom(), pipeline: async () => boom(), phase: async () => boom(), log: () => { }, budget: { total: null, spent: () => 0, remaining: () => Infinity }, }; } const DANGEROUS_OWN_KEYS = new Set(["__proto__", "constructor", "prototype"]); function graphHasInjectedProtoKey(v, seen = new Set()) { if (v == null || typeof v !== "object") return false; if (seen.has(v)) return false; seen.add(v); for (const k of Object.getOwnPropertyNames(v)) { if (DANGEROUS_OWN_KEYS.has(k)) { const val = Object.getOwnPropertyDescriptor(v, k)?.value; if (val != null && typeof val === "object") return true; } } for (const k of Object.getOwnPropertyNames(v)) { if (DANGEROUS_OWN_KEYS.has(k)) continue; if (graphHasInjectedProtoKey(v[k], seen)) return true; } return false; } function looksLikeHostCapability(v) { if (v == null) return false; if (typeof v === "function") return true; if (typeof v === "object") { const o = v; return "pid" in o || "env" in o || "version" in o || "mainModule" in o || "binding" in o; } return false; } function witnessReportsHostCapability(v) { return typeof v === "object" && v !== null && v.hostCapabilityReached === true; } function reachedHostCapability(v) { return looksLikeHostCapability(v) || witnessReportsHostCapability(v); } const CANARY_MARKER = "workflow-conformance-canary"; let canarySeq = 0; const CANARY_BODY = `const c = args.canary\n` + `return { marker: '${CANARY_MARKER}', nonce: c.nonce, sum: c.x + c.y, joined: ['${CANARY_MARKER}', c.nonce].join(':') }`; async function runLivenessCanary(runner) { const nonce = `n${++canarySeq}`; const scriptArgs = { canary: { nonce, x: 17, y: 25 } }; let result; try { const out = await runner.run({ scriptSource: META + CANARY_BODY, primitives: benignPrimitives(), scriptArgs }); result = out.result; } catch (err) { return { ok: false, detail: `run() REJECTED with: ${thrownText(err)}` }; } const expected = { marker: CANARY_MARKER, nonce, sum: 42, joined: `${CANARY_MARKER}:${nonce}` }; try { assert.deepEqual(result, expected); } catch { return { ok: false, detail: `run() returned ${JSON.stringify(result)} — expected ${JSON.stringify(expected)}` }; } return { ok: true, detail: "" }; } async function runProbeBody(runner, scriptSource, primitives, family, signal) { try { const out = await runner.run({ scriptSource, primitives, scriptArgs: { probe: true }, signal }); return { executed: true, result: out.result }; } catch (err) { const live = await runLivenessCanary(runner); assert.ok(live.ok, `${family}: the probe threw AND the runner then failed the liveness canary — this runner is BROKEN, ` + `not blocking, so nothing about its membrane was proven. probe: ${scriptSource.slice(META.length)} | ` + `probe threw: ${thrownText(err)} | canary: ${live.detail}`); return { executed: false }; } } export async function assertWorkflowPrimitivesWiring(runner) { assert.equal(runner.safeForUntrustedScripts, true, "wiring conformance targets a HARD runner (safeForUntrustedScripts must be true)"); const hostSeen = { phaseCalls: [], scopedBodyRan: false, logs: [] }; const primitives = { agent: async () => ({ status: "completed", result: "ok" }), parallel: async (thunks) => Promise.all(thunks.map((t) => t())), pipeline: async (items, ...stages) => { let out = items; for (const st of stages) { out = await Promise.all(out.map((x, i) => st(x, x, i))); } return out; }, phase: async (title, body) => { hostSeen.phaseCalls.push({ title, hasBody: body !== undefined }); if (body === undefined) return undefined; const out = await body(); if (title === "wiring-scoped") hostSeen.scopedBodyRan = true; return out; }, log: (message) => void hostSeen.logs.push(message), budget: { total: 1000, spent: () => 25, remaining: () => 975 }, }; const scriptSource = META + [ `const report = { typeofs: { agent: typeof agent, parallel: typeof parallel, pipeline: typeof pipeline, phase: typeof phase, log: typeof log, budgetRemaining: budget && typeof budget.remaining }, calls: {} }`, `log('wiring-probe-log')`, `report.calls.bareUndefined = (await phase('wiring-marker')) === undefined`, `let scopedRan = false`, `await phase('wiring-scoped', async () => { scopedRan = true })`, `report.calls.scopedRan = scopedRan`, `report.calls.budgetRemaining = budget.remaining()`, `const par = await parallel([async () => 1, async () => 2])`, `report.calls.parallelOk = Array.isArray(par) && par.length === 2`, `const pipe = await pipeline([1, 2], async (x) => x)`, `report.calls.pipelineOk = Array.isArray(pipe) && pipe.length === 2`, `const a = await agent({ objective: 'wiring' })`, `report.calls.agentOk = a && a.status === 'completed'`, `return report`, ].join("\n"); const out = await runner.run({ scriptSource, primitives, scriptArgs: {} }); const report = out.result; assert.ok(report && report.typeofs && report.calls, "wiring probe returned no report — script body did not run"); for (const k of ["agent", "parallel", "pipeline", "phase", "log", "budgetRemaining"]) { assert.equal(report.typeofs[k], "function", `primitive "${k}" is not a function INSIDE the isolate — injection table incomplete (the missing-injection failure mode)`); } assert.equal(report.calls.bareUndefined, true, "bare phase(title) must return undefined in-isolate"); assert.equal(report.calls.scopedRan, true, "scoped phase body did not run in-isolate"); assert.equal(report.calls.budgetRemaining, 975, "budget.remaining() is not bridged to the live host accessor"); assert.equal(report.calls.parallelOk, true, "parallel() thunks not executed"); assert.equal(report.calls.pipelineOk, true, "pipeline() stages not executed"); assert.equal(report.calls.agentOk, true, "agent() result not bridged back"); assert.deepEqual(hostSeen.phaseCalls.map((c) => `${c.title}:${c.hasBody ? "scoped" : "bare"}`), ["wiring-marker:bare", "wiring-scoped:scoped"], "phase() calls did not reach the HOST primitive (bridge severed — phases would record [])"); assert.equal(hostSeen.scopedBodyRan, true, "the scoped body's completion did not round-trip through the host phase()"); assert.deepEqual(hostSeen.logs, ["wiring-probe-log"], "log() did not reach the HOST primitive"); } export async function assertWorkflowSandboxConformance(runner) { assert.equal(runner.safeForUntrustedScripts, true, "conformance is only meaningful for a HARD runner (safeForUntrustedScripts must be true)"); const live = await runLivenessCanary(runner); assert.ok(live.ok, `POSITIVE CONTROL FAILED — this runner did not execute the canary script and return its value, so the ` + `escape battery below cannot certify anything about it (a runner that runs nothing produces the same ` + `"nothing leaked" results as a perfect sandbox). ${live.detail}`); for (const escBody of ESCAPE_BODIES) { const out = await runProbeBody(runner, META + WITNESS_PRELUDE + escBody, benignPrimitives(), "sandbox escape"); if (!out.executed) continue; assert.equal(reachedHostCapability(out.result), false, `sandbox escape REACHED a host capability via: ${escBody}`); } for (const errBody of ERROR_PATH_BODIES) { const out = await runProbeBody(runner, META + WITNESS_PRELUDE + errBody, throwingPrimitives(), "error-path"); if (!out.executed) continue; assert.equal(reachedHostCapability(out.result), false, `error-path: a host-side primitive error leaked a host capability via: ${errBody}`); } for (const polBody of POLLUTION_BODIES) { const out = await runProbeBody(runner, META + polBody, benignPrimitives(), "prototype pollution"); if (!out.executed) continue; assert.equal(graphHasInjectedProtoKey(out.result), false, `prototype pollution: the marshalled result carries an injected __proto__/constructor/prototype own key via: ${polBody}`); } const hostSignal = new AbortController().signal; for (const sigBody of SIGNAL_BODIES) { const out = await runProbeBody(runner, META + WITNESS_PRELUDE + sigBody, benignPrimitives(), "signal-path", hostSignal); if (!out.executed) continue; assert.equal(reachedHostCapability(out.result), false, `signal-path: the host AbortSignal leaked a host capability via: ${sigBody}`); } } const DETERMINISM_FORBIDDEN_BODIES = [ `try { return Date.now() } catch { return "threw" }`, `try { return Math.random() } catch { return "threw" }`, `try { return new Date().getTime() } catch { return "threw" }`, `try { return new Date(0).constructor.now() } catch { return "threw" }`, ]; export async function assertWorkflowDeterminism(runner) { for (const detBody of DETERMINISM_FORBIDDEN_BODIES) { const scriptSource = META + detBody; let threw = false; let result; try { const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true } }); result = out.result; } catch { threw = true; } assert.equal(threw || result === "threw", true, `determinism: ${detBody.trim().split("\n")[0]} did NOT throw — a clock/randomness read would silently bust resume`); } const out = await runner.run({ scriptSource: META + `return new Date(0).getTime()`, primitives: benignPrimitives(), scriptArgs: { probe: true } }); assert.equal(out.result, 0, "determinism: DATED construction new Date(0) must stay available"); }