All posts

Is It Safe to Host HTML That Runs Its Own JavaScript?

I let people host any HTML they generate with AI, JavaScript and all, without reading it first. Here is exactly how I run untrusted code safely: a null-origin sandbox, a separate cookieless origin, and an honest account of what it does not protect against, including cryptominers and browser zero-days.

July 8, 2026 · 6 min read

The usual answer to whether it is safe to host HTML that runs its own JavaScript is to clean the HTML first: strip the scripts, filter the scary parts, let the rest through. I think that puts the safety in the wrong place.

I built ShareMyPage, which hosts any HTML people generate with AI, JavaScript included, and most of it was never read line by line by anyone. Running strangers' code is the whole product. So the question I had to answer is what stops one of those pages from stealing a login, or going after the person who opens it.

My answer: I don't try to make the HTML safe. I assume every page is out to get someone, and I make that assumption not matter.

Sanitizing the HTML is the wrong fix

I decided against scrubbing uploads, for two reasons.

The first is that it would gut the product. The pages people share pull live data and draw charts, they're prototypes you click through, they're small tools that do something. The JavaScript is the point. Strip it and a working page becomes a screenshot.

The second is that sanitizing untrusted HTML is a fight you lose eventually. You're trying to list every dangerous thing a browser can be tricked into doing, forever, and you only have to miss once. So I flipped it: run the page exactly as written, somewhere it can't reach me, or you, or anyone else who opens it.

This approach is for you if:

  • you host code you didn't write and can't review
  • that code has to run, not just display
  • you can serve it from a second origin

The downside is that you give up any say over what the page does inside its own frame. That's the next three sections, and the last one is about what it still allows.

Leave out allow-same-origin and the page belongs to nobody

A shared page never renders on the main site. It loads inside a sandboxed iframe, and the part that matters is the permission I leave out: allow-same-origin.

<iframe
  src="{a short-lived signed URL on the content origin}"
  sandbox="allow-scripts allow-popups allow-forms allow-downloads
           allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
  referrerpolicy="no-referrer"
></iframe>

Without allow-same-origin, the framed document gets an opaque, null origin. Scripts still run, so charts animate and forms submit, but the page can't read cookies, localStorage throws when it's touched, and it can't reach into the app that framed it. The only way it talks to the app is postMessage, and the app checks where each message came from.

The other tokens let ordinary things work: external links, mailto: and tel: buttons, form posts, downloads. Two of them change navigation, so they're worth being exact about. allow-popups-to-escape-sandbox means a tab the page opens is a normal, unsandboxed tab on whatever site it points to. allow-top-navigation-by-user-activation lets a click take you to another site, but only a click: a page can't redirect your tab on load. The ungated allow-top-navigation stays out, because it's the anti-phishing control. None of these grants same-origin access.

The one line I never cross is putting allow-scripts and allow-same-origin together. Combined, they hand the page a real origin back, and the null origin everything above relies on is gone. Slide decks and version previews render under the same rule.

Serve the page from an origin with nothing on it

The sandbox is the first layer. The second is where the HTML comes from.

It isn't served by the app you log into. It comes from a separate content origin whose route never reads or writes a cookie, and the login middleware doesn't run there, so no session cookie is ever set on it. The app checks on the server whether you may see the page, and only then creates a signed link to it that expires after sixty seconds.

So picture the worst case: a browser bug lets a script climb out of the sandbox. It lands on an origin with no cookie, no token, nothing signed in. The comment in my own code says "even a sandbox escape finds nothing to steal", and that is the design goal.

That origin also tells the browser that only my app's own domain may frame it, so a page can't be lifted out and embedded somewhere else to fake a convincing login box. It sends no referrer, so the signed link doesn't leak to anything the page loads, and it marks every response noindex.

See the sandbox on a real page
Open any published page, then open dev tools. It is an iframe, on a different origin, with no origin of its own. Nothing to take our word for.
Open a live example

Injection and session hijacking both fail before they start

Injection, the XSS kind, is untrusted HTML running inside your trusted, cookie-carrying origin and reading what's there. On ShareMyPage the uploaded HTML never runs on that origin. There's no trusted place for it to inject into. It's quarantined by where it runs, not by me inspecting it.

Session hijacking works by reading a session cookie and replaying it. The sandboxed script has a null origin, so cookie and storage access are gone before it runs, and the origin it runs on carries no session anyway. It fails for two unrelated reasons at once, which is the point of stacking them.

The sandbox protects your account, not your CPU or what you type

Here is what the model does not do.

It doesn't stop a page from being greedy with your CPU. A sandboxed page can run a cryptominer or drain your battery, and nothing in my setup limits that today. I made a deliberate call about what I'm defending: your data, and the other people on the platform. Not your CPU from a page you chose to open. I'd rather call it an open hole than pretend it's handled.

It can't beat a real browser zero-day. If a browser ships a bug that breaks iframe sandboxing itself, nothing on my end saves you, short of not running the code, which is the one thing I won't do. So I went after a different target: make an escape worthless. That's the whole reason for the cookieless origin.

It doesn't limit where a page connects. The content security policy on shared pages is permissive on purpose, because AI-generated pages load fonts and call APIs. So whatever you type into a page can go wherever its author's code sends it. The sandbox keeps your ShareMyPage session away from the page, not the things you hand it.

And isolation doesn't make the author trustworthy. A public link is a public link, and someone can put misleading junk on one the same way they can on any host. That's a moderation problem, not a same-origin one. The part you control is who can open each page: just you, your workspace, anyone with the link, or anyone with the link and a password.

The short version

  • Don't sanitize untrusted HTML. Isolate it.
  • Never combine allow-scripts with allow-same-origin.
  • Serve the page from an origin with no cookies, so an escape lands somewhere empty.
  • Say plainly what that doesn't cover.

You don't have to take my word for any of it. Publish a page, open your browser's dev tools, and look: it's in an iframe, served from a different origin, with no origin of its own.

One tip if you build something like this yourself: search your codebase for allow-same-origin before every release. It is the one string that undoes the rest.

Check it yourself, right here.
Drop in an HTML file and it goes live in seconds, no account. Then open dev tools on it and look: sandboxed iframe, separate origin, no origin of its own. Nothing to take my word for.
On a phone, or nothing to hand? Open a live page and inspect it
Want to check the claims yourself?
The full security review lists what is enforced, what is not, and the gaps that are still open, in the same plain terms as this post.
Read the security review