npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mininux

v0.1.3

Published

A lightweight, embeddable Linux sandbox running in WASM. Execute shell commands, Python scripts, and create isolated sessions—perfect for sandboxed code execution, LLM backends, and educational projects.

Readme

MiniNux 🐧

A lightweight, embeddable Linux sandbox running in WASM. Execute shell commands, Python scripts, and create isolated sessions—all in your browser or Node.js backend.

🎉 This is a fun experimental project — it's not for production critical workloads, but perfect for learning, demos, LLM agents, CTF challenges, and creative hacks.

Features

  • Tiny & Fast — 32 MiB WASM VM with BusyBox, MicroPython, and v86 emulation
  • 🔒 Isolated Sessions — Separate workspaces with environment state and RAM filesystems
  • 🐍 Python Ready — MicroPython built-in (python / python3) for data processing and scripts
  • 🟢 Node.js & npm — opt-in profile: "node" boots real Node.js 22 with a working npm install
  • 🌐 Opt-in Networkingnetwork: true gives the guest real outbound TCP (DNS, HTTPS) via an in-process Wisp relay
  • 📦 Easy Embedding — One Linux instance = one VM, multiple instances = parallel execution
  • 🎯 Session State — Environment variables and working directory persist across commands
  • ⏱️ Timeouts & Controls — Per-command timeoutMs, kill long-running processes
  • 🔍 Structured Outputstdout, stderr, exitCode, timedOut, and durationMs in every response

Quick Start

Installation

npm install mininux

Basic Usage

Works out of the box in Node.js and Bun — the Linux assets ship inside the package:

import { Linux } from "mininux";

const linux = new Linux();
await linux.start(); // optional — execute() boots the VM on first use

// Execute a command
const result = await linux.execute('echo "Hello from MiniNux!"');
console.log(result.stdout);   // "Hello from MiniNux!"
console.log(result.exitCode); // 0

await linux.destroy(); // shut the VM down when you're done

Your project needs "type": "module" in package.json (or use an .mjs file).

Browser Usage

In the browser, serve the four asset files (v86.wasm, seabios.bin, vgabios.bin, bzImage from node_modules/mininux/dist/assets) under /mini-linux/ — that's the default path — and the same zero-config code works. To serve them somewhere else, pass assets:

const linux = new Linux({
  assets: {
    wasm: '/my-assets/v86.wasm',
    bios: '/my-assets/seabios.bin',
    vgaBios: '/my-assets/vgabios.bin',
    kernel: '/my-assets/bzImage',
  },
});

Working with Sessions

Sessions maintain state across commands—environment variables, working directory, and files. session() is synchronous and returns a LinuxSession:

const session = linux.session('my-workspace');

// Commands run in the same context
await session.execute('export NAME=MiniNux');
await session.execute('cd /tmp');
const result = await session.execute('echo $NAME && pwd');
console.log(result.stdout);
// Output:
// MiniNux
// /tmp

// Wipe the session's files, cwd, and environment
await session.reset();

Each session starts in its own workspace at /work/sessions/<name>.

Python Scripts

MicroPython is available as both python and python3:

const result = await linux.execute(`python3 << 'EOF'
import json
data = {"status": "running", "version": "1.0"}
print(json.dumps(data))
EOF
`);
console.log(JSON.parse(result.stdout));

Note: this is MicroPython, not CPython — most of the core language and common modules (json, os, sys, …) work, but not the full standard library or pip packages.

Node.js & npm

The node profile boots a bigger rootfs (rootfs-node.cpio.gz) with real Node.js 22 and npm. Combine it with network: true and npm install talks to the real registry — the guest does its own DNS and TLS through a local Wisp relay the SDK starts on 127.0.0.1:

const linux = new Linux({
  profile: "node",   // Node.js 22 + npm image, 256 MiB RAM default
  network: true,     // real outbound TCP (Node.js 22+ host required)
});

const session = linux.session("app");
await session.execute("node --version");
await session.execute("npm install left-pad --no-audit --no-fund", {
  timeoutMs: 600_000,
});
const result = await session.execute(
  `node -e "console.log(require('left-pad')('42', 6, '0'))"`,
);
console.log(result.stdout); // "000042"

Expectations to set:

  • It is slow. v86 emulates a ~100 MHz-class CPU: node -e takes seconds, npm install takes minutes. The node profile raises the default command timeout to 120 s (max 900 s) and the boot timeout to 180 s.
  • Pure-JS packages only. There is no compiler in the guest and no prebuilt binaries exist for 32-bit musl, so packages with native addons won't build.
  • Networking is off by default — sandboxed code only reaches the internet when you opt in. In the browser, pass network: { relayUrl: "wisps://your-relay/" } pointing at a Wisp or WebSocket ethernet relay you host.

