@bytecodealliance/preview2-shim 0.17.6 → 0.17.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/lib/browser/clocks.js +10 -6
- package/lib/browser/filesystem.js +72 -0
- package/lib/browser/http.js +677 -140
- package/lib/browser/io.js +68 -8
- package/lib/common/instantiation.js +126 -3
- package/lib/nodejs/filesystem.js +39 -0
- package/package.json +1 -1
- package/types/filesystem.d.ts +27 -0
- package/types/instantiation.d.ts +64 -1
package/README.md
CHANGED
|
@@ -51,6 +51,54 @@ const component = await instantiate(loader, new WASIShim().getImportObject());
|
|
|
51
51
|
// TODO: Code that uses your component's exports goes here.
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
## Sandboxing
|
|
55
|
+
|
|
56
|
+
By default, the preview2-shim provides full access to the host filesystem, environment variables,
|
|
57
|
+
and network - matching the default behavior of Node.js libraries. However, you can configure
|
|
58
|
+
sandboxing to restrict what guests can access.
|
|
59
|
+
|
|
60
|
+
### Using WASIShim for sandboxing
|
|
61
|
+
|
|
62
|
+
The `WASIShim` class accepts a `sandbox` configuration option to control access:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { WASIShim } from '@bytecodealliance/preview2-shim/instantiation';
|
|
66
|
+
|
|
67
|
+
// Fully sandboxed - no filesystem, network, or env access
|
|
68
|
+
const sandboxedShim = new WASIShim({
|
|
69
|
+
sandbox: {
|
|
70
|
+
preopens: {}, // No filesystem access
|
|
71
|
+
env: {}, // No environment variables
|
|
72
|
+
args: ['arg1'], // Custom arguments
|
|
73
|
+
enableNetwork: false, // Disable network access
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Limited filesystem access - map virtual paths to host paths
|
|
78
|
+
const limitedShim = new WASIShim({
|
|
79
|
+
sandbox: {
|
|
80
|
+
preopens: {
|
|
81
|
+
'/data': '/tmp/guest-data', // Guest sees /data, maps to /tmp/guest-data
|
|
82
|
+
'/config': '/etc/app' // Guest sees /config, maps to /etc/app
|
|
83
|
+
},
|
|
84
|
+
env: { 'ENV1': '42' }, // Only expose specific env vars
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const component = await instantiate(loader, sandboxedShim.getImportObject());
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Notes on sandboxing
|
|
92
|
+
|
|
93
|
+
- By default (when no options are passed), the shim is providing full access to match typical
|
|
94
|
+
Node.js library behavior.
|
|
95
|
+
- Each `WASIShim` instance has its own isolated preopens, environment variables, and arguments.
|
|
96
|
+
Multiple instances with different configurations will not affect each other.
|
|
97
|
+
- The direct preopen functions (`_setPreopens`, `_clearPreopens`, etc.) modify global state and
|
|
98
|
+
affect all components not using `WASIShim` with explicit configuration. For isolation, prefer
|
|
99
|
+
using `WASIShim` with the `sandbox` option containing `preopens` and `env`.
|
|
100
|
+
- When `sandbox.enableNetwork: false`, all socket and HTTP operations will throw "access-denied" errors.
|
|
101
|
+
|
|
54
102
|
[jco]: https://www.npmjs.com/package/@bytecodealliance/jco
|
|
55
103
|
|
|
56
104
|
# License
|
package/lib/browser/clocks.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { poll } from './io.js';
|
|
2
|
+
const { Pollable } = poll;
|
|
3
|
+
|
|
1
4
|
export const monotonicClock = {
|
|
2
5
|
resolution() {
|
|
3
6
|
// usually we dont get sub-millisecond accuracy in the browser
|
|
@@ -10,15 +13,16 @@ export const monotonicClock = {
|
|
|
10
13
|
},
|
|
11
14
|
subscribeInstant(instant) {
|
|
12
15
|
instant = BigInt(instant);
|
|
13
|
-
const now =
|
|
16
|
+
const now = monotonicClock.now();
|
|
14
17
|
if (instant <= now) {
|
|
15
|
-
return
|
|
18
|
+
return new Pollable(new Promise(resolve => setTimeout(resolve, 0)));
|
|
16
19
|
}
|
|
17
|
-
return
|
|
20
|
+
return monotonicClock.subscribeDuration(instant - now);
|
|
18
21
|
},
|
|
19
|
-
subscribeDuration(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
subscribeDuration(duration) {
|
|
23
|
+
duration = BigInt(duration);
|
|
24
|
+
const ms = duration <= 0n ? 0 : Number(duration / 1_000_000n);
|
|
25
|
+
return new Pollable(new Promise(resolve => setTimeout(resolve, ms)));
|
|
22
26
|
},
|
|
23
27
|
};
|
|
24
28
|
|
|
@@ -6,6 +6,25 @@ export { _setCwd } from './config.js';
|
|
|
6
6
|
|
|
7
7
|
const { InputStream, OutputStream } = streams;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} FileDataEntry
|
|
11
|
+
* @property {Record<string, FileDataEntry>} [dir] - Directory contents (present for directories)
|
|
12
|
+
* @property {Uint8Array|string} [source] - File contents (present for files)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {FileDataEntry} FileData
|
|
17
|
+
* Root file data structure representing a filesystem tree.
|
|
18
|
+
* Each entry is either a directory (has `dir` property) or a file (has `source` property).
|
|
19
|
+
* @example
|
|
20
|
+
* // A simple filesystem with one directory containing one file:
|
|
21
|
+
* const fileData = {
|
|
22
|
+
* dir: {
|
|
23
|
+
* 'myfile.txt': { source: new Uint8Array([72, 101, 108, 108, 111]) }
|
|
24
|
+
* }
|
|
25
|
+
* };
|
|
26
|
+
*/
|
|
27
|
+
|
|
9
28
|
export function _setFileData(fileData) {
|
|
10
29
|
_fileData = fileData;
|
|
11
30
|
_rootPreopen[0] = new Descriptor(fileData);
|
|
@@ -322,6 +341,59 @@ export const preopens = {
|
|
|
322
341
|
},
|
|
323
342
|
};
|
|
324
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Replace all preopens with the given set.
|
|
346
|
+
* @param {Record<string, FileData>} preopensConfig - Map of virtual paths to file data entries
|
|
347
|
+
*/
|
|
348
|
+
export function _setPreopens(preopensConfig) {
|
|
349
|
+
_preopens = [];
|
|
350
|
+
for (const [virtualPath, fileData] of Object.entries(preopensConfig)) {
|
|
351
|
+
_addPreopen(virtualPath, fileData);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Add a single preopen mapping.
|
|
357
|
+
* @param {string} virtualPath - The virtual path visible to the guest
|
|
358
|
+
* @param {FileData} fileData - The file data object representing the directory
|
|
359
|
+
*/
|
|
360
|
+
export function _addPreopen(virtualPath, fileData) {
|
|
361
|
+
const descriptor = new Descriptor(fileData);
|
|
362
|
+
_preopens.push([descriptor, virtualPath]);
|
|
363
|
+
if (virtualPath === '/') {
|
|
364
|
+
_rootPreopen = [descriptor, virtualPath];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Clear all preopens, giving the guest no filesystem access.
|
|
370
|
+
*
|
|
371
|
+
* This functionality exists mostly to maintain backwards compatibility. Prefer setting preopens
|
|
372
|
+
* via `WASIShim` rather than making top level changes to preopens using these functions.
|
|
373
|
+
*/
|
|
374
|
+
export function _clearPreopens() {
|
|
375
|
+
_preopens = [];
|
|
376
|
+
_rootPreopen = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get current preopens configuration.
|
|
381
|
+
* @returns {Array<[Descriptor, string]>} Array of [descriptor, virtualPath] pairs
|
|
382
|
+
*/
|
|
383
|
+
export function _getPreopens() {
|
|
384
|
+
return [..._preopens];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Create a preopen descriptor for file data.
|
|
389
|
+
* This is used internally to create isolated preopen instances.
|
|
390
|
+
* @param {FileData} fileData - The file data object representing the directory
|
|
391
|
+
* @returns {Descriptor} A preopen descriptor
|
|
392
|
+
*/
|
|
393
|
+
export function _createPreopenDescriptor(fileData) {
|
|
394
|
+
return new Descriptor(fileData);
|
|
395
|
+
}
|
|
396
|
+
|
|
325
397
|
export const types = {
|
|
326
398
|
Descriptor,
|
|
327
399
|
DirectoryEntryStream,
|