How the Preview Sandbox Protects Your Saved Projects

How the Preview Sandbox Protects Your Saved Projects

●2 ●8 ●35
calendar_today ago • schedule4 min read
— Originally published at nitroide.com

NitroIDE's best feature is also its scariest one, and I mean that as a compliment to the feature.

Send someone a link — nitroide.com/tools/codebox.html?code=... — and they get a running app instantly. No signup, no install, no "clone this repo first". The whole workspace is compressed into the URL itself, so the person who opens it is looking at live, editable code in about a second.

Now think about what that link actually is. It is a URL, from anyone, that runs arbitrary JavaScript in your browser the moment you open it. That is the deal. If you build a tool that runs strangers' code on click, the security of that tool isn't a feature you add later. It is the tool.

So here is the honest version of how NitroIDE keeps a shared link from ever touching your saved projects — including the parts the sandbox does not protect you from.

What's actually at stake

Your saved projects live in your browser's localStorage, under keys like nitro_projects. That's the whole database: no server, no account, no cloud copy. Which means a malicious share link doesn't need to hack a server to steal your work. It just needs to run JavaScript that can reach your localStorage.

In a browser, what decides whether code can reach another page's storage is the origin — the combination of protocol, host, and port. JavaScript running on nitroide.com can read nitroide.com's localStorage. JavaScript running anywhere else cannot. The preview pane runs your code. So the entire security question reduces to one thing: which origin does the preview run on?

If the answer were "nitroide.com", a malicious link could read every project you've ever saved. The v25 audit found exactly that hole: the preview iframe's sandbox included allow-same-origin, which gave stranger code our origin privileges. That was the first critical finding, and the fix was removing one attribute value.

The one line that does the work

Today the preview iframe is this, verified in the repo:

<iframe id="liveIframe" title="Live preview"
  sandbox="allow-scripts allow-modals allow-popups allow-forms"></iframe>

Read that list slowly, because the important part is what's missing from it. allow-scripts lets the preview run JavaScript at all — without it there is no live preview. allow-modals lets your code call alert() and confirm(). allow-popups lets links open in a new tab. allow-forms lets forms submit. And allow-same-origin is nowhere in the list. I grepped the whole codebase to be sure: zero occurrences.

Without allow-same-origin, the browser gives the iframe an opaque origin — a unique, throwaway origin that matches nothing, not even itself on reload. Code inside it cannot touch the parent page's DOM. It cannot read nitroide.com's localStorage, cookies, or IndexedDB. It cannot even tell what origin it is running under. Your saved projects are in a different universe as far as it is concerned, and no amount of clever JavaScript inside the preview changes that, because the boundary is enforced by the browser, not by my code.

How the preview still works without origin privileges

A sandbox that strict raises an obvious question: if the preview can't talk to the parent page, how do the editor, preview, and console stay in sync?

The answer is that they don't share memory — they pass messages. The parent feeds the preview its content by setting the iframe's srcdoc property, which is allowed even on an opaque origin. Then the two sides communicate through postMessage: the parent sends update-css and eval messages down, the preview sends console logs and error reports back up. That is the whole bridge. It is narrow on purpose — a handful of typed messages, not a shared object graph.

And the distrust runs both ways. The parent doesn't blindly trust what comes back from the preview either. Messages arriving from the iframe go through a strict sanitizer that allowlists only the exact HTML the console legitimately produces, with everything else escaped and every field validated. The v25 audit found the parent rendering those messages into innerHTML unescaped — a crafted link could have gotten code executed in the parent page itself. That's fixed, and it's the same principle: the preview is untrusted code, so even its console output is treated as hostile input until proven otherwise.

What the sandbox does not protect you from

I want to be straight about the limits, because a sandbox that sounds absolute is a lie.

The preview can still make network requests. Sandboxed code with allow-scripts can fetch() an external URL, which means a malicious link could exfiltrate anything you type into the preview itself — that's why you shouldn't paste secrets into a demo someone sent you. It can also render a fake login form inside the preview pane, pixel-perfect, because phishing doesn't need origin privileges. And allow-popups means it can open new tabs. The sandbox protects your saved data, not your judgment.

So the practical rule is boring and unchanged: treat a share link from a stranger the same way you'd treat any link from a stranger. The sandbox is the reason clicking one is safe for your projects. It is not a reason to type your password into it.

The tradeoff, stated plainly

There is one real cost. Code inside the preview that touches localStorage will throw — in Safari, notably — because an opaque origin has no storage to touch. A demo that saves a high score or persists a theme will break inside the preview. That's the correct posture, and it's the same one CodePen and CodeSandbox take: I'd rather break a convenience than leave a theft vector open. Your own code, running in your own editor session outside the preview, is unaffected — this only applies to the sandboxed pane.

The feature you're proudest of is the one you should attack first. Share links are NitroIDE's flagship, so they got the hardest look, and the answer turned out to be one missing attribute value and a browser-enforced wall behind it. Your projects stay yours — not because I promise it, but because the preview literally cannot see them.

I build NitroIDE — a browser-based IDE where your code never leaves your machine: https://nitroide.com?utm_source=coderlegion&utm_medium=article&utm_campaign=daily-2026-11-12

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

AAIF Hits 10 Projects. Its CTO on What to Adopt Now and the Security Gaps Left Open

Tom Smithverified - Oct 23

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

Comparison: Universal Import vs. Plaid/Yodlee

Pocket Portfolio - Mar 12

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23
chevron_left
656 Points • 45 Badges
India • nitroide.com
18Posts
7Comments
5Connections
I’m a full-stack developer passionate about developer experience, performance, and modern web engine... Show more

Related Jobs

Commenters (This Week)

1 comment
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!