In the browser, also serve rootfs-node.cpio.gz alongside the other assets.

File Operations

The shell is BusyBox ash (POSIX sh — no bashisms like {1..5} brace expansion):

const session = linux.session('files');

// Write to the session workspace (its default cwd)
await session.execute('echo "config data" > config.txt');

// Read and process
const result = await session.execute('cat config.txt');
console.log(result.stdout);

// Create scripts on the fly
await session.execute(`cat > /tmp/process.sh << 'EOF'
#!/bin/sh
for i in $(seq 1 5); do
  echo "Iteration $i"
done
EOF
chmod +x /tmp/process.sh
/tmp/process.sh
`);

Real-World Examples

1. LLM Agent Backend

Use MiniNux to safely execute code suggestions from Claude or other LLMs:

import { Linux } from 'mininux';
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();
const linux = new Linux();

const userRequest = "Write a script that calculates fibonacci up to 10 numbers";

const message = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: userRequest }],
  system: "You are a helpful assistant. Write Python code when asked. Wrap code in ```python...``` blocks."
});

// Extract code from response
const code = message.content[0].text.match(/```python\n([\s\S]*?)\n```/)[1];

// Execute safely in MiniNux sandbox
const result = await linux.execute(`python3 << 'EOF'\n${code}\nEOF`);
console.log("Output:", result.stdout);
console.log("Errors:", result.stderr);

2. Educational CTF/Hacking Challenges

Create isolated environments for security challenges:

const sandbox = new Linux();

// Set up challenge environment
const session = sandbox.session('ctf-challenge-1');
await session.execute('echo "flag{hidden_data_here}" > flag.txt');
await session.execute('chmod 600 flag.txt');

// User attempts to read flag
const userAttempt = await session.execute('cat flag.txt');
if (userAttempt.exitCode !== 0) {
  console.log("Access denied! Try another approach...");
}

3. Backend API for Command Execution

Build a safe REST API that executes user-submitted commands. Timeouts are built in — pass timeoutMs per command:

import express from 'express';
import { Linux } from 'mininux';

const app = express();
const linuxInstances = new Map();

function getOrCreateLinux(sessionId) {
  if (!linuxInstances.has(sessionId)) {
    linuxInstances.set(sessionId, new Linux());
  }
  return linuxInstances.get(sessionId);
}

