Skip to main content

Example on GitHub

Runnable example for capturing console output.
Console output from sandboxed code is not buffered into result fields. exec() and run() do not return stdout or stderr. Use the onStdio hook to capture output.

Runnable example

import {
  NodeRuntime,
  createNodeDriver,
  createNodeRuntimeDriverFactory,
} from "../../../packages/secure-exec/src/index.ts";

const events: string[] = [];

const runtime = new NodeRuntime({
  systemDriver: createNodeDriver(),
  runtimeDriverFactory: createNodeRuntimeDriverFactory(),
});

try {
  const result = await runtime.exec(
    `
      console.log("hello from the sandbox");
      console.error("oops from the sandbox");
    `,
    {
      onStdio: (event) => {
        events.push(`[${event.channel}] ${event.message}`);
      },
    }
  );

  if (result.code !== 0) {
    throw new Error(`Unexpected execution result: ${JSON.stringify(result)}`);
  }

  const expected = [
    "[stdout] hello from the sandbox",
    "[stderr] oops from the sandbox",
  ];

  if (JSON.stringify(events) !== JSON.stringify(expected)) {
    throw new Error(`Unexpected stdio events: ${JSON.stringify(events)}`);
  }

  console.log(
    JSON.stringify({
      ok: true,
      events,
      summary: "captured stdout and stderr with onStdio",
    }),
  );
} finally {
  runtime.dispose();
}
Source: examples/features/src/output-capture.ts

Default hook

Set a runtime-level hook that applies to all executions:
const runtime = new NodeRuntime({
  systemDriver: createNodeDriver(),
  runtimeDriverFactory: createNodeRuntimeDriverFactory(),
  onStdio: (event) => console.log(event.message),
});
Per-execution hooks override the default.

StdioHook type

type StdioHook = (event: {
  channel: "stdout" | "stderr";
  message: string;
}) => void;

Patterns

Collect to array:
const output: string[] = [];
await runtime.exec(code, {
  onStdio: (e) => output.push(e.message),
});
Stream to logger:
await runtime.exec(code, {
  onStdio: (e) => {
    if (e.channel === "stderr") logger.warn(e.message);
    else logger.info(e.message);
  },
});
Filter by channel:
const errors: string[] = [];
await runtime.exec(code, {
  onStdio: (e) => {
    if (e.channel === "stderr") errors.push(e.message);
  },
});

Why no buffering?

Buffering console output by default would let untrusted code grow host memory without bound by logging at high volume. The explicit onStdio hook puts you in control of how output is stored and when to stop collecting.