hakojs
Version:
A secure, embeddable JavaScript engine that runs untrusted code inside WebAssembly sandboxes with fine-grained permissions and resource limits
118 lines (116 loc) • 4.05 kB
JavaScript
// src/index.ts
import { useClock, useRandom, useStdio, WASI } from "uwasi";
import { HakoError } from "./etc/errors";
import { CallbackManager } from "./host/callback";
import { Container } from "./host/container";
import { HakoRuntime } from "./host/runtime";
import { MemoryManager } from "./mem/memory";
import { default as default2 } from "./variants/hako.g";
import { default as default3 } from "./variants/hako-debug.g";
var defaultInitialMemory = 25165824;
var defaultMaximumMemory = 268435456;
async function createHakoRuntime(options) {
const memConfig = options.wasm?.memory || {};
const initialMemory = memConfig.initial || defaultInitialMemory;
const maximumMemory = memConfig.maximum || defaultMaximumMemory;
let wasmMemory;
if (memConfig.byom) {
wasmMemory = memConfig.byom;
if (wasmMemory.buffer instanceof SharedArrayBuffer) {
throw new HakoError("Hako memory cannot be a SharedArrayBuffer. Use a regular ArrayBuffer instead.");
}
} else {
const initialPages = Math.ceil(initialMemory / 65536);
const maximumPages = Math.ceil(maximumMemory / 65536);
wasmMemory = new WebAssembly.Memory({
initial: initialPages,
maximum: maximumPages,
shared: false
});
}
const memory = new MemoryManager;
const callbacks = new CallbackManager(memory);
const wasi = new WASI({
features: [
useStdio({
stdout: options.wasm?.io?.stdout || ((lines) => console.log(lines)),
stderr: options.wasm?.io?.stderr || ((lines) => console.error(lines))
}),
useClock,
useRandom({
randomFillSync: (buffer) => {
return crypto.getRandomValues(buffer);
}
})
],
args: options.wasm?.args || [],
env: options.wasm?.env || {},
preopens: options.wasm?.preopens || {}
});
const imports = {
wasi_snapshot_preview1: wasi.wasiImport,
env: {
memory: wasmMemory
},
...callbacks.getImports()
};
let instance;
if (options.loader.binary) {
const result = await WebAssembly.instantiate(options.loader.binary, imports);
instance = result.instance;
} else if (options.loader.fetch && options.loader.src) {
const result = await WebAssembly.instantiateStreaming(options.loader.fetch(options.loader.src), imports);
instance = result.instance;
} else {
throw new HakoError("No WebAssembly binary provided");
}
wasi.initialize(instance);
const exports = instance.exports;
const container = new Container(exports, memory, callbacks);
const rtPtr = container.exports.HAKO_NewRuntime();
if (rtPtr === 0) {
throw new HakoError("Failed to create runtime");
}
const runtime = new HakoRuntime(container, rtPtr);
if (options.runtime?.interruptHandler) {
runtime.enableInterruptHandler(options.runtime.interruptHandler);
}
if (options.runtime?.memoryLimit) {
runtime.setMemoryLimit(options.runtime.memoryLimit);
}
return runtime;
}
var decodeVariant = (encoded) => {
let module;
if (typeof Uint8Array.fromBase64 === "function") {
module = Uint8Array.fromBase64(encoded, {
lastChunkHandling: "strict"
});
} else {
const decoded = atob(encoded);
module = new Uint8Array(decoded.length);
for (let i = 0;i < decoded.length; i++) {
module[i] = decoded.charCodeAt(i);
}
}
if (module.length < 8) {
throw new HakoError("Buffer too small to be a valid WebAssembly module");
}
const isMagicValid = module[0] === 0 && module[1] === 97 && module[2] === 115 && module[3] === 109;
if (!isMagicValid) {
throw new HakoError("Invalid WebAssembly module");
}
const isVersionValid = module[4] === 1 && module[5] === 0 && module[6] === 0 && module[7] === 0;
if (!isVersionValid) {
throw new HakoError(`Unsupported WebAssembly version: ${module[4]}.${module[5]}.${module[6]}.${module[7]}`);
}
return module;
};
export {
decodeVariant,
createHakoRuntime,
default2 as HAKO_PROD,
default3 as HAKO_DEBUG
};
//# debugId=7B89BBB074C8C5AC64756E2164756E21
//# sourceMappingURL=index.js.map