JavaScript Sandbox Demo#

Securely execute untrusted JavaScript using Goja–a JavaScript interpreter written in Go–compiled to WebAssembly.

Use case: Run user-provided scripts in complete isolation, with no access to DOM, network, or browser APIs.

Loading WASM module (first load: ~3MB compressed)...

Sandbox Proof

The code below runs in Goja, not your browser's JS engine. The __goja__ global only exists inside Goja, and window/document don't exist.

Console Output
(run code to see output)
Return Value
-

When To Use This#

Use Goja-in-WASM when:

  • Your backend is Go and you want the same interpreter client + server
  • You need to integrate with other Go libraries in the sandbox
  • You’re already using gowasm-bindgen for other Go-to-WASM code

Use quickjs-emscripten instead when:

  • Bundle size is critical (~500KB vs ~2.5MB)
  • You don’t need Go ecosystem integration
  • You need better ES6+ support

Alternatives#

SolutionSize (brotli)BrowserNodeNotes
This demo (Goja)~2.5 MBYesYesGo ecosystem integration
quickjs-emscripten~500 KBYesYesSmaller, C-based
SandboxJS~50 KBYesYesJS-based parser
vm2tinyNoYesNode.js only

For most browser sandboxing needs, quickjs-emscripten is the better choice due to its smaller size. This demo is valuable for Go-centric architectures where you want the same interpreter on client and server.

How It Works#

This demo uses gowasm-bindgen to generate TypeScript bindings for Go code that embeds the Goja JavaScript interpreter:

//go:build js && wasm

package main

import (
    "github.com/dop251/goja"
    "runtime"
)

// JSResult contains the output from running JavaScript code.
type JSResult struct {
    Result   string
    Logs     string
    ErrorMsg string
}

// RunJS executes JavaScript code in the Goja interpreter.
func RunJS(code string) JSResult {
    vm := goja.New()

    // Inject proof-of-goja globals
    vm.Set("__goja__", map[string]interface{}{
        "engine":    "goja",
        "goVersion": runtime.Version(),
        "goOS":      runtime.GOOS,
        "goArch":    runtime.GOARCH,
    })

    // Capture console.log output...
    // Run user code...

    return JSResult{Result: ..., Logs: ...}
}

Generated TypeScript#

gowasm-bindgen generates a typed client:

export interface RunJSResult {
  result: string;
  logs: string;
  errorMsg: string;
}

export class GoGoja {
  static async init(workerUrl: string): Promise<GoGoja>;
  runJS(code: string): Promise<RunJSResult>;
}

Why This Matters#

This demo showcases:

  1. gowasm-bindgen works with standard Go (not just TinyGo)
  2. Complex Go libraries (Goja uses reflection heavily) compile to WASM
  3. Go ecosystem in browser - same interpreter as your Go backend

Bundle Size#

FormatSize
Uncompressed17 MB
Gzip3.7 MB
Brotli2.5 MB

The WASM file is cached after first load.

Source Code#