@earendil-works/gondolin
v0.7.0
Published
Alpine Linux sandbox for running untrusted code with controlled filesystem and network access
Downloads
4,302
Readme
Gondolin
Local Linux micro-VMs with a fully programmable network stack and filesystem.
Gondolin runs lightweight micro-VMs on your Mac or Linux machine (QEMU by
default, optional krun backend). The network stack and virtual filesystem are
implemented in TypeScript, giving you complete programmatic control over what
the sandbox can access and what secrets it can use.
Requirements
You need QEMU installed to run the micro-VMs (default backend):
| macOS | Linux (Debian/Ubuntu) |
| ------------------- | ---------------------------------- |
| brew install qemu | sudo apt install qemu-system-arm |
Optional experimental backend:
libkrun+host/krun-runner(sandbox.vmm = "krun")make krun-runnerfrom repo root stages dependencies locally and builds the runner- on macOS, it also ad-hoc signs the runner with
com.apple.security.hypervisor - Gondolin auto-detects this local runner for
--vmm krun
- on macOS, it also ad-hoc signs the runner with
@earendil-works/gondolinpublishes platform-specific optional runner packages for supported targets (darwin-arm64,linux-x64)- krun boot assets are provided by image manifests (
assets.krunKernel/assets.krunInitrd) produced bygondolin buildand published image releases gondolin bash --vmm krunselects the backend per-commandGONDOLIN_VMM=krunstill works as a global default- backend parity matrix: docs/backends.md
Linux prerequisites for make krun-runner (Ubuntu/Debian):
sudo apt install \
build-essential curl git make pkg-config clang lld xz-utils \
libclang-dev llvm-dev libcap-ng-dev
# libkrun needs current stable Rust (edition2024 crates)
curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal
. "$HOME/.cargo/env"
# install Zig 0.15.2 for your Linux architecture- Node.js >= 23.6
Note: Runtime validation is currently strongest on ARM64 (Apple Silicon, Linux aarch64). Linux x86_64 is currently smoke-tested for
make krun-runnerin CI.
Installation
npm install @earendil-works/gondolinQuick start (CLI)
npx @earendil-works/gondolin bashYou can also discover and re-attach to running VMs from another terminal:
# List running sessions
npx @earendil-works/gondolin list
# Attach a second shell to a running VM by UUID/prefix
npx @earendil-works/gondolin attach <session-id>
# Snapshot a running VM session (stops that session)
npx @earendil-works/gondolin snapshot <session-id>
# Resume from snapshot id/path
npx @earendil-works/gondolin bash --resume <snapshot-id-or-path>Guest images (~200MB) are automatically resolved on first run via
builtin-image-registry.json and cached in ~/.cache/gondolin/images/.
If no explicit image is provided, Gondolin uses GONDOLIN_DEFAULT_IMAGE
(default: alpine-base:latest).
Hello world
import { VM, createHttpHooks, MemoryProvider } from "@earendil-works/gondolin";
const { httpHooks, env } = createHttpHooks({
allowedHosts: ["api.github.com"],
secrets: {
GITHUB_TOKEN: {
hosts: ["api.github.com"],
value: process.env.GITHUB_TOKEN!,
},
},
});
const vm = await VM.create({
httpHooks,
env,
vfs: {
mounts: { "/workspace": new MemoryProvider() },
},
});
// NOTE:
// - `vm.exec("...")` runs via `/bin/sh -lc "..."` (shell features work)
// - `vm.exec([cmd, ...argv])` executes `cmd` directly and does not search `$PATH`
// so `cmd` must be an absolute path
const cmd = `
curl -sS -f \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
https://api.github.com/user
`;
// You can pass a string to `vm.exec(...)` as shorthand for `/bin/sh -lc "..."`.
const result = await vm.exec(cmd);
console.log("exitCode:", result.exitCode);
console.log("stdout:\n", result.stdout);
console.log("stderr:\n", result.stderr);
await vm.close();The guest never sees the real secret values. It only gets placeholders.
Placeholders are substituted by the host in outbound HTTP headers, including
Authorization: Basic … (the base64 token is decoded and placeholders in
username:password are replaced).
By default, placeholders in URL query parameters are not substituted. You can
opt in with replaceSecretsInQuery: true, but this increases reflection
risk and should only be used when required.
Note: Avoid mounting a
MemoryProviderat/unless you also provide a system CA bundle. Gondolin injects its MITM CA at/etc/gondolin/mitm/ca.crt, but if your root mount hides distro CA files then public TLS verification can still fail (e.g.curl: (60)).
Outbound SSH host allowlist
SSH egress can be enabled with an explicit host allowlist:
const vm = await VM.create({
dns: {
mode: "synthetic",
syntheticHostMapping: "per-host",
},
ssh: {
allowedHosts: ["github.com"],
// Upstream host key verification
//
// If `hostVerifier` is not provided, gondolin verifies upstream host keys using
// OpenSSH known_hosts (by default: ~/.ssh/known_hosts and /etc/ssh/ssh_known_hosts).
// You can override this with `knownHostsFile`.
// knownHostsFile: "/path/to/known_hosts",
// Option A: use an ssh-agent (recommended for encrypted keys)
agent: process.env.SSH_AUTH_SOCK,
// Option B: provide a raw private key
credentials: {
"github.com": {
username: "git",
privateKey: process.env.GITHUB_DEPLOY_KEY!,
},
},
},
});syntheticHostMapping: "per-host" is required so the host can map outbound
TCP connections on port 22 back to the intended hostname.
When credentials or an SSH agent are configured, the host terminates the guest SSH
session and proxies exec requests (including Git smart-protocol commands) to the
real upstream host using host-side authentication. The private key is never
visible in the guest.
If no matching credential is configured for a host and no ssh.agent is set, the
SSH flow is blocked (direct passthrough is not supported).
Because this is SSH termination, the guest sees a host-provided SSH host key;
configure guest known_hosts (or disable strict checking explicitly) as needed.
Upstream SSH host keys are verified against OpenSSH known_hosts by default.
If the upstream host is missing (or the key changes), the proxied SSH request
will fail.
License and Links
- Documentation
- Issue Tracker
- License: Apache-2.0