app.post('/execute', express.json(), async (req, res) => {
  const { sessionId, command, timeout = 30000 } = req.body;

  if (!command || command.trim().length === 0) {
    return res.status(400).json({ error: 'Command required' });
  }

  try {
    const linux = getOrCreateLinux(sessionId);
    const result = await linux.execute(command, {
      session: sessionId,
      timeoutMs: timeout,
    });

    res.json({
      stdout: result.stdout,
      stderr: result.stderr,
      exitCode: result.exitCode,
      timedOut: result.timedOut,
      durationMs: result.durationMs
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('API running on :3000'));

Usage:

curl -X POST http://localhost:3000/execute \
  -H 'Content-Type: application/json' \
  -d '{
    "sessionId": "user-123",
    "command": "ls -la /work",
    "timeout": 30000
  }'

Session names (like all identifiers passed to session() / execute({ session })) must match [A-Za-z0-9_-]+ — validate user input first.

4. Multi-Tenant Sandbox Service

Parallel execution with isolated VMs:

import { Linux } from 'mininux';

// Each user gets their own VM for true isolation
const userSandboxes = new Map();

async function executeForUser(userId, command) {
  if (!userSandboxes.has(userId)) {
    userSandboxes.set(userId, new Linux());
  }
  return userSandboxes.get(userId).session(userId).execute(command);
}

// Parallel execution, no crosstalk
await Promise.all([
  executeForUser('alice', 'echo "VM A"'),
  executeForUser('bob', 'echo "VM B"'),
  executeForUser('charlie', 'echo "VM C"')
]);

API Reference

new Linux(options?)

Main class for managing a v86 WASM Linux instance.

interface LinuxOptions {
  assets?: Partial<LinuxAssets>; // wasm, bios, vgaBios, kernel, initrd?
                                 // Default: files under /mini-linux/ (browser)
  profile?: "minimal" | "node";  // "node" = Node.js 22 + npm image. Default: "minimal"
  network?: boolean | { relayUrl?: string }; // Emulated NIC with outbound TCP. Default: off
  memoryMiB?: number;            // Guest RAM, rounded up to a power of two. Default: 32 (node: 256)
  bootTimeoutMs?: number;        // Time allowed for boot. Default: 15_000 (node: 180_000)
  commandTimeoutMs?: number;     // Default per-command timeout. Default: 10_000 (node: 120_000, max 900_000)
  onConsoleLine?: (line: string) => void; // Raw serial output, useful for boot debugging
  kernelCommandLine?: string;    // Extra kernel arguments
}

start(): Promise<void>

Boot the VM and wait until the guest agent is ready. Optional — execute() boots on first use.

execute(command: string, options?): Promise<ExecuteResult>

Run a shell command (BusyBox ash).

interface ExecuteOptions {
  session?: string;   // Named session to run in. Default: "default"
  timeoutMs?: number; // Per-command timeout override
}
const result = await linux.execute('whoami');
// { stdout: 'root\n', stderr: '', exitCode: 0, timedOut: false, durationMs: 145 }

session(name: string): LinuxSession

Get a handle to a named session with persistent state (synchronous).

resetSession(name: string): Promise<void>

Delete a session's files, working directory, and environment snapshot.

saveState(): Promise<ArrayBuffer> / restoreState(state): Promise<void>

Snapshot and restore the entire VM (RAM, all sessions). State files are v86-version-specific.

destroy(): Promise<void>

Shut down the VM and reject any pending commands. Always call when done.

LinuxSession

A persistent workspace with environment variables and working directory, rooted at /work/sessions/<name>.

execute(command: string, options?): Promise<ExecuteResult>

Run a command in this session's context.

reset(): Promise<void>

Wipe this session's files, cwd, and environment.

ExecuteResult

interface ExecuteResult {
  stdout: string;        // Command output
  stderr: string;        // Error output
  exitCode: number;      // Exit code (0 = success)
  timedOut: boolean;     // Whether command exceeded timeout
  durationMs: number;    // Execution time in milliseconds
}

Architecture

MiniNux runs a complete Linux stack in WASM:

┌─────────────────────────────────────┐
│   Your JavaScript/Node.js App       │
└────────────┬────────────────────────┘
             │
             ├─ Linux.execute()
             └─ LinuxSession.execute()
                      │
         ┌────────────▼────────────┐
         │  TypeScript SDK         │
         │ (serial frame protocol) │
         └────────────┬────────────┘
                      │
         ┌────────────▼──────────────────┐
         │  v86 x86 WASM Emulator        │
         │  (32-bit Linux kernel)        │
         └────────────┬──────────────────┘
                      │
         ┌────────────▼──────────────────┐
         │  BusyBox initramfs            │
         │  + MicroPython runtime        │
         │  + session RAM filesystems    │
         └───────────────────────────────┘

Key Points:

  • Each Linux instance = one v86 VM (isolated, 32 MiB)
  • Commands serialized through base64-encoded serial frames
  • Sessions live in /work/sessions/<name> with persistent state
  • All execution is sandboxed—no access to host filesystem
  • Commands are queued per instance and executed one at a time

Building From Source

# Prerequisites: Node.js 20+, Buildroot, make
npm install

# Build guest (Linux kernel + initramfs)
npm run build:guest

# Prepare v86 assets
npm run assets:v86

# Test everything
npm run test:v86

# Publish to NPM
npm publish

See CHUNKING.md for details on how large binary assets are split for GitHub.

Contributing

This is an experimental project, and contributions are welcome!

Areas for improvement:

  • Performance — Optimize WASM initialization and command latency
  • Features — Add syscall passthrough for real hardware access
  • Docs — More examples and use-cases
  • Testing — Security hardening and fuzzing
  • UX — Better error messages and debugging

To contribute:

  1. Fork the repo
  2. Create a feature branch
  3. Write tests
  4. Submit a PR

Limitations & Caveats

  • ⚠️ Not production-grade — Use for fun, learning, and experiments
  • ⚠️ WASM performance — ~100-500ms overhead per command (v86 emulation)
  • ⚠️ Memory-heavy — Each VM instance uses ~32 MiB
  • ⚠️ MicroPython, not CPython — no pip, partial standard library
  • ⚠️ BusyBox ash, not bash — POSIX sh syntax only
  • ⚠️ Limited syscalls — Some Linux features not available in emulation
  • ⚠️ Networking is opt-in — no internet access unless you pass network: true (Node 22+) or a relay URL
  • ⚠️ Node profile is heavy — bigger download, 256 MiB RAM per VM, slow startup, pure-JS npm packages only
  • ⚠️ Command size limit — Commands are capped at 64 KiB of UTF-8
  • ⚠️ Browser compatibility — Requires WebAssembly support (all modern browsers)

Inspiration

Built for Cogniva (an AI blog research platform) to safely execute LLM-generated code. Perfect for:

  • 🤖 AI agent backends
  • 🎮 Interactive coding tutorials
  • 🛡️ Security CTF challenges
  • 📚 Educational computing environments
  • 🔬 Isolated code experimentation

License

MIT — Feel free to use, modify, and distribute. Have fun!

Questions?

Open an issue on GitHub or reach out to the community.

Happy hacking! 🚀