How to Safely Use eval() in Chrome Extensions with Sandboxed Iframes (2026)
The eval() Problem in Chrome Extensions
Chrome extensions enforce a strict Content Security Policy that blocks eval(), new Function(), and setTimeout/setInterval with string arguments. This protects users from code injection attacks, but it also breaks templating libraries (Handlebars, Underscore templates) and other tools that rely on dynamic code generation.
The solution: sandboxed iframes. They run in an isolated origin where eval() is allowed, but they can’t access chrome.* APIs or the extension’s privileged context.
Why Not Just Allow eval()?
Allowing eval() in your extension’s main context would give dynamically generated code full access to all chrome APIs, stored data, and permissions your extension holds. If any user input or external data reaches eval(), it becomes a severe security vulnerability.
Sandboxing separates concerns: dangerous operations run in isolation, results are passed back via postMessage().
Step 1: Declare Sandboxed Pages
{
"manifest_version": 3,
"name": "Template Engine Demo",
"version": "1.0",
"sandbox": {
"pages": ["sandbox.html"]
},
"action": {
"default_popup": "popup.html"
}
}Step 2: Create the Sandbox
sandbox.html
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.min.js"></script>
</head>
<body>
<script type="text/x-handlebars-template" id="user-card">
<div class="card">
<h2>{{name}}</h2>
<p>{{email}}</p>
{{#if premium}}<span class="badge">Premium</span>{{/if}}
<ul>
{{#each extensions}}
<li>{{this.name}} - {{this.users}} users</li>
{{/each}}
</ul>
</div>
</script>
<script type="text/x-handlebars-template" id="stats-dashboard">
<div class="stats">
<h3>{{title}}</h3>
<p>Total installs: {{formatNumber installs}}</p>
<p>Rating: {{rating}}/5</p>
</div>
</script>
<script src="sandbox.js"></script>
</body>
</html>sandbox.js
// Pre-compile all templates
const templates = {};
document.querySelectorAll('script[type="text/x-handlebars-template"]').forEach((el) => {
templates[el.id] = Handlebars.compile(el.innerHTML);
});
// Register custom helpers
Handlebars.registerHelper('formatNumber', (num) => {
return new Handlebars.SafeString(Number(num).toLocaleString());
});
// Listen for render requests from the extension
window.addEventListener('message', (event) => {
const { templateId, context, requestId } = event.data;
if (!templateId || !templates[templateId]) {
event.source.postMessage({
requestId,
error: `Template "${templateId}" not found`
}, event.origin);
return;
}
try {
const html = templates[templateId](context);
event.source.postMessage({ requestId, html }, event.origin);
} catch (error) {
event.source.postMessage({
requestId,
error: error.message
}, event.origin);
}
});Step 3: Communicate from Extension Pages
popup.html
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 350px; padding: 16px; font-family: system-ui; }
#output { margin-top: 12px; }
.card h2 { margin: 0 0 4px; }
.badge { background: #22c55e; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; }
</style>
</head>
<body>
<iframe id="sandbox" src="sandbox.html" style="display:none"></iframe>
<div id="output">Loading...</div>
<script src="popup.js"></script>
</body>
</html>popup.js
const sandbox = document.getElementById('sandbox');
const pendingRequests = new Map();
let requestCounter = 0;
window.addEventListener('message', (event) => {
const { requestId, html, error } = event.data;
const resolver = pendingRequests.get(requestId);
if (resolver) {
pendingRequests.delete(requestId);
if (error) {
resolver.reject(new Error(error));
} else {
resolver.resolve(html);
}
}
});
function renderTemplate(templateId, context) {
return new Promise((resolve, reject) => {
const requestId = ++requestCounter;
pendingRequests.set(requestId, { resolve, reject });
sandbox.contentWindow.postMessage(
{ templateId, context, requestId },
'*'
);
});
}
// Usage
async function init() {
const html = await renderTemplate('user-card', {
name: 'Jane Developer',
email: 'jane@example.com',
premium: true,
extensions: [
{ name: 'Tab Manager', users: 15000 },
{ name: 'Dark Reader', users: 85000 }
]
});
document.getElementById('output').innerHTML = html;
}
sandbox.addEventListener('load', init);What Can and Can’t Run in the Sandbox
| Capability | Sandbox | Extension Page |
|---|---|---|
eval() | Yes | No |
new Function() | Yes | No |
chrome.* APIs | No | Yes |
| DOM access (own page) | Yes | Yes |
| Network requests | Yes | Yes |
postMessage | Yes | Yes |
| Extension storage | No | Yes |
Security Considerations
- Sanitize HTML output — Even though the sandbox is isolated, rendered HTML gets injected into your extension page. Use
textContentinstead ofinnerHTMLwhen possible, or sanitize with DOMPurify. - Validate message origins — Check
event.originin your message handlers. - Don’t pass functions —
postMessage()only serializes data. Functions, DOM nodes, and class instances can’t cross the boundary. - Limit template complexity — Complex templates with many helpers increase the attack surface.
Beyond Templating
The sandbox pattern works for any CSP-incompatible code:
- Markdown parsers that use
eval()internally - Math expression evaluators
- Custom scripting languages or DSLs
- Legacy libraries that rely on dynamic code generation
What’s Next
Sandboxed iframes let you use powerful dynamic code features without compromising your extension’s security. Keep privileged operations in the main context, dangerous code in the sandbox, and communicate through structured messages.
Build secure, feature-rich extensions with ExtensionBooster.
Share this article
Build better extensions with free tools
Icon generator, MV3 converter, review exporter, and more — no signup needed.
Related Articles
I Built the Same Chrome Extension With 5 Different Frameworks. Here's What Actually Happened.
WXT vs Plasmo vs CRXJS vs Extension.js vs Bedframe. Real benchmarks, honest opinions, and the framework with 12K stars that's quietly dying.
5 Best Email Marketing Services to Grow Your Chrome Extension (2026)
Compare the top email marketing platforms for SaaS and Chrome extension developers. MailerLite, Mailchimp, Brevo, ActiveCampaign, and Drip reviewed.
15 Best Practices to Build a Browser Extension That Users Love (2026 Guide)
Master browser extension development in 2026. Manifest V3, security, performance, and UX best practices to build extensions users love.