Migrating from gather.town?
Get a discount!
David Négrier
CTO & Founder

How Chrome 142+ broke the local scripting workflow and how we fixed it

Chrome 142 introduced new protections against local network attacks. For most apps, that change is barely noticeable. For us, it broke a very specific, very common local development workflow.

If you use the WorkAdventure map starter kit and your custom scripts suddenly stopped loading from localhost, you did not misconfigure anything. Chrome changed how it treats local requests coming from sandboxed iframes, and our previous approach relied on exactly that.

In this article, I’ll walk through what changed, why it fails (even with permissive CORS headers), the short term workaround we shipped, and the longer term fix that makes development and production behave the same way.

If you just want the fix

The fix is already shipped in WorkAdventure v1.27.6. This fix opens more possibilities for local scripts and if you want to benefit from this possibilities when your script is in production, we advise you to:

Update the Vite plugin in your map starter kit’s package.json:

"wa-map-optimizer-vite": "^1.2"

and run npm install again.

Your scripts will now load from a proper origin in both development and production, which also makes CORS behavior much more predictable.

The rest of the article explains what happened and why this fixes it.

What changed in Chrome 142

Chrome 142 effectively introduced two changes that matter here.

1) A new “local network access” permission prompt

When a page tries to access resources on the local network, for example localhost, Chrome now requires an explicit user decision.

In our case, when WorkAdventure (hosted at https://play.workadventu.re) tries to load resources from your local dev server at localhost:5173, Chrome shows a prompt asking whether to allow or deny local network access.

That part is manageable. You click Allow and move on.

2) The breaking change: local requests from opaque origins are blocked

The more disruptive change is how Chrome handles local network requests coming from an origin it considers untrusted.

Sandboxed iframes created without allow-same-origin end up with an opaque origin. In practice, that often shows up as origin: null. With Chrome 142, local network requests coming from null are blocked very early, before CORS is even evaluated.

That’s the key detail: even if your dev server sends permissive CORS headers, Chrome may block the request before those headers matter.

Why it broke the map starter kit workflow

The WorkAdventure map starter kit is designed for fast iteration.

When you run:

$ npm run dev

a dev server starts on localhost:5173. WorkAdventure then loads maps, images, JavaScript, CSS, and other resources from that server, so you see changes in real time.

For map resources, the new Chrome permission prompt is usually enough to keep going. But scripts are special in WorkAdventure.

Scripts run in sandboxed iframes by design

WorkAdventure lets you write custom scripts to add interactivity using the scripting API. For security, those user scripts are not executed in the main WorkAdventure context. Instead, they run inside sandboxed iframes.

Before Chrome 142, our local development flow looked like this:

  1. WorkAdventure creates an empty iframe with sandbox=“allow-scripts” (without allow-same-origin)

  2. It uses srcdoc to inject a tiny HTML document into that iframe

  3. That document includes a <script> tag pointing to your dev server, for example http://localhost:5173/my-script.js

  4. The browser fetches the script from localhost and executes it inside the sandboxed iframe

After Chrome 142, step 4 fails.

What you’ll see when it fails

When the script is blocked, it never loads, and Chrome logs errors like:

Access to script at ‘http://localhost:5173/src/main.ts ’ from origin ‘null’ has been blocked by CORS policy: Permission was denied for this request to access the unknown address space.

At this point, the dev server is running, your code is fine, and CORS headers do not help. The request is denied earlier in the pipeline due to the local network access gating.

First fix: unblock local development with a proxy endpoint

To get developers unblocked quickly, we changed how WorkAdventure loads user scripts during local development.

The WorkAdventure server now exposes a proxy endpoint:

/local-script?script=XXX

Instead of letting the sandboxed iframe fetch the script directly from localhost, WorkAdventure fetches it via this endpoint. That makes the request originate from the WorkAdventure server origin, not from null.

“Isn’t that a security issue?”

It could be, depending on how it’s implemented. We scoped it tightly.

This endpoint only allows scripts hosted on localhost. So while it does enable loading “any” local script, an attacker would need access to the user’s machine to host something malicious on localhost. That’s a much higher bar, and if an attacker already has local access, the system is effectively compromised anyway.

This solution fixed the immediate Chrome 142 breakage. But it introduced another problem.

The hidden downside: development and production no longer matched

With the proxy approach, local development changed the script’s effective origin.

In development, scripts were now being loaded from an iframe whose origin is https://play.workadventu.re (the WorkAdventure server). But in production, scripts were still loaded using srcdoc, so the iframe origin remained null.

That mismatch can cause hard to diagnose issues.

If a script relies on running under a real origin with more privileges, for example access to cookies, localStorage, or origin scoped resources served by WorkAdventure, it might work locally and then break after deployment.

So we decided to go further and fix the root inconsistency.

The long term fix: give scripts a real origin everywhere

We wanted one property above all: scripts should load the same way in development and production.

Using /local-script in production was not an option. A production proxy that can serve arbitrary WorkAdventure hosted scripts to user code would be a major security risk, with potential access to internal APIs and user data.

Instead, we leaned on something already true about WorkAdventure maps.

Where maps are hosted today

WorkAdventure maps are hosted either:

  • on GitHub Pages, for legacy setups
  • or on the WorkAdventure map storage server

That gave us a clean path: host an HTML page alongside the script, and load that page in the iframe using a normal src URL.

Why this is safe enough

In the SaaS offering, the map storage server uses one domain per world. That isolates scripts per customer world at the domain level.

In self hosted setups, the map storage server is usually on the same domain as WorkAdventure, or on a dedicated domain controlled by the same administrator. In that context, the map owner and the server admin are typically the same person, so this does not introduce a new cross tenant risk.

What we changed in the map starter kit

The map starter kit ships with a Vite plugin responsible for building scripts. We extended that plugin so it also generates a small HTML page for each script, which loads the script via a <script> tag.

For example, if you have a script named my-script.js, the plugin generates my-script.html like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://play.workadventu.re/iframe_api.js"></script>
    <script src="my-script.js"></script>
</head>
<body>
</body>
</html>

WorkAdventure then loads the HTML page in the iframe using src, rather than injecting srcdoc and trying to fetch the script from localhost directly.

The outcome is exactly what we wanted:

  • a proper origin in development
  • a proper origin in production
  • consistent behavior across environments

As a bonus, scripts that previously struggled with CORS protected resources are generally much easier to reason about now.

Wrap up

Chrome 142 did not just add a permission prompt. It also changed how local network requests from opaque origins, such as sandboxed iframes with origin: null, are treated. That broke a common pattern for loading local scripts during development.

We shipped a short term workaround to unblock local development, then implemented a more robust approach: load scripts through dedicated HTML pages hosted alongside maps, so scripts have a real origin in both development and production.

If you are using the map starter kit, upgrading wa-map-optimizer-vite to ^1.2 should get you back to a smooth workflow, with fewer origin and CORS surprises along the way.

You may also be interested in