@secure-exec/core 0.1.1-rc.2 → 0.2.0-rc.1

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.
Files changed (102) hide show
  1. package/dist/esm-compiler.d.ts +5 -1
  2. package/dist/esm-compiler.js +5 -1
  3. package/dist/fs-helpers.d.ts +1 -1
  4. package/dist/generated/isolate-runtime.d.ts +15 -15
  5. package/dist/generated/isolate-runtime.js +15 -15
  6. package/dist/index.d.ts +25 -6
  7. package/dist/index.js +23 -3
  8. package/dist/isolate-runtime/apply-custom-global-policy.js +3 -3
  9. package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +10 -8
  10. package/dist/isolate-runtime/apply-timing-mitigation-off.js +2 -2
  11. package/dist/isolate-runtime/bridge-attach.js +2 -2
  12. package/dist/isolate-runtime/bridge-initial-globals.js +3 -3
  13. package/dist/isolate-runtime/eval-script-result.js +1 -1
  14. package/dist/isolate-runtime/global-exposure-helpers.js +2 -2
  15. package/dist/isolate-runtime/init-commonjs-module-globals.js +2 -2
  16. package/dist/isolate-runtime/override-process-cwd.js +1 -1
  17. package/dist/isolate-runtime/override-process-env.js +1 -1
  18. package/dist/isolate-runtime/require-setup.js +2236 -19
  19. package/dist/isolate-runtime/set-commonjs-file-globals.js +2 -2
  20. package/dist/isolate-runtime/set-stdin-data.js +1 -1
  21. package/dist/isolate-runtime/setup-dynamic-import.js +47 -15
  22. package/dist/isolate-runtime/setup-fs-facade.js +2 -2
  23. package/dist/kernel/command-registry.d.ts +44 -0
  24. package/dist/kernel/command-registry.js +114 -0
  25. package/dist/kernel/device-layer.d.ts +12 -0
  26. package/dist/kernel/device-layer.js +262 -0
  27. package/dist/kernel/dns-cache.d.ts +29 -0
  28. package/dist/kernel/dns-cache.js +52 -0
  29. package/dist/kernel/fd-table.d.ts +84 -0
  30. package/dist/kernel/fd-table.js +278 -0
  31. package/dist/kernel/file-lock.d.ts +34 -0
  32. package/dist/kernel/file-lock.js +123 -0
  33. package/dist/kernel/host-adapter.d.ts +50 -0
  34. package/dist/kernel/host-adapter.js +8 -0
  35. package/dist/kernel/index.d.ts +36 -0
  36. package/dist/kernel/index.js +34 -0
  37. package/dist/kernel/inode-table.d.ts +43 -0
  38. package/dist/kernel/inode-table.js +85 -0
  39. package/dist/kernel/kernel.d.ts +9 -0
  40. package/dist/kernel/kernel.js +1396 -0
  41. package/dist/kernel/permissions.d.ts +27 -0
  42. package/dist/kernel/permissions.js +118 -0
  43. package/dist/kernel/pipe-manager.d.ts +64 -0
  44. package/dist/kernel/pipe-manager.js +267 -0
  45. package/dist/kernel/proc-layer.d.ts +11 -0
  46. package/dist/kernel/proc-layer.js +501 -0
  47. package/dist/kernel/process-table.d.ts +124 -0
  48. package/dist/kernel/process-table.js +631 -0
  49. package/dist/kernel/pty.d.ts +108 -0
  50. package/dist/kernel/pty.js +541 -0
  51. package/dist/kernel/socket-table.d.ts +305 -0
  52. package/dist/kernel/socket-table.js +1124 -0
  53. package/dist/kernel/timer-table.d.ts +54 -0
  54. package/dist/kernel/timer-table.js +108 -0
  55. package/dist/kernel/types.d.ts +500 -0
  56. package/dist/kernel/types.js +89 -0
  57. package/dist/kernel/user.d.ts +29 -0
  58. package/dist/kernel/user.js +35 -0
  59. package/dist/kernel/vfs.d.ts +54 -0
  60. package/dist/kernel/vfs.js +8 -0
  61. package/dist/kernel/wait.d.ts +45 -0
  62. package/dist/kernel/wait.js +112 -0
  63. package/dist/kernel/wstatus.d.ts +21 -0
  64. package/dist/kernel/wstatus.js +33 -0
  65. package/dist/module-resolver.d.ts +4 -0
  66. package/dist/module-resolver.js +4 -0
  67. package/dist/package-bundler.d.ts +6 -1
  68. package/dist/runtime-driver.d.ts +3 -1
  69. package/dist/shared/bridge-contract.d.ts +529 -94
  70. package/dist/shared/bridge-contract.js +86 -3
  71. package/dist/shared/console-formatter.js +4 -0
  72. package/dist/shared/global-exposure.js +345 -0
  73. package/dist/shared/in-memory-fs.d.ts +30 -11
  74. package/dist/shared/in-memory-fs.js +383 -109
  75. package/dist/shared/permissions.d.ts +4 -6
  76. package/dist/shared/permissions.js +24 -28
  77. package/dist/types.d.ts +20 -130
  78. package/dist/types.js +5 -0
  79. package/package.json +12 -22
  80. package/dist/bridge/active-handles.d.ts +0 -22
  81. package/dist/bridge/active-handles.js +0 -55
  82. package/dist/bridge/child-process.d.ts +0 -99
  83. package/dist/bridge/child-process.js +0 -656
  84. package/dist/bridge/fs.d.ts +0 -281
  85. package/dist/bridge/fs.js +0 -2231
  86. package/dist/bridge/index.d.ts +0 -10
  87. package/dist/bridge/index.js +0 -41
  88. package/dist/bridge/module.d.ts +0 -75
  89. package/dist/bridge/module.js +0 -299
  90. package/dist/bridge/network.d.ts +0 -250
  91. package/dist/bridge/network.js +0 -1433
  92. package/dist/bridge/os.d.ts +0 -13
  93. package/dist/bridge/os.js +0 -256
  94. package/dist/bridge/polyfills.d.ts +0 -2
  95. package/dist/bridge/polyfills.js +0 -11
  96. package/dist/bridge/process.d.ts +0 -89
  97. package/dist/bridge/process.js +0 -994
  98. package/dist/bridge.js +0 -11766
  99. package/dist/python-runtime.d.ts +0 -16
  100. package/dist/python-runtime.js +0 -45
  101. package/dist/runtime.d.ts +0 -31
  102. package/dist/runtime.js +0 -69
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  (() => {
3
- // isolate-runtime/src/common/global-exposure.ts
3
+ // ../core/isolate-runtime/src/common/global-exposure.ts
4
4
  function defineRuntimeGlobalBinding(name, value, mutable) {
5
5
  Object.defineProperty(globalThis, name, {
6
6
  value,
@@ -21,7 +21,7 @@
21
21
  return createRuntimeGlobalExposer(true);
22
22
  }
23
23
 
24
- // isolate-runtime/src/inject/set-commonjs-file-globals.ts
24
+ // ../core/isolate-runtime/src/inject/set-commonjs-file-globals.ts
25
25
  var __runtimeExposeMutableGlobal = getRuntimeExposeMutableGlobal();
26
26
  var __commonJsFileConfig = globalThis.__runtimeCommonJsFileConfig ?? {};
27
27
  var __filePath = typeof __commonJsFileConfig.filePath === "string" ? __commonJsFileConfig.filePath : "/<entry>.js";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  (() => {
3
- // isolate-runtime/src/inject/set-stdin-data.ts
3
+ // ../core/isolate-runtime/src/inject/set-stdin-data.ts
4
4
  if (typeof globalThis._stdinData !== "undefined") {
5
5
  globalThis._stdinData = globalThis.__runtimeStdinData;
6
6
  globalThis._stdinPosition = 0;
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  (() => {
3
- // isolate-runtime/src/common/global-access.ts
3
+ // ../core/isolate-runtime/src/common/global-access.ts
4
4
  function isObjectLike(value) {
5
5
  return value !== null && (typeof value === "object" || typeof value === "function");
6
6
  }
7
7
 
8
- // isolate-runtime/src/common/global-exposure.ts
8
+ // ../core/isolate-runtime/src/common/global-exposure.ts
9
9
  function defineRuntimeGlobalBinding(name, value, mutable) {
10
10
  Object.defineProperty(globalThis, name, {
11
11
  value,
@@ -26,26 +26,57 @@
26
26
  return createRuntimeGlobalExposer(false);
27
27
  }
28
28
 
29
- // isolate-runtime/src/inject/setup-dynamic-import.ts
29
+ // ../core/isolate-runtime/src/inject/setup-dynamic-import.ts
30
30
  var __runtimeExposeCustomGlobal = getRuntimeExposeCustomGlobal();
31
31
  var __dynamicImportConfig = globalThis.__runtimeDynamicImportConfig ?? {};
32
32
  var __fallbackReferrer = typeof __dynamicImportConfig.referrerPath === "string" && __dynamicImportConfig.referrerPath.length > 0 ? __dynamicImportConfig.referrerPath : "/";
33
- var __dynamicImportHandler = async function(specifier, fromPath) {
33
+ var __dynamicImportCache = /* @__PURE__ */ new Map();
34
+ var __resolveDynamicImportPath = function(request, referrer) {
35
+ if (!request.startsWith("./") && !request.startsWith("../") && !request.startsWith("/")) {
36
+ return request;
37
+ }
38
+ const baseDir = referrer.endsWith("/") ? referrer : referrer.slice(0, referrer.lastIndexOf("/")) || "/";
39
+ const segments = baseDir.split("/").filter(Boolean);
40
+ for (const part of request.split("/")) {
41
+ if (part === "." || part.length === 0) continue;
42
+ if (part === "..") {
43
+ segments.pop();
44
+ continue;
45
+ }
46
+ segments.push(part);
47
+ }
48
+ return `/${segments.join("/")}`;
49
+ };
50
+ var __dynamicImportHandler = function(specifier, fromPath) {
34
51
  const request = String(specifier);
35
52
  const referrer = typeof fromPath === "string" && fromPath.length > 0 ? fromPath : __fallbackReferrer;
36
- const allowRequireFallback = request.endsWith(".cjs") || request.endsWith(".json");
37
- const namespace = await globalThis._dynamicImport(request, referrer);
38
- if (namespace !== null) {
39
- return namespace;
53
+ let resolved = null;
54
+ if (typeof globalThis._resolveModuleSync !== "undefined") {
55
+ resolved = globalThis._resolveModuleSync.applySync(
56
+ void 0,
57
+ [request, referrer, "import"]
58
+ );
40
59
  }
41
- if (!allowRequireFallback) {
42
- throw new Error("Cannot find module '" + request + "'");
60
+ const resolvedPath = typeof resolved === "string" && resolved.length > 0 ? resolved : __resolveDynamicImportPath(request, referrer);
61
+ const cacheKey = typeof resolved === "string" && resolved.length > 0 ? resolved : `${referrer}\0${request}`;
62
+ const cached = __dynamicImportCache.get(cacheKey);
63
+ if (cached) return Promise.resolve(cached);
64
+ if (typeof globalThis._requireFrom !== "function") {
65
+ throw new Error("Cannot load module: " + resolvedPath);
43
66
  }
44
- const runtimeRequire = globalThis.require;
45
- if (typeof runtimeRequire !== "function") {
46
- throw new Error("Cannot find module '" + request + "'");
67
+ let mod;
68
+ try {
69
+ mod = globalThis._requireFrom(resolved ?? request, referrer);
70
+ } catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") {
73
+ throw new Error("Cannot load module: " + resolvedPath);
74
+ }
75
+ if (message.startsWith("Cannot find module ")) {
76
+ throw new Error("Cannot load module: " + resolvedPath);
77
+ }
78
+ throw error;
47
79
  }
48
- const mod = runtimeRequire(request);
49
80
  const namespaceFallback = { default: mod };
50
81
  if (isObjectLike(mod)) {
51
82
  for (const key of Object.keys(mod)) {
@@ -54,7 +85,8 @@
54
85
  }
55
86
  }
56
87
  }
57
- return namespaceFallback;
88
+ __dynamicImportCache.set(cacheKey, namespaceFallback);
89
+ return Promise.resolve(namespaceFallback);
58
90
  };
59
91
  __runtimeExposeCustomGlobal("__dynamicImport", __dynamicImportHandler);
60
92
  })();
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  (() => {
3
- // isolate-runtime/src/common/global-exposure.ts
3
+ // ../core/isolate-runtime/src/common/global-exposure.ts
4
4
  function defineRuntimeGlobalBinding(name, value, mutable) {
5
5
  Object.defineProperty(globalThis, name, {
6
6
  value,
@@ -21,7 +21,7 @@
21
21
  return createRuntimeGlobalExposer(false);
22
22
  }
23
23
 
24
- // isolate-runtime/src/inject/setup-fs-facade.ts
24
+ // ../core/isolate-runtime/src/inject/setup-fs-facade.ts
25
25
  var __runtimeExposeCustomGlobal = getRuntimeExposeCustomGlobal();
26
26
  var __fsFacade = {};
27
27
  Object.defineProperties(__fsFacade, {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Command registry.
3
+ *
4
+ * Maps command names to runtime drivers. When a process calls
5
+ * spawn("grep", ...), the registry resolves "grep" to the WasmVM driver.
6
+ * Also populates /bin in the VFS so shell PATH lookup succeeds.
7
+ */
8
+ import type { RuntimeDriver } from "./types.js";
9
+ import type { VirtualFileSystem } from "./vfs.js";
10
+ export declare class CommandRegistry {
11
+ /** command name → RuntimeDriver */
12
+ private commands;
13
+ /** Warning log for command overrides. */
14
+ private warnings;
15
+ /**
16
+ * Register all commands from a driver.
17
+ * Last-mounted driver wins on conflicts (allows override with warning).
18
+ */
19
+ register(driver: RuntimeDriver): void;
20
+ /** Get recorded warnings (for testing). */
21
+ getWarnings(): readonly string[];
22
+ /**
23
+ * Register a single command to a driver.
24
+ * Used for on-demand dynamic registration (e.g. after tryResolve).
25
+ */
26
+ registerCommand(command: string, driver: RuntimeDriver): void;
27
+ /**
28
+ * Resolve a command name to a driver. Returns null if unknown.
29
+ * Supports path-based lookup: '/bin/ls' resolves to the driver for 'ls'.
30
+ */
31
+ resolve(command: string): RuntimeDriver | null;
32
+ /** List all registered commands. Returns command → driver name. */
33
+ list(): Map<string, string>;
34
+ /**
35
+ * Create a single /bin stub entry for a command.
36
+ * Used for on-demand registration after tryResolve discovers a new command.
37
+ */
38
+ populateBinEntry(vfs: VirtualFileSystem, command: string): Promise<void>;
39
+ /**
40
+ * Populate /bin in the VFS with stub entries for all registered commands.
41
+ * This enables brush-shell's PATH lookup to find commands.
42
+ */
43
+ populateBin(vfs: VirtualFileSystem): Promise<void>;
44
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Command registry.
3
+ *
4
+ * Maps command names to runtime drivers. When a process calls
5
+ * spawn("grep", ...), the registry resolves "grep" to the WasmVM driver.
6
+ * Also populates /bin in the VFS so shell PATH lookup succeeds.
7
+ */
8
+ export class CommandRegistry {
9
+ /** command name → RuntimeDriver */
10
+ commands = new Map();
11
+ /** Warning log for command overrides. */
12
+ warnings = [];
13
+ /**
14
+ * Register all commands from a driver.
15
+ * Last-mounted driver wins on conflicts (allows override with warning).
16
+ */
17
+ register(driver) {
18
+ for (const cmd of driver.commands) {
19
+ const existing = this.commands.get(cmd);
20
+ if (existing) {
21
+ const msg = `command "${cmd}" overridden: ${existing.name} → ${driver.name}`;
22
+ this.warnings.push(msg);
23
+ console.warn(`[CommandRegistry] ${msg}`);
24
+ }
25
+ this.commands.set(cmd, driver);
26
+ }
27
+ }
28
+ /** Get recorded warnings (for testing). */
29
+ getWarnings() {
30
+ return this.warnings;
31
+ }
32
+ /**
33
+ * Register a single command to a driver.
34
+ * Used for on-demand dynamic registration (e.g. after tryResolve).
35
+ */
36
+ registerCommand(command, driver) {
37
+ const existing = this.commands.get(command);
38
+ if (existing) {
39
+ const msg = `command "${command}" overridden: ${existing.name} → ${driver.name}`;
40
+ this.warnings.push(msg);
41
+ console.warn(`[CommandRegistry] ${msg}`);
42
+ }
43
+ this.commands.set(command, driver);
44
+ }
45
+ /**
46
+ * Resolve a command name to a driver. Returns null if unknown.
47
+ * Supports path-based lookup: '/bin/ls' resolves to the driver for 'ls'.
48
+ */
49
+ resolve(command) {
50
+ // Direct name lookup
51
+ const direct = this.commands.get(command);
52
+ if (direct)
53
+ return direct;
54
+ // Path-based: extract basename and retry
55
+ if (command.includes("/")) {
56
+ const basename = command.split("/").pop();
57
+ if (basename)
58
+ return this.commands.get(basename) ?? null;
59
+ }
60
+ return null;
61
+ }
62
+ /** List all registered commands. Returns command → driver name. */
63
+ list() {
64
+ const result = new Map();
65
+ for (const [cmd, driver] of this.commands) {
66
+ result.set(cmd, driver.name);
67
+ }
68
+ return result;
69
+ }
70
+ /**
71
+ * Create a single /bin stub entry for a command.
72
+ * Used for on-demand registration after tryResolve discovers a new command.
73
+ */
74
+ async populateBinEntry(vfs, command) {
75
+ if (!(await vfs.exists("/bin"))) {
76
+ await vfs.mkdir("/bin", { recursive: true });
77
+ }
78
+ const path = `/bin/${command}`;
79
+ if (!(await vfs.exists(path))) {
80
+ const stub = new TextEncoder().encode("#!/bin/sh\n# kernel command stub\n");
81
+ await vfs.writeFile(path, stub);
82
+ try {
83
+ await vfs.chmod(path, 0o755);
84
+ }
85
+ catch {
86
+ // chmod may not be supported by all VFS backends
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Populate /bin in the VFS with stub entries for all registered commands.
92
+ * This enables brush-shell's PATH lookup to find commands.
93
+ */
94
+ async populateBin(vfs) {
95
+ // Ensure /bin exists
96
+ if (!(await vfs.exists("/bin"))) {
97
+ await vfs.mkdir("/bin", { recursive: true });
98
+ }
99
+ // Create a stub file for each command
100
+ const stub = new TextEncoder().encode("#!/bin/sh\n# kernel command stub\n");
101
+ for (const cmd of this.commands.keys()) {
102
+ const path = `/bin/${cmd}`;
103
+ if (!(await vfs.exists(path))) {
104
+ await vfs.writeFile(path, stub);
105
+ try {
106
+ await vfs.chmod(path, 0o755);
107
+ }
108
+ catch {
109
+ // chmod may not be supported by all VFS backends
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Device layer.
3
+ *
4
+ * Intercepts device node paths (/dev/*) before they reach the VFS backend.
5
+ * Wraps a VirtualFileSystem and handles device-specific read/write semantics.
6
+ */
7
+ import type { VirtualFileSystem } from "./vfs.js";
8
+ /**
9
+ * Wrap a VFS with device node interception.
10
+ * Device paths are handled directly; all other paths pass through.
11
+ */
12
+ export declare function createDeviceLayer(vfs: VirtualFileSystem): VirtualFileSystem;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Device layer.
3
+ *
4
+ * Intercepts device node paths (/dev/*) before they reach the VFS backend.
5
+ * Wraps a VirtualFileSystem and handles device-specific read/write semantics.
6
+ */
7
+ import { KernelError } from "./types.js";
8
+ const DEVICE_PATHS = new Set([
9
+ "/dev/null",
10
+ "/dev/zero",
11
+ "/dev/stdin",
12
+ "/dev/stdout",
13
+ "/dev/stderr",
14
+ "/dev/urandom",
15
+ "/dev/random",
16
+ "/dev/tty",
17
+ "/dev/console",
18
+ "/dev/full",
19
+ "/dev/ptmx",
20
+ ]);
21
+ const DEVICE_INO = {
22
+ "/dev/null": 0xffff_0001,
23
+ "/dev/zero": 0xffff_0002,
24
+ "/dev/stdin": 0xffff_0003,
25
+ "/dev/stdout": 0xffff_0004,
26
+ "/dev/stderr": 0xffff_0005,
27
+ "/dev/urandom": 0xffff_0006,
28
+ "/dev/random": 0xffff_0007,
29
+ "/dev/tty": 0xffff_0008,
30
+ "/dev/console": 0xffff_0009,
31
+ "/dev/full": 0xffff_000a,
32
+ "/dev/ptmx": 0xffff_000b,
33
+ };
34
+ /** Device pseudo-directories that contain dynamic entries. */
35
+ const DEVICE_DIRS = new Set(["/dev/fd", "/dev/pts", "/dev/shm"]);
36
+ function isDevicePath(path) {
37
+ return DEVICE_PATHS.has(path) || path.startsWith("/dev/fd/") || path.startsWith("/dev/pts/");
38
+ }
39
+ function isDeviceDir(path) {
40
+ return path === "/dev" || DEVICE_DIRS.has(path);
41
+ }
42
+ function deviceStat(path) {
43
+ const now = Date.now();
44
+ return {
45
+ mode: 0o666,
46
+ size: 0,
47
+ isDirectory: false,
48
+ isSymbolicLink: false,
49
+ atimeMs: now,
50
+ mtimeMs: now,
51
+ ctimeMs: now,
52
+ birthtimeMs: now,
53
+ ino: DEVICE_INO[path] ?? 0xffff_0000,
54
+ nlink: 1,
55
+ uid: 0,
56
+ gid: 0,
57
+ };
58
+ }
59
+ const DEV_DIR_ENTRIES = [
60
+ { name: "null", isDirectory: false },
61
+ { name: "zero", isDirectory: false },
62
+ { name: "stdin", isDirectory: false },
63
+ { name: "stdout", isDirectory: false },
64
+ { name: "stderr", isDirectory: false },
65
+ { name: "urandom", isDirectory: false },
66
+ { name: "random", isDirectory: false },
67
+ { name: "tty", isDirectory: false },
68
+ { name: "console", isDirectory: false },
69
+ { name: "full", isDirectory: false },
70
+ { name: "ptmx", isDirectory: false },
71
+ { name: "fd", isDirectory: true },
72
+ { name: "pts", isDirectory: true },
73
+ { name: "shm", isDirectory: true },
74
+ ];
75
+ /**
76
+ * Wrap a VFS with device node interception.
77
+ * Device paths are handled directly; all other paths pass through.
78
+ */
79
+ export function createDeviceLayer(vfs) {
80
+ const wrapped = {
81
+ prepareOpenSync(path, flags) {
82
+ if (isDevicePath(path) || isDeviceDir(path))
83
+ return false;
84
+ const syncVfs = vfs;
85
+ return syncVfs.prepareOpenSync?.(path, flags) ?? false;
86
+ },
87
+ async readFile(path) {
88
+ if (path === "/dev/null" || path === "/dev/full")
89
+ return new Uint8Array(0);
90
+ if (path === "/dev/zero")
91
+ return new Uint8Array(4096);
92
+ if (path === "/dev/urandom" || path === "/dev/random") {
93
+ const buf = new Uint8Array(4096);
94
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
95
+ globalThis.crypto.getRandomValues(buf);
96
+ }
97
+ else {
98
+ for (let i = 0; i < buf.length; i++) {
99
+ buf[i] = (Math.random() * 256) | 0;
100
+ }
101
+ }
102
+ return buf;
103
+ }
104
+ if (path === "/dev/tty" || path === "/dev/console" || path === "/dev/ptmx")
105
+ return new Uint8Array(0);
106
+ return vfs.readFile(path);
107
+ },
108
+ async pread(path, offset, length) {
109
+ if (path === "/dev/null" || path === "/dev/full")
110
+ return new Uint8Array(0);
111
+ if (path === "/dev/zero")
112
+ return new Uint8Array(length);
113
+ if (path === "/dev/urandom" || path === "/dev/random") {
114
+ const buf = new Uint8Array(length);
115
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
116
+ globalThis.crypto.getRandomValues(buf);
117
+ }
118
+ else {
119
+ for (let i = 0; i < buf.length; i++) {
120
+ buf[i] = (Math.random() * 256) | 0;
121
+ }
122
+ }
123
+ return buf;
124
+ }
125
+ if (path === "/dev/tty" || path === "/dev/console" || path === "/dev/ptmx")
126
+ return new Uint8Array(0);
127
+ return vfs.pread(path, offset, length);
128
+ },
129
+ async readTextFile(path) {
130
+ if (path === "/dev/null")
131
+ return "";
132
+ const bytes = await this.readFile(path);
133
+ return new TextDecoder().decode(bytes);
134
+ },
135
+ async readDir(path) {
136
+ if (path === "/dev") {
137
+ return DEV_DIR_ENTRIES.map((e) => e.name);
138
+ }
139
+ // /dev/fd and /dev/pts are dynamic — return empty at VFS level
140
+ if (DEVICE_DIRS.has(path))
141
+ return [];
142
+ return vfs.readDir(path);
143
+ },
144
+ async readDirWithTypes(path) {
145
+ if (path === "/dev") {
146
+ return DEV_DIR_ENTRIES;
147
+ }
148
+ if (DEVICE_DIRS.has(path))
149
+ return [];
150
+ return vfs.readDirWithTypes(path);
151
+ },
152
+ async writeFile(path, content) {
153
+ // /dev/full always returns ENOSPC on write (POSIX behavior)
154
+ if (path === "/dev/full")
155
+ throw new KernelError("ENOSPC", "No space left on device");
156
+ // Discard writes to sink devices
157
+ if (path === "/dev/null" || path === "/dev/zero" || path === "/dev/urandom"
158
+ || path === "/dev/random" || path === "/dev/tty" || path === "/dev/console"
159
+ || path === "/dev/ptmx")
160
+ return;
161
+ return vfs.writeFile(path, content);
162
+ },
163
+ async createDir(path) {
164
+ if (isDeviceDir(path))
165
+ return;
166
+ return vfs.createDir(path);
167
+ },
168
+ async mkdir(path, options) {
169
+ if (isDeviceDir(path))
170
+ return;
171
+ return vfs.mkdir(path, options);
172
+ },
173
+ async exists(path) {
174
+ if (isDevicePath(path) || isDeviceDir(path))
175
+ return true;
176
+ return vfs.exists(path);
177
+ },
178
+ async stat(path) {
179
+ if (isDevicePath(path))
180
+ return deviceStat(path);
181
+ if (isDeviceDir(path)) {
182
+ const now = Date.now();
183
+ return {
184
+ mode: 0o755,
185
+ size: 0,
186
+ isDirectory: true,
187
+ isSymbolicLink: false,
188
+ atimeMs: now,
189
+ mtimeMs: now,
190
+ ctimeMs: now,
191
+ birthtimeMs: now,
192
+ ino: DEVICE_INO[path] ?? 0xffff_0000,
193
+ nlink: 2,
194
+ uid: 0,
195
+ gid: 0,
196
+ };
197
+ }
198
+ return vfs.stat(path);
199
+ },
200
+ async removeFile(path) {
201
+ if (isDevicePath(path))
202
+ throw new KernelError("EPERM", "cannot remove device");
203
+ return vfs.removeFile(path);
204
+ },
205
+ async removeDir(path) {
206
+ if (isDeviceDir(path))
207
+ throw new KernelError("EPERM", `cannot remove ${path}`);
208
+ return vfs.removeDir(path);
209
+ },
210
+ async rename(oldPath, newPath) {
211
+ if (isDevicePath(oldPath) || isDevicePath(newPath)) {
212
+ throw new KernelError("EPERM", "cannot rename device");
213
+ }
214
+ return vfs.rename(oldPath, newPath);
215
+ },
216
+ async realpath(path) {
217
+ if (isDevicePath(path) || isDeviceDir(path))
218
+ return path;
219
+ return vfs.realpath(path);
220
+ },
221
+ // Passthrough for POSIX extensions
222
+ async symlink(target, linkPath) {
223
+ return vfs.symlink(target, linkPath);
224
+ },
225
+ async readlink(path) {
226
+ return vfs.readlink(path);
227
+ },
228
+ async lstat(path) {
229
+ if (isDevicePath(path))
230
+ return deviceStat(path);
231
+ if (isDeviceDir(path))
232
+ return this.stat(path);
233
+ return vfs.lstat(path);
234
+ },
235
+ async link(oldPath, newPath) {
236
+ if (isDevicePath(oldPath))
237
+ throw new KernelError("EPERM", "cannot link device");
238
+ return vfs.link(oldPath, newPath);
239
+ },
240
+ async chmod(path, mode) {
241
+ if (isDevicePath(path))
242
+ return;
243
+ return vfs.chmod(path, mode);
244
+ },
245
+ async chown(path, uid, gid) {
246
+ if (isDevicePath(path))
247
+ return;
248
+ return vfs.chown(path, uid, gid);
249
+ },
250
+ async utimes(path, atime, mtime) {
251
+ if (isDevicePath(path))
252
+ return;
253
+ return vfs.utimes(path, atime, mtime);
254
+ },
255
+ async truncate(path, length) {
256
+ if (isDevicePath(path))
257
+ return;
258
+ return vfs.truncate(path, length);
259
+ },
260
+ };
261
+ return wrapped;
262
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Kernel DNS cache shared across runtimes.
3
+ *
4
+ * Runtimes call kernel DNS cache before falling through to the host
5
+ * adapter. Entries expire after their TTL.
6
+ */
7
+ import type { DnsResult } from "./host-adapter.js";
8
+ export interface DnsCacheOptions {
9
+ /** Default TTL in milliseconds when none is specified. Default: 30000 (30s). */
10
+ defaultTtlMs?: number;
11
+ }
12
+ export declare class DnsCache {
13
+ private cache;
14
+ private defaultTtlMs;
15
+ constructor(options?: DnsCacheOptions);
16
+ /**
17
+ * Look up a cached DNS result. Returns null on miss or expired entry.
18
+ */
19
+ lookup(hostname: string, rrtype: string): DnsResult | null;
20
+ /**
21
+ * Store a DNS result with TTL.
22
+ * @param ttlMs TTL in milliseconds. Uses defaultTtlMs if not provided.
23
+ */
24
+ store(hostname: string, rrtype: string, result: DnsResult, ttlMs?: number): void;
25
+ /** Flush all cached entries. */
26
+ flush(): void;
27
+ /** Number of entries (including possibly expired). */
28
+ get size(): number;
29
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Kernel DNS cache shared across runtimes.
3
+ *
4
+ * Runtimes call kernel DNS cache before falling through to the host
5
+ * adapter. Entries expire after their TTL.
6
+ */
7
+ export class DnsCache {
8
+ cache = new Map();
9
+ defaultTtlMs;
10
+ constructor(options) {
11
+ this.defaultTtlMs = options?.defaultTtlMs ?? 30_000;
12
+ }
13
+ /**
14
+ * Look up a cached DNS result. Returns null on miss or expired entry.
15
+ */
16
+ lookup(hostname, rrtype) {
17
+ const key = cacheKey(hostname, rrtype);
18
+ const entry = this.cache.get(key);
19
+ if (!entry)
20
+ return null;
21
+ // Expired — remove and return miss
22
+ if (Date.now() >= entry.expiresAt) {
23
+ this.cache.delete(key);
24
+ return null;
25
+ }
26
+ return entry.result;
27
+ }
28
+ /**
29
+ * Store a DNS result with TTL.
30
+ * @param ttlMs TTL in milliseconds. Uses defaultTtlMs if not provided.
31
+ */
32
+ store(hostname, rrtype, result, ttlMs) {
33
+ const key = cacheKey(hostname, rrtype);
34
+ const ttl = ttlMs ?? this.defaultTtlMs;
35
+ this.cache.set(key, {
36
+ result,
37
+ expiresAt: Date.now() + ttl,
38
+ });
39
+ }
40
+ /** Flush all cached entries. */
41
+ flush() {
42
+ this.cache.clear();
43
+ }
44
+ /** Number of entries (including possibly expired). */
45
+ get size() {
46
+ return this.cache.size;
47
+ }
48
+ }
49
+ /** Canonical cache key: "hostname:rrtype" */
50
+ function cacheKey(hostname, rrtype) {
51
+ return `${hostname}:${rrtype}`;
52
+ }