A secure, lightweight sandbox for executing AI-generated JavaScript code in isolation. Available as both a React component and an imperative JavaScript API. Perfect for building Claude Artifacts-style experiences.
- ✅ Secure Execution - Runs untrusted AI code without XSS vulnerabilities using blob URLs and strict CSP
- ✅ Two-Way Bridge - Bidirectional communication for both events and function calls between parent and sandbox
- ✅ Self-Healing - Catches runtime errors with line numbers for AI feedback loops
- ✅ Chart.js Included - Pre-loaded Chart.js v4 for instant data visualization
- ✅ Zero Backend - Purely client-side, no Node.js runtime required
- ✅ TypeScript - Full type definitions included
- ✅ ~5KB gzipped - Minimal bundle size
# This package is not published yet, but when it is:
# npm install micro-ai-sandboximport { AgentCanvas } from 'micro-ai-sandbox';
import { useState } from 'react';
function App() {
const [code, setCode] = useState(`
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#4CAF50';
ctx.fillRect(100, 100, 200, 150);
`);
return (
<div style={{ width: '800px', height: '600px' }}>
<AgentCanvas
code={code}
allowNetwork={false}
allowCookies={false}
onError={(error) => {
console.error(`${error.type} at line ${error.line}: ${error.message}`);
}}
onMessage={(data) => {
console.log('Sandbox emitted:', data);
}}
/>
</div>
);
}import { Sandbox } from 'micro-ai-sandbox';
const sandbox = new Sandbox({
code: `
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 200, 100);
`,
onMessage: (data) => console.log('Message:', data),
onError: (error) => console.error('Error:', error)
});
// Mount to DOM
document.getElementById('container').appendChild(sandbox.getIframe());
// Clean up when done
sandbox.destroy();| Prop | Type | Default | Description |
|---|---|---|---|
code |
string |
required | JavaScript or HTML code to execute |
contentType |
'javascript' | 'html' |
'javascript' |
Type of content |
allowNetwork |
boolean |
false |
Allow fetch/XHR requests |
allowCookies |
boolean |
false |
Allow cookie access |
csp |
string |
optional | Custom CSP (overrides allow flags) |
onError |
(error: SandboxError) => void |
optional | Error callback |
onMessage |
(data: any) => void |
optional | Message callback |
onReady |
() => void |
optional | Ready callback |
hostFunctions |
Record<string, Function> |
optional | Functions callable from sandbox |
sandboxRef |
React.RefObject<SandboxHandle> |
optional | Ref to sandbox instance |
className |
string |
optional | CSS class for container |
style |
React.CSSProperties |
optional | Inline styles for container |
iframeStyle |
React.CSSProperties |
optional | Inline styles for iframe |
developerMode |
boolean |
false |
Show errors in UI overlay |
constructor(options: SandboxOptions)
interface SandboxOptions {
code: string;
contentType?: 'javascript' | 'html';
allowNetwork?: boolean;
allowCookies?: boolean;
csp?: string;
onError?: (error: SandboxError) => void;
onMessage?: (data: any) => void;
onReady?: () => void;
hostFunctions?: Record<string, (...args: any[]) => any | Promise<any>>;
container?: HTMLElement;
iframeStyle?: Partial<CSSStyleDeclaration>;
}Methods:
sendEvent(eventName: string, data?: any): void- Send event to sandboxcallFunction(functionName: string, args: any[]): Promise<any>- Call sandbox functionupdateCode(code: string): void- Update sandbox codegetIframe(): HTMLIFrameElement | null- Get iframe elementgetError(): SandboxError | null- Get current errordestroy(): void- Cleanup and destroy sandbox
Two-Way Bridge for both events and function calls - The sandbox provides four communication channels between parent and sandbox:
| Direction | Method | Description |
|---|---|---|
| Sandbox → Parent | window.emit(event, data) |
Send events/data from sandbox to parent (fire-and-forget) |
| Sandbox → Parent | host.functionName(args) |
Call parent functions from sandbox (returns Promise) |
| Parent → Sandbox | sandbox.sendEvent(event, data) |
Send events to sandbox (received as CustomEvent) |
| Parent → Sandbox | sandbox.callFunction(name, args) |
Call sandbox functions (returns Promise) |
Inside the sandbox, code has access to:
Use this to notify the parent of events or send data without expecting a response.
// In sandbox
window.emit('dataProcessed', { result: 42 });
window.emit('chartClicked', { bar: 3, value: 150 });
// In parent (React)
<AgentCanvas
onMessage={(data) => {
console.log(data.event); // 'dataProcessed'
console.log(data.data); // { result: 42 }
}}
/>
// In parent (Imperative)
const sandbox = new Sandbox({
code: '...',
onMessage: (data) => {
console.log(data.event, data.data);
}
});Use this when sandbox needs data or services from the parent. Returns a Promise.
// Parent provides host functions
const hostFunctions = {
fetchWeather: async (params) => {
const response = await fetch('/api/weather', {
method: 'POST',
body: JSON.stringify(params)
});
return response.json();
},
saveData: async (data) => {
localStorage.setItem('myData', JSON.stringify(data));
return { success: true };
}
};
// Sandbox code calls them (note: 'window.' is optional)
const weather = await host.fetchWeather({
location: 'New York',
startDate: '2024-12-01',
endDate: '2024-12-10'
});
const result = await window.host.saveData({ foo: 'bar' });The parent can also send events and call functions in the sandbox.
// Imperative API
const sandbox = new Sandbox({ code: '...' });
sandbox.sendEvent('updateTheme', { theme: 'dark' });
// React API
const sandboxRef = useRef<SandboxHandle>(null);
sandboxRef.current?.sendEvent('updateTheme', { theme: 'dark' });
// Sandbox receives as CustomEvent
window.addEventListener('updateTheme', (event) => {
console.log('Theme changed:', event.detail); // { theme: 'dark' }
document.body.style.background = event.detail.theme === 'dark' ? '#000' : '#fff';
});// Sandbox defines functions on window
window.getData = () => {
return { temperature: 72, humidity: 65 };
};
window.processData = async (input) => {
// Simulate async work
await new Promise(r => setTimeout(r, 100));
return { result: input * 2 };
};
// Imperative API - Parent calls these functions
const sandbox = new Sandbox({ code: '...' });
const data = await sandbox.callFunction('getData', []);
console.log(data); // { temperature: 72, humidity: 65 }
const result = await sandbox.callFunction('processData', [42]);
console.log(result); // { result: 84 }
// React API - Using sandboxRef
const sandboxRef = useRef<SandboxHandle>(null);
const data = await sandboxRef.current?.callFunction('getData', []);All code is wrapped in an async context, enabling top-level await:
// ✅ This works!
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// ✅ Call host functions with await
const result = await host.myFunction(arg1, arg2);
// ✅ Proper error handling
try {
const data = await someAsyncOperation();
} catch (error) {
console.error('Async error:', error);
}function WeatherApp() {
const hostFunctions = {
fetchWeather: async (params) => {
const response = await fetch('/api/weather', {
method: 'POST',
body: JSON.stringify(params)
});
return response.json();
}
};
const code = `
// Call host function from sandbox
const weather = await host.fetchWeather({
location: 'New York',
startDate: '2024-12-01',
endDate: '2024-12-10',
variables: ['temperature_2m_max']
});
// Create visualization
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
weather.data.daily.temperature_2m_max.forEach((temp, i) => {
ctx.fillRect(50 + i * 20, 500 - temp * 3, 15, temp * 3);
});
`;
return <AgentCanvas code={code} hostFunctions={hostFunctions} />;
}function InteractiveChart() {
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const code = `
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const chart = new Chart(canvas, {
type: 'bar',
data: {
labels: ['Product A', 'Product B', 'Product C'],
datasets: [{
label: 'Sales',
data: [65, 59, 80],
backgroundColor: 'rgba(75, 192, 192, 0.5)'
}]
},
options: {
onClick: (event, elements) => {
if (elements.length > 0) {
const index = elements[0].index;
const label = chart.data.labels[index];
window.emit('barClicked', { product: label });
}
}
}
});
`;
return (
<div>
{selectedItem && <p>You clicked: {selectedItem}</p>}
<AgentCanvas
code={code}
onMessage={(data) => {
if (data.event === 'barClicked') {
setSelectedItem(data.data.product);
}
}}
/>
</div>
);
}const htmlCode = `
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 40px;
}
.card {
background: rgba(255,255,255,0.1);
padding: 20px;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="card">
<h1>Dashboard</h1>
<button onclick="window.emit('buttonClicked', {time: Date.now()})">
Click Me
</button>
</div>
</body>
</html>
`;
<AgentCanvas
contentType="html"
code={htmlCode}
onMessage={(data) => console.log('Button clicked:', data.time)}
/>Here's a sample system prompt for AI models generating code for micro-ai-sandbox:
You are a visualization assistant that generates JavaScript code to run in a sandbox.
When generating visualizations:
- Use vanilla JavaScript and Canvas API or Chart.js (pre-loaded)
- Code runs in a sandboxed environment with access to standard Web APIs
- All code supports top-level await (wrapped in async context)
Available sandbox APIs:
1. host.functionName(args) - Call parent functions (returns Promise)
Example: const data = await host.fetchData({ query: 'sales' });
2. window.emit(eventName, data) - Send events to parent (fire-and-forget)
Example: window.emit('chartClicked', { bar: 3, value: 150 });
3. Standard Web APIs - Canvas, DOM, console, setTimeout, fetch (if enabled)
Example code structure:
```javascript
// Fetch data from parent if needed
const data = await host.fetchData({ type: 'sales', range: '2024' });
// Create canvas
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
// Draw visualization
data.values.forEach((value, i) => {
ctx.fillStyle = '#4CAF50';
ctx.fillRect(50 + i * 30, 500 - value * 2, 25, value * 2);
});
// Send event when user interacts
canvas.addEventListener('click', (e) => {
const barIndex = Math.floor((e.offsetX - 50) / 30);
window.emit('barClicked', { index: barIndex, value: data.values[barIndex] });
});
- Blob URL Isolation - Each execution runs in a unique origin
- Content Security Policy - Strict CSP headers block unauthorized access
- Iframe Sandbox - Restrictive sandbox attributes prevent dangerous operations
- Cookie Blocking - Optional
document.cookieaccess prevention - Network Blocking - Optional fetch/XHR blocking
- Message Validation - postMessage origin validation
For fine-grained control:
const sandbox = new Sandbox({
csp: [
"default-src 'none'",
"script-src 'unsafe-inline' 'unsafe-eval'",
"connect-src https://api.example.com",
"img-src * data: blob:",
"style-src 'unsafe-inline'"
].join('; '),
code: yourCode
});Note: When csp is provided, allowNetwork and allowCookies are ignored.
Built on the "Micro-Sandbox" pattern using blob URLs for maximum security with minimal overhead:
- Zero runtime dependencies (React is peer dependency)
- ~5KB gzipped
- No build step required for consumers
- Chart.js loaded from CDN on demand
# Install dependencies
npm install
# Build for production
npm run build
# Type checking
npm run typecheck
# Development mode with watch
npm run devSee examples/ directory for complete working examples including:
- React integration
- Weather data visualization with host functions
- AI agent integration
- Interactive charts
MIT
Made for building Claude Artifacts-style experiences. Perfect for AI-powered data visualization, interactive demos, and secure code execution.
