first commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# ModuleResolver
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`ModuleResolver` is the gatekeeper between user code and the host's module surface. It maintains an allowlist of modules the sandbox may consult, refuses to register anything on the denylist (`fs`, `net`, `child_process`, `cluster`, `dgram`, `dns`, `http`, `https`, `http2`, `tls`, `worker_threads`, `process`, `os`, `path`, `readline`, `repl`, `vm`), and ships three built-in safe modules (`Math`, `JSON`, `Date`) auto-loaded when their names appear in the allowed list.
|
||||
|
||||
The resolver is the only place in the package where module names are reasoned about. Engines consume `resolver.getModules()` and inject the returned exports into the execution context; they never reason about names directly. This makes the security boundary auditable in one file rather than spread across four engine implementations.
|
||||
|
||||
## Features
|
||||
|
||||
- Hard-coded denylist of 17 dangerous Node module names.
|
||||
- Three built-in safe modules with curated exports.
|
||||
- `register` / `unregister` for custom modules.
|
||||
- `isBlocked` / `isAllowed` / `getBlockedModules` introspection helpers.
|
||||
- `BLOCKED_MODULES` and `BUILTIN_SAFE_MODULES` exported for downstream
|
||||
composition.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { ModuleResolver } from "@suaveplan/sandbox/module-resolver";
|
||||
|
||||
const resolver = new ModuleResolver(["Math"]);
|
||||
resolver.register({ name: "config", exports: { factor: 21 } });
|
||||
console.log(resolver.getModules().length); // 2
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Surface the denylist to a UI that shows users why their module registration
|
||||
was rejected:
|
||||
|
||||
```ts
|
||||
import {
|
||||
BLOCKED_MODULES,
|
||||
ModuleResolver,
|
||||
} from "@suaveplan/sandbox/module-resolver";
|
||||
|
||||
function offerSuggestions(name: string): string {
|
||||
if (BLOCKED_MODULES.has(name)) {
|
||||
return `"${name}" is denylisted — use the sandbox's host bridge instead.`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new ModuleResolver(allowedNames?: readonly string[])`.
|
||||
- `register(module: SandboxModule): void` — throws `SandboxError(BLOCKED_MODULE)`.
|
||||
- `unregister(name: string): boolean`.
|
||||
- `getModules(): readonly SandboxModule[]`.
|
||||
- `isBlocked(name: string): boolean`.
|
||||
- `isAllowed(name: string): boolean`.
|
||||
- `getBlockedModules(): readonly string[]`.
|
||||
- `BLOCKED_MODULES: ReadonlySet<string>`.
|
||||
- `BUILTIN_SAFE_MODULES: Readonly<Record<string, () => SandboxModule>>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The denylist is hard-coded rather than configurable because it lists Node
|
||||
intrinsics whose mere presence inside a sandbox compromises isolation
|
||||
regardless of whether the engine is a vm context, a worker, or a child
|
||||
process. The `BUILTIN_SAFE_MODULES` factory returns a fresh module per call so
|
||||
custom rebinding by one sandbox does not leak into another sharing the
|
||||
resolver constructor.
|
||||
Reference in New Issue
Block a user