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.
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.
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#
| Solution | Size (brotli) | Browser | Node | Notes |
|---|---|---|---|---|
| This demo (Goja) | ~2.5 MB | Yes | Yes | Go ecosystem integration |
| quickjs-emscripten | ~500 KB | Yes | Yes | Smaller, C-based |
| SandboxJS | ~50 KB | Yes | Yes | JS-based parser |
| vm2 | tiny | No | Yes | Node.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:
- gowasm-bindgen works with standard Go (not just TinyGo)
- Complex Go libraries (Goja uses reflection heavily) compile to WASM
- Go ecosystem in browser - same interpreter as your Go backend
Bundle Size#
| Format | Size |
|---|---|
| Uncompressed | 17 MB |
| Gzip | 3.7 MB |
| Brotli | 2.5 MB |
The WASM file is cached after first load.