Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

### Bug Fixes

- **logic-engine:** prevent trigger sandbox evaluation from freezing host prototypes while preserving `return`-based logic scripts; reported in [#496](https://github.com/the-open-engine/zeroshot/pull/496).

# [5.3.0](https://github.com/covibes/zeroshot/compare/v5.2.1...v5.3.0) (2026-01-12)

### Bug Fixes
Expand Down
33 changes: 7 additions & 26 deletions src/logic-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ function buildAgentContext(agent) {
};
}

function getSafeBuiltins() {
return { Set, Map, Array, Object, String, Number, Boolean, Math, Date, JSON };
}

function getQuietConsole() {
return {
log: () => {},
Expand Down Expand Up @@ -114,33 +110,19 @@ class LogicEngine {
// Build sandbox context
const context = this._buildContext(agent, message);

// Create isolated context with frozen prototypes
// This prevents prototype pollution attacks
const isolatedContext = {};

// Freeze Object, Array, Function prototypes in the sandbox
isolatedContext.Object = Object.freeze({ ...Object });
isolatedContext.Array = Array;
isolatedContext.Function = Function;
// Contextify before execution so scripts use VM-owned globals instead of
// host constructors. Keep the function-body contract: trigger scripts use
// return statements throughout built-in templates and user configs.
const sandbox = { ...context };
vm.createContext(sandbox);

// Copy safe context properties
Object.assign(isolatedContext, context);

// Wrap script to prevent prototype access
const wrappedScript = `(function() {
'use strict';
// Prevent prototype pollution
const frozenObject = Object;
const frozenArray = Array;
Object.freeze(frozenObject.prototype);
Object.freeze(frozenArray.prototype);

${script}
})()`;

// Create and run in context
vm.createContext(isolatedContext);
const result = vm.runInContext(wrappedScript, isolatedContext, {
// Run in context
const result = vm.runInContext(wrappedScript, sandbox, {
timeout: this.timeout,
displayErrors: true,
});
Expand Down Expand Up @@ -168,7 +150,6 @@ class LogicEngine {
ledger: ledgerAPI,
cluster: buildClusterAPI(this.cluster, clusterId),
helpers: buildHelpers(ledgerAPI),
...getSafeBuiltins(),
console: getQuietConsole(),
};
}
Expand Down
37 changes: 37 additions & 0 deletions tests/integration/trigger-evaluation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

const assert = require('node:assert');
const { spawnSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const os = require('os');
Expand Down Expand Up @@ -417,6 +418,42 @@ function defineScriptValidationTests() {
assert.strictEqual(validResult.valid, true);
assert.strictEqual(invalidResult.valid, false);
});

it('should not freeze host prototypes when evaluating scripts', () => {
const childScript = `
const LogicEngine = require(${JSON.stringify(path.resolve(__dirname, '../../src/logic-engine'))});
const messageBus = {
query: () => [],
findLast: () => null,
count: () => 0,
since: () => [],
};
const engine = new LogicEngine(messageBus, { id: 'test-cluster', agents: [] });
const result = engine.evaluate(
'return true;',
{ id: 'evaluator', cluster_id: 'test-cluster' },
{ topic: 'TRIGGER' }
);
console.log(JSON.stringify({
result,
objectPrototypeFrozen: Object.isFrozen(Object.prototype),
arrayPrototypeFrozen: Object.isFrozen(Array.prototype),
}));
`;

const child = spawnSync(process.execPath, ['-e', childScript], {
encoding: 'utf8',
});

assert.strictEqual(child.status, 0, child.stderr || child.stdout);
const output = JSON.parse(child.stdout.trim());

assert.deepStrictEqual(output, {
result: true,
objectPrototypeFrozen: false,
arrayPrototypeFrozen: false,
});
});
});
}

Expand Down
Loading