This document defines the public API surface of MoonBash, designed to be 100% compatible with just-bash.
Status note (2026-05-16): MoonBash is aligning to just-bash@3.0.1. The compatibility work now covers root exports, command-name helpers, ByteString helpers, compile-only public type consumers for root and browser entries, low-risk ExecOptions fields (replaceEnv, stdinKind, args, pre-aborted signal), deprecated top-level execution limit aliases, public processInfo and trace types, processInfo runtime mapping for $$, $BASHPID, $PPID, $UID, and $EUID, public async InMemoryFs, MountableFs routing/cross-filesystem behavior, root-confined ReadWriteFs, copy-on-write OverlayFs, the Vercel-style Sandbox object model, the Transform/parser facade for simple commands, pipelines, basic if/for/while/until/function AST, command substitutions, and recursive command collection, package exports for Node ESM, CommonJS, browser, and executor subpaths, a basic TypeScript js-exec facade for -c, script files, -- script separation, upstream-style CLI errors/version output, bootstrap, top-level await, javascript.invokeTool tools proxy calls, CommonJS and module-mode fs/path shims backed by the VFS, and the upstream node stub, plus an executor companion for inline tools and setup custom sources with approval and elicitation gates. Full QuickJS isolation, the remaining Node-compatible module shims and full ESM loader support, GraphQL/OpenAPI/MCP executor SDK discovery, SDK-native elicitation, and full upstream AST coverage are planned in separate phases.
The main entry point for creating and interacting with a Bash interpreter instance.
class Bash {
constructor(options?: BashOptions);
/**
* Execute a bash script and return the result.
*/
exec(script: string, options?: ExecOptions): Promise<BashExecResult>;
/**
* Get the current filesystem instance.
*/
getFs(): IFileSystem;
/**
* Get the current working directory.
*/
getCwd(): string;
/**
* Get the current environment variables.
*/
getEnv(): Record<string, string>;
/**
* Register a transform plugin for AST manipulation.
*/
registerTransformPlugin(plugin: TransformPlugin): void;
}Vercel Sandbox API-compatible wrapper around Bash.
class Sandbox {
static create(options?: SandboxOptions): Promise<Sandbox>;
/**
* Execute a command in the sandbox and return a finished command.
*/
runCommand(params: RunCommandParams): Promise<CommandFinished>;
runCommand(command: string, args: string[]): Promise<CommandFinished>;
runCommand(command: string, opts?: { cwd?: string; env?: Record<string, string> }): Promise<CommandFinished>;
/**
* Get the underlying Bash instance.
*/
get bashEnvInstance(): Bash;
}interface BashOptions {
/**
* Initial files to populate the virtual filesystem.
* Keys are absolute paths, values are file contents.
*/
files?: InitialFiles;
/**
* Initial environment variables.
*/
env?: Record<string, string>;
/**
* Initial working directory. Default: "/home/user"
*/
cwd?: string;
/**
* Filesystem implementation. Default: InMemoryFs
*/
fs?: IFileSystem;
/**
* Execution safety limits.
*/
executionLimits?: ExecutionLimits;
/**
* Network access configuration. Default: disabled
*/
network?: NetworkConfig;
/**
* Restrict available commands to this list.
* If not specified, all commands are available.
*/
commands?: CommandName[];
/**
* Custom sleep implementation (for testing).
*/
sleep?: (ms: number) => Promise<void>;
/**
* Register custom commands.
*/
customCommands?: CustomCommand[];
/**
* Logging configuration.
*/
logger?: BashLogger;
/**
* Performance tracing callback.
*/
trace?: TraceCallback;
/**
* Defense-in-depth security configuration.
*/
defenseInDepth?: DefenseInDepthConfig | boolean;
}interface ExecOptions {
/**
* Additional environment variables for this execution only.
*/
env?: Record<string, string>;
/**
* Override working directory for this execution only.
*/
cwd?: string;
/**
* If true, skip script preprocessing.
*/
rawScript?: boolean;
/**
* Provide stdin input for the script.
*/
stdin?: string;
}interface ExecutionLimits {
/** Max function call/recursion depth. Default: 100 */
maxCallDepth?: number;
/** Max total commands executed. Default: 10000 */
maxCommandCount?: number;
/** Max iterations per loop. Default: 10000 */
maxLoopIterations?: number;
/** Max AWK loop iterations. Default: 10000 */
maxAwkIterations?: number;
/** Max SED loop iterations. Default: 10000 */
maxSedIterations?: number;
/** Max jq iterations. Default: 10000 */
maxJqIterations?: number;
/** Max glob filesystem operations. Default: 100000 */
maxGlobOperations?: number;
/** Max string length in bytes. Default: 10485760 (10MB) */
maxStringLength?: number;
/** Max array elements. Default: 100000 */
maxArrayElements?: number;
/** Max heredoc size. Default: 10485760 (10MB) */
maxHeredocSize?: number;
/** Max command substitution nesting depth. Default: 50 */
maxSubstitutionDepth?: number;
}interface NetworkConfig {
/**
* URL prefixes that are allowed for network access.
* Must include protocol and path prefix.
* Example: ["https://api.github.com/repos/myorg/"]
*/
allowedUrlPrefixes: string[];
/**
* Allowed HTTP methods. Default: ["GET", "HEAD"]
*/
allowedMethods?: HttpMethod[];
/**
* Max redirects to follow. Default: 20
*/
maxRedirects?: number;
/**
* Request timeout in milliseconds. Default: 30000
*/
timeoutMs?: number;
/**
* Max response body size in bytes. Default: 10485760 (10MB)
*/
maxResponseSize?: number;
}
type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH";interface ExecResult {
/** Standard output text. */
stdout: string;
/** Standard error text. */
stderr: string;
/** Process exit code. 0 = success. */
exitCode: number;
}interface BashExecResult extends ExecResult {
/** Environment variables after execution. */
env: Record<string, string>;
/** Optional metadata from transform plugins. */
metadata?: Record<string, unknown>;
}All filesystem implementations must satisfy this interface.
interface IFileSystem {
readFile(path: string, options?: { encoding?: string }): Promise<string>;
readFileBuffer(path: string): Promise<Uint8Array>;
writeFile(path: string, content: string | Uint8Array,
options?: { mode?: number }): Promise<void>;
appendFile(path: string, content: string): Promise<void>;
exists(path: string): Promise<boolean>;
stat(path: string): Promise<FsStat>;
readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
cp(src: string, dest: string,
options?: { recursive?: boolean }): Promise<void>;
symlink(target: string, path: string): Promise<void>;
readlink(path: string): Promise<string>;
chmod(path: string, mode: number): Promise<void>;
}interface FsStat {
isFile: boolean;
isDirectory: boolean;
isSymbolicLink: boolean;
size: number;
mode: number;
mtime: Date;
}interface DirentEntry {
name: string;
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
}class InMemoryFs implements IFileSystem {
constructor(initialFiles?: InitialFiles);
// Async just-bash-compatible memory filesystem:
// read/write/append, binary reads, lazy files, directories,
// symlinks, hard links, stat/lstat/realpath, chmod, utimes,
// readdirWithFileTypes, and path resolution.
}class OverlayFs implements IFileSystem {
constructor(options: OverlayFsOptions);
}
interface OverlayFsOptions {
/** Root directory on real filesystem to read from. */
root: string;
/** Virtual mount point. Default: "/home/user/project" */
mountPoint?: string;
/** If true, writes throw an error. Default: false */
readOnly?: boolean;
/** Max file size to read from disk. Default: 10MB */
maxFileReadSize?: number;
/** Whether real and virtual symlinks are allowed. Default: false */
allowSymlinks?: boolean;
}class ReadWriteFs implements IFileSystem {
constructor(options: ReadWriteFsOptions);
// Direct root-confined read/write to real filesystem via Node.js fs module.
}class MountableFs implements IFileSystem {
constructor(options?: MountableFsOptions);
/**
* Mount a filesystem at a virtual path.
* Cannot mount at root "/".
*/
mount(mountPoint: string, fs: IFileSystem): void;
/**
* Unmount a filesystem.
*/
unmount(mountPoint: string): void;
getMounts(): ReadonlyArray<{ mountPoint: string; filesystem: IFileSystem }>;
isMountPoint(path: string): boolean;
}interface CustomCommand {
name: string;
execute(args: string[], ctx: CommandContext): Promise<ExecResult>;
}
// Lazy-loaded variant for code splitting
interface LazyCommand {
name: string;
load(): Promise<CustomCommand>;
}
/**
* Helper to define a custom command.
*/
function defineCommand(
name: string,
fn: (args: string[], ctx: CommandContext) => Promise<ExecResult>
): CustomCommand;Provided to all command implementations during execution.
interface CommandContext {
/** Virtual filesystem handle. */
fs: IFileSystem;
/** Current working directory. */
cwd: string;
/** Environment variables (Map to prevent prototype pollution). */
env: Map<string, string>;
/** Standard input for this command. */
stdin: string;
/** Execution limits. */
limits: Required<ExecutionLimits>;
/** Tracing callback. */
trace?: TraceCallback;
/** Execute a sub-command. */
exec?: (command: string, options?: ExecOptions) => Promise<ExecResult>;
/** Network fetch (only if network enabled). */
fetch?: SecureFetch;
/** Get all registered command names. */
getRegisteredCommands?: () => string[];
/** Custom sleep function. */
sleep?: (ms: number) => Promise<void>;
/** Open file descriptors. */
fileDescriptors?: Map<number, string>;
}interface TransformPlugin {
/** Unique plugin name. */
name: string;
/**
* Transform the AST before execution.
* Can modify, wrap, or analyze the AST.
*/
transform(ctx: TransformContext): TransformResult;
}
interface TransformContext {
/** The parsed AST. */
ast: ScriptNode;
/** The original script text. */
source: string;
}
interface TransformResult {
/** The (possibly modified) AST. */
ast: ScriptNode;
/** Optional metadata returned in BashExecResult. */
metadata?: Record<string, unknown>;
}interface DefenseInDepthConfig {
/** Enable defense-in-depth protections. Default: true */
enabled?: boolean;
/** Log violations without blocking. Default: false */
auditMode?: boolean;
/** Callback when a violation is detected. */
onViolation?: (violation: SecurityViolation) => void;
/** Exclude specific violation types from checking. */
excludeViolationTypes?: SecurityViolationType[];
}
interface SecurityViolation {
type: SecurityViolationType;
message: string;
stack?: string;
}
type SecurityViolationType =
| "function_constructor"
| "eval"
| "timers"
| "async_constructors"
| "weak_refs"
| "reflect"
| "proxy"
| "process"
| "require"
| "webassembly"
| "shared_memory";interface BashLogger {
info(message: string, data?: unknown): void;
debug(message: string, data?: unknown): void;
}
type TraceCallback = (event: TraceEvent) => void;
interface TraceEvent {
category: string; // "command", "expansion", "pipeline", etc.
name: string; // Specific operation name
durationMs: number; // Execution duration
metadata?: Record<string, unknown>;
}/**
* Mapping of absolute file paths to file contents.
* Used to pre-populate the virtual filesystem.
*/
type InitialFiles = Record<string, string | Uint8Array>;import { Bash } from "moon-bash";
const bash = new Bash();
const result = await bash.exec("echo hello world");
// result.stdout === "hello world\n"
// result.exitCode === 0const bash = new Bash({
files: {
"/data/input.csv": "name,age\nAlice,30\nBob,25\n",
"/scripts/process.sh": "#!/bin/bash\ncat /data/input.csv | sort",
},
});
const result = await bash.exec("bash /scripts/process.sh");import { Bash, defineCommand } from "moon-bash";
const fetchData = defineCommand("fetch-data", async (args, ctx) => {
return {
stdout: JSON.stringify({ result: "ok" }),
stderr: "",
exitCode: 0,
};
});
const bash = new Bash({ customCommands: [fetchData] });
const result = await bash.exec("fetch-data | jq .result");import { Bash, OverlayFs } from "moon-bash";
const fs = new OverlayFs({
root: "/real/project/path",
mountPoint: "/home/user/project",
});
const bash = new Bash({ fs });
// Reads from real filesystem, writes go to memory only
const result = await bash.exec("cat README.md | wc -l");const bash = new Bash({
network: {
allowedUrlPrefixes: ["https://api.github.com/repos/myorg/"],
allowedMethods: ["GET"],
timeoutMs: 10000,
},
});
const result = await bash.exec(
'curl -s https://api.github.com/repos/myorg/myrepo | jq .stargazers_count'
);const bash = new Bash({
commands: ["echo", "cat", "grep", "sort", "uniq", "wc"],
});
// Only listed commands are available
// Any other command will return "command not found"import { Bash, TransformPlugin } from "moon-bash";
class AuditPlugin implements TransformPlugin {
name = "audit";
commands: string[] = [];
transform(ctx) {
// Walk AST and collect command names
this.collectCommands(ctx.ast);
return {
ast: ctx.ast,
metadata: { auditedCommands: this.commands },
};
}
}
const audit = new AuditPlugin();
const bash = new Bash();
bash.registerTransformPlugin(audit);
const result = await bash.exec("echo hello | grep hello");
console.log(result.metadata?.auditedCommands); // ["echo", "grep"]