@bytecodealliance/preview2-shim
Advanced tools
@@ -9,2 +9,21 @@ import { streams } from './io.js'; | ||
| /** | ||
| * @typedef {Object} FileDataEntry | ||
| * @property {Record<string, FileDataEntry>} [dir] - Directory contents (present for directories) | ||
| * @property {Uint8Array|string} [source] - File contents (present for files) | ||
| */ | ||
| /** | ||
| * @typedef {FileDataEntry} FileData | ||
| * Root file data structure representing a filesystem tree. | ||
| * Each entry is either a directory (has `dir` property) or a file (has `source` property). | ||
| * @example | ||
| * // A simple filesystem with one directory containing one file: | ||
| * const fileData = { | ||
| * dir: { | ||
| * 'myfile.txt': { source: new Uint8Array([72, 101, 108, 108, 111]) } | ||
| * } | ||
| * }; | ||
| */ | ||
| export function _setFileData(fileData) { | ||
@@ -326,2 +345,55 @@ _fileData = fileData; | ||
| /** | ||
| * Replace all preopens with the given set. | ||
| * @param {Record<string, FileData>} preopensConfig - Map of virtual paths to file data entries | ||
| */ | ||
| export function _setPreopens(preopensConfig) { | ||
| _preopens = []; | ||
| for (const [virtualPath, fileData] of Object.entries(preopensConfig)) { | ||
| _addPreopen(virtualPath, fileData); | ||
| } | ||
| } | ||
| /** | ||
| * Add a single preopen mapping. | ||
| * @param {string} virtualPath - The virtual path visible to the guest | ||
| * @param {FileData} fileData - The file data object representing the directory | ||
| */ | ||
| export function _addPreopen(virtualPath, fileData) { | ||
| const descriptor = new Descriptor(fileData); | ||
| _preopens.push([descriptor, virtualPath]); | ||
| if (virtualPath === '/') { | ||
| _rootPreopen = [descriptor, virtualPath]; | ||
| } | ||
| } | ||
| /** | ||
| * Clear all preopens, giving the guest no filesystem access. | ||
| * | ||
| * This functionality exists mostly to maintain backwards compatibility. Prefer setting preopens | ||
| * via `WASIShim` rather than making top level changes to preopens using these functions. | ||
| */ | ||
| export function _clearPreopens() { | ||
| _preopens = []; | ||
| _rootPreopen = null; | ||
| } | ||
| /** | ||
| * Get current preopens configuration. | ||
| * @returns {Array<[Descriptor, string]>} Array of [descriptor, virtualPath] pairs | ||
| */ | ||
| export function _getPreopens() { | ||
| return [..._preopens]; | ||
| } | ||
| /** | ||
| * Create a preopen descriptor for file data. | ||
| * This is used internally to create isolated preopen instances. | ||
| * @param {FileData} fileData - The file data object representing the directory | ||
| * @returns {Descriptor} A preopen descriptor | ||
| */ | ||
| export function _createPreopenDescriptor(fileData) { | ||
| return new Descriptor(fileData); | ||
| } | ||
| export const types = { | ||
@@ -328,0 +400,0 @@ Descriptor, |
@@ -47,2 +47,29 @@ import * as wasi from '@bytecodealliance/preview2-shim'; | ||
| * | ||
| * For sandboxing, you can configure preopens, environment variables, and other | ||
| * capabilities via the `sandbox` option: | ||
| * | ||
| * ```js | ||
| * import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation" | ||
| * | ||
| * // Fully sandboxed - no filesystem, network, or env access | ||
| * const sandboxedShim = new WASIShim({ | ||
| * sandbox: { | ||
| * preopens: {}, // No filesystem access | ||
| * env: {}, // No environment variables | ||
| * args: ['program'], // Custom arguments | ||
| * enableNetwork: false, // Disable network (default: true for backward compat) | ||
| * } | ||
| * }); | ||
| * | ||
| * // Limited filesystem access | ||
| * const limitedShim = new WASIShim({ | ||
| * sandbox: { | ||
| * preopens: { | ||
| * '/data': '/tmp/guest-data', // Guest sees /data, maps to /tmp/guest-data | ||
| * '/config': '/etc/app' // Guest sees /config, maps to /etc/app | ||
| * } | ||
| * } | ||
| * }); | ||
| * ``` | ||
| * | ||
| * Note that this object is similar but not identical to the Node `WASI` object -- | ||
@@ -70,4 +97,16 @@ * it is solely concerned with shimming of preview2 when dealing with a WebAssembly | ||
| #http; | ||
| /** Isolated preopens for this instance */ | ||
| #preopens; | ||
| /** Isolated environment for this instance */ | ||
| #environment; | ||
| constructor(shims) { | ||
| /** | ||
| * Create a new WASIShim instance. | ||
| * | ||
| * @param {import('../types/instantiation.d.ts').WASIShimConfig} [config] - Configuration options | ||
| */ | ||
| constructor(config) { | ||
| // Support both old 'shims' parameter name and new 'config' style | ||
| const shims = config; | ||
| this.#cli = shims?.cli ?? wasi.cli; | ||
@@ -80,2 +119,33 @@ this.#filesystem = shims?.filesystem ?? wasi.filesystem; | ||
| this.#http = shims?.http ?? wasi.http; | ||
| // Extract sandbox options | ||
| const sandbox = shims?.sandbox; | ||
| // Create isolated preopens if configured | ||
| if (sandbox?.preopens !== undefined) { | ||
| this.#preopens = createIsolatedPreopens(sandbox.preopens); | ||
| } | ||
| // Create isolated environment if env or args are configured | ||
| if (sandbox?.env !== undefined || sandbox?.args !== undefined) { | ||
| this.#environment = createIsolatedEnvironment( | ||
| sandbox?.env, | ||
| sandbox?.args, | ||
| this.#cli | ||
| ); | ||
| } | ||
| // Apply network restrictions if disabled | ||
| if (sandbox?.enableNetwork === false) { | ||
| // Use the sockets module's built-in deny functions | ||
| if (this.#sockets._denyTcp) { | ||
| this.#sockets._denyTcp(); | ||
| } | ||
| if (this.#sockets._denyUdp) { | ||
| this.#sockets._denyUdp(); | ||
| } | ||
| if (this.#sockets._denyDnsLookup) { | ||
| this.#sockets._denyDnsLookup(); | ||
| } | ||
| } | ||
| } | ||
@@ -99,3 +169,4 @@ | ||
| const obj = {}; | ||
| obj[`wasi:cli/environment${versionSuffix}`] = this.#cli.environment; | ||
| obj[`wasi:cli/environment${versionSuffix}`] = this.#environment ?? this.#cli.environment; | ||
| obj[`wasi:cli/exit${versionSuffix}`] = this.#cli.exit; | ||
@@ -119,3 +190,3 @@ obj[`wasi:cli/stderr${versionSuffix}`] = this.#cli.stderr; | ||
| obj[`wasi:filesystem/preopens${versionSuffix}`] = this.#filesystem.preopens; | ||
| obj[`wasi:filesystem/preopens${versionSuffix}`] = this.#preopens ?? this.#filesystem.preopens; | ||
| obj[`wasi:filesystem/types${versionSuffix}`] = this.#filesystem.types; | ||
@@ -140,1 +211,53 @@ | ||
| } | ||
| /** | ||
| * Create an isolated preopens object with its own preopen entries. | ||
| * | ||
| * @param {Record<string, string>} preopensConfig - Map of virtual paths to host paths | ||
| * @returns {object} A preopens object with Descriptor and getDirectories() | ||
| */ | ||
| function createIsolatedPreopens(preopensConfig) { | ||
| const { types, _createPreopenDescriptor } = wasi.filesystem; | ||
| const entries = []; | ||
| // Populate entries using the filesystem's descriptor creation | ||
| if (_createPreopenDescriptor) { | ||
| for (const [virtualPath, hostPath] of Object.entries(preopensConfig)) { | ||
| const descriptor = _createPreopenDescriptor(hostPath); | ||
| entries.push([descriptor, virtualPath]); | ||
| } | ||
| } | ||
| return { | ||
| Descriptor: types.Descriptor, | ||
| getDirectories() { | ||
| return entries; | ||
| }, | ||
| }; | ||
| } | ||
| /** | ||
| * Create an isolated CLI environment with its own env and args. | ||
| * | ||
| * @param {Record<string, string>} env - Environment variables | ||
| * @param {string[]} args - Command-line arguments | ||
| * @param {object} baseCli - The base CLI module to extend | ||
| * @returns {object} An isolated CLI environment object | ||
| */ | ||
| function createIsolatedEnvironment(env, args, baseCli) { | ||
| const envEntries = env ? Object.entries(env) : null; | ||
| const argsArray = args || null; | ||
| return { | ||
| ...baseCli.environment, | ||
| getEnvironment() { | ||
| return envEntries ?? baseCli.environment.getEnvironment(); | ||
| }, | ||
| getArguments() { | ||
| return argsArray ?? baseCli.environment.getArguments(); | ||
| }, | ||
| initialCwd() { | ||
| return baseCli.environment.initialCwd(); | ||
| }, | ||
| }; | ||
| } |
@@ -741,2 +741,6 @@ import { | ||
| /** | ||
| * Replace all preopens with the given set. | ||
| * @param {Record<string, string>} preopens - Map of virtual paths to host paths | ||
| */ | ||
| export function _setPreopens(preopens) { | ||
@@ -749,2 +753,7 @@ preopenEntries = []; | ||
| /** | ||
| * Add a single preopen mapping. | ||
| * @param {string} virtualPath - The virtual path visible to the guest | ||
| * @param {string} hostPreopen - The host filesystem path | ||
| */ | ||
| export function _addPreopen(virtualPath, hostPreopen) { | ||
@@ -755,2 +764,32 @@ const preopenEntry = [descriptorCreatePreopen(hostPreopen), virtualPath]; | ||
| /** | ||
| * Clear all preopens, giving the guest no filesystem access. | ||
| * Call this immediately after import to disable default full filesystem access. | ||
| * | ||
| * @example | ||
| * import { _clearPreopens } from '@bytecodealliance/preview2-shim/filesystem'; | ||
| * _clearPreopens(); // Now guest has no filesystem access by default | ||
| */ | ||
| export function _clearPreopens() { | ||
| preopenEntries = []; | ||
| } | ||
| /** | ||
| * Get current preopens configuration. | ||
| * @returns {Array<[Descriptor, string]>} Array of [descriptor, virtualPath] pairs | ||
| */ | ||
| export function _getPreopens() { | ||
| return [...preopenEntries]; | ||
| } | ||
| /** | ||
| * Create a preopen descriptor for a host path. | ||
| * This is used internally to create isolated preopen instances. | ||
| * @param {string} hostPreopen - The host filesystem path | ||
| * @returns {Descriptor} A preopen descriptor | ||
| */ | ||
| export function _createPreopenDescriptor(hostPreopen) { | ||
| return descriptorCreatePreopen(hostPreopen); | ||
| } | ||
| function convertFsError(e) { | ||
@@ -757,0 +796,0 @@ switch (e.code) { |
+1
-1
| { | ||
| "name": "@bytecodealliance/preview2-shim", | ||
| "version": "0.17.6", | ||
| "version": "0.17.7", | ||
| "description": "WASI Preview2 shim for JS environments", | ||
@@ -5,0 +5,0 @@ "author": "Guy Bedford, Eduardo Rodrigues<16357187+eduardomourar@users.noreply.github.com>", |
+48
-0
@@ -54,2 +54,50 @@ # Preview2 Shim | ||
| ## Sandboxing | ||
| By default, the preview2-shim provides full access to the host filesystem, environment variables, | ||
| and network - matching the default behavior of Node.js libraries. However, you can configure | ||
| sandboxing to restrict what guests can access. | ||
| ### Using WASIShim for sandboxing | ||
| The `WASIShim` class accepts a `sandbox` configuration option to control access: | ||
| ```js | ||
| import { WASIShim } from '@bytecodealliance/preview2-shim/instantiation'; | ||
| // Fully sandboxed - no filesystem, network, or env access | ||
| const sandboxedShim = new WASIShim({ | ||
| sandbox: { | ||
| preopens: {}, // No filesystem access | ||
| env: {}, // No environment variables | ||
| args: ['arg1'], // Custom arguments | ||
| enableNetwork: false, // Disable network access | ||
| } | ||
| }); | ||
| // Limited filesystem access - map virtual paths to host paths | ||
| const limitedShim = new WASIShim({ | ||
| sandbox: { | ||
| preopens: { | ||
| '/data': '/tmp/guest-data', // Guest sees /data, maps to /tmp/guest-data | ||
| '/config': '/etc/app' // Guest sees /config, maps to /etc/app | ||
| }, | ||
| env: { 'ENV1': '42' }, // Only expose specific env vars | ||
| } | ||
| }); | ||
| const component = await instantiate(loader, sandboxedShim.getImportObject()); | ||
| ``` | ||
| ### Notes on sandboxing | ||
| - By default (when no options are passed), the shim is providing full access to match typical | ||
| Node.js library behavior. | ||
| - Each `WASIShim` instance has its own isolated preopens, environment variables, and arguments. | ||
| Multiple instances with different configurations will not affect each other. | ||
| - The direct preopen functions (`_setPreopens`, `_clearPreopens`, etc.) modify global state and | ||
| affect all components not using `WASIShim` with explicit configuration. For isolation, prefer | ||
| using `WASIShim` with the `sandbox` option containing `preopens` and `env`. | ||
| - When `sandbox.enableNetwork: false`, all socket and HTTP operations will throw "access-denied" errors. | ||
| [jco]: https://www.npmjs.com/package/@bytecodealliance/jco | ||
@@ -56,0 +104,0 @@ |
| export type * as preopens from './interfaces/wasi-filesystem-preopens.d.ts'; | ||
| export type * as types from './interfaces/wasi-filesystem-types.d.ts'; | ||
| import type { Descriptor } from './interfaces/wasi-filesystem-types.d.ts'; | ||
| /** | ||
| * Replace all preopens with the given set. | ||
| * @param preopens - Map of virtual paths to host paths | ||
| */ | ||
| export function _setPreopens(preopens: Record<string, string>): void; | ||
| /** | ||
| * Add a single preopen mapping. | ||
| * @param virtualPath - The virtual path visible to the guest | ||
| * @param hostPreopen - The host filesystem path | ||
| */ | ||
| export function _addPreopen(virtualPath: string, hostPreopen: string): void; | ||
| /** | ||
| * Clear all preopens, giving the guest no filesystem access. | ||
| * Call this immediately after import to disable default full filesystem access. | ||
| */ | ||
| export function _clearPreopens(): void; | ||
| /** | ||
| * Get current preopens configuration. | ||
| * @returns Array of [descriptor, virtualPath] pairs | ||
| */ | ||
| export function _getPreopens(): Array<[Descriptor, string]>; |
@@ -68,2 +68,38 @@ /** | ||
| /** | ||
| * Sandbox configuration options for WASIShim | ||
| */ | ||
| interface SandboxConfig { | ||
| /** Filesystem preopens mapping (virtual path -> host path) */ | ||
| preopens?: Record<string, string>; | ||
| /** Environment variables visible to the guest */ | ||
| env?: Record<string, string>; | ||
| /** Command-line arguments */ | ||
| args?: string[]; | ||
| /** Whether to enable network access (sockets, HTTP). Default: true */ | ||
| enableNetwork?: boolean; | ||
| } | ||
| /** | ||
| * Configuration options for WASIShim | ||
| */ | ||
| interface WASIShimConfig { | ||
| /** Custom CLI shim */ | ||
| cli?: object; | ||
| /** Custom filesystem shim */ | ||
| filesystem?: object; | ||
| /** Custom I/O shim */ | ||
| io?: object; | ||
| /** Custom random shim */ | ||
| random?: object; | ||
| /** Custom clocks shim */ | ||
| clocks?: object; | ||
| /** Custom sockets shim */ | ||
| sockets?: object; | ||
| /** Custom HTTP shim */ | ||
| http?: object; | ||
| /** Sandbox configuration for restricting guest capabilities */ | ||
| sandbox?: SandboxConfig; | ||
| } | ||
| /** | ||
| * (EXPERIMENTAL) A class that holds WASI shims and can be used to configure | ||
@@ -112,2 +148,29 @@ * an instantiation of a WebAssembly component transpiled with jco | ||
| * | ||
| * For sandboxing, you can configure preopens, environment variables, and other | ||
| * capabilities via the `sandbox` option: | ||
| * | ||
| * ```js | ||
| * import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation" | ||
| * | ||
| * // Fully sandboxed - no filesystem, network, or env access | ||
| * const sandboxedShim = new WASIShim({ | ||
| * sandbox: { | ||
| * preopens: {}, // No filesystem access | ||
| * env: {}, // No environment variables | ||
| * args: ['program'], // Custom arguments | ||
| * enableNetwork: false, // Disable network | ||
| * } | ||
| * }); | ||
| * | ||
| * // Limited filesystem access | ||
| * const limitedShim = new WASIShim({ | ||
| * sandbox: { | ||
| * preopens: { | ||
| * '/data': '/tmp/guest-data', // Guest sees /data, maps to /tmp/guest-data | ||
| * '/config': '/etc/app' // Guest sees /config, maps to /etc/app | ||
| * } | ||
| * } | ||
| * }); | ||
| * ``` | ||
| * | ||
| * Note that this object is similar but not identical to the Node `WASI` object -- | ||
@@ -121,3 +184,3 @@ * it is solely concerned with shimming of preview2 when dealing with a WebAssembly | ||
| export class WASIShim { | ||
| constructor(shims?: Partial<WASIImportObject>); | ||
| constructor(config?: WASIShimConfig); | ||
@@ -124,0 +187,0 @@ /** |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
362269
3.46%10221
2.98%114
72.73%30
3.45%