Skip to content

Commit 47a2ce2

Browse files
authored
refactor: replace eval with ShadowRealm for safe expression evaluation (#103)
2 parents b458181 + 099e569 commit 47a2ce2

File tree

1 file changed

+45
-3
lines changed

1 file changed

+45
-3
lines changed

examples/tools-example.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ import * as dotenv from 'dotenv';
2323
import { z } from 'zod/v4';
2424
import { OpenRouter, ToolType } from '../src/index.js';
2525

26+
// Type declaration for ShadowRealm (TC39 Stage 3 proposal)
27+
// See: https://tc39.es/proposal-shadowrealm/
28+
declare class ShadowRealm {
29+
constructor();
30+
evaluate(sourceText: string): unknown;
31+
importValue(specifier: string, bindingName: string): Promise<unknown>;
32+
}
33+
2634
dotenv.config();
2735

2836
const client = new OpenRouter({
@@ -163,6 +171,41 @@ async function generatorToolExample() {
163171
const _message = await response.getMessage();
164172
}
165173

174+
/**
175+
* Safely evaluate a math expression using ShadowRealm
176+
*
177+
* ShadowRealm provides a secure, isolated JavaScript execution environment
178+
* that prevents access to the host realm's globals (no access to DOM, fetch,
179+
* filesystem, etc.). Combined with input validation, this makes it safe for
180+
* evaluating untrusted math expressions.
181+
*
182+
* Note: ShadowRealm isolates from host APIs but does not prevent DoS vectors
183+
* like infinite loops. For production use, consider additional safeguards.
184+
*
185+
* Browser support: Chrome 124+, Edge 124+, Firefox 128+, Safari 18+
186+
* Node.js support: Available behind --experimental-shadow-realm flag (v19+)
187+
* See: https://tc39.es/proposal-shadowrealm/
188+
*/
189+
function safeEvaluateMath(expression: string): number {
190+
// Only allow digits, whitespace, basic operators, decimal point, and parentheses
191+
if (!/^[\d\s+\-*/.()]+$/.test(expression)) {
192+
throw new Error('Expression contains invalid characters');
193+
}
194+
195+
// ShadowRealm creates a separate realm with its own global object, preventing
196+
// access to the host environment's APIs (no DOM, no Node.js APIs, no fetch, etc.)
197+
const realm = new ShadowRealm();
198+
199+
// The expression is evaluated in complete isolation
200+
const result = realm.evaluate(expression);
201+
202+
if (typeof result !== 'number' || !Number.isFinite(result)) {
203+
throw new Error('Expression did not evaluate to a finite number');
204+
}
205+
206+
return result;
207+
}
208+
166209
/**
167210
* Example 3: Manual Tool Execution
168211
* Define a tool without execute function for manual handling
@@ -203,10 +246,9 @@ async function manualToolExample() {
203246
}
204247
).expression;
205248

206-
// In a real app, you would safely evaluate this
207-
// For demo purposes only - don't use eval in production!
249+
// Safely evaluate the math expression using ShadowRealm
208250
try {
209-
const _result = eval(expression);
251+
const _result = safeEvaluateMath(expression);
210252
} catch (_error) {}
211253
}
212254
}

0 commit comments

Comments
 (0)