@earendil-works/gondolin 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/gondolin",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Alpine Linux sandbox for running untrusted code with controlled filesystem and network access",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/src/index.js",
package/dist/bash.js DELETED
@@ -1,82 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vm_1 = require("./vm");
4
- const WS_URL = process.env.WS_URL;
5
- const TOKEN = process.env.ELWING_TOKEN ?? process.env.SANDBOX_WS_TOKEN;
6
- const MAX_CHUNK = 32 * 1024;
7
- let shuttingDown = false;
8
- let exitCode = 1;
9
- function buildEnv() {
10
- const env = [];
11
- if (process.env.TERM)
12
- env.push(`TERM=${process.env.TERM}`);
13
- return env;
14
- }
15
- function wireStdin(exec) {
16
- if (process.stdin.isTTY) {
17
- process.stdin.setRawMode(true);
18
- }
19
- process.stdin.resume();
20
- process.stdin.on("data", (chunk) => {
21
- if (shuttingDown)
22
- return;
23
- for (let offset = 0; offset < chunk.length; offset += MAX_CHUNK) {
24
- const slice = chunk.subarray(offset, offset + MAX_CHUNK);
25
- void exec.sendStdin(slice);
26
- }
27
- });
28
- process.stdin.on("end", () => {
29
- if (shuttingDown)
30
- return;
31
- void exec.endStdin();
32
- });
33
- }
34
- function cleanup() {
35
- if (process.stdin.isTTY) {
36
- process.stdin.setRawMode(false);
37
- }
38
- process.stdin.pause();
39
- }
40
- async function shutdown(vm) {
41
- if (shuttingDown)
42
- return;
43
- shuttingDown = true;
44
- cleanup();
45
- await vm.stop();
46
- process.exit(exitCode);
47
- }
48
- async function main() {
49
- const vm = new vm_1.VM({ url: WS_URL ?? undefined, token: TOKEN ?? undefined });
50
- try {
51
- const exec = await vm.execStream(["bash", "-i"], {
52
- env: buildEnv(),
53
- stdin: true,
54
- pty: true,
55
- buffer: false,
56
- });
57
- exec.stdout.on("data", (chunk) => {
58
- process.stdout.write(chunk);
59
- });
60
- exec.stderr.on("data", (chunk) => {
61
- process.stderr.write(chunk);
62
- });
63
- wireStdin(exec);
64
- const result = await exec.result;
65
- exitCode = result.exitCode ?? 1;
66
- if (result.signal !== undefined) {
67
- process.stderr.write(`process exited due to signal ${result.signal}\n`);
68
- }
69
- }
70
- catch (err) {
71
- const message = err instanceof Error ? err.message : String(err);
72
- process.stderr.write(`${message}\n`);
73
- exitCode = 1;
74
- }
75
- finally {
76
- await shutdown(vm);
77
- }
78
- }
79
- main().catch((err) => {
80
- console.error(err.message);
81
- process.exit(1);
82
- });
@@ -1,111 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runBadBash = runBadBash;
4
- const vm_1 = require("../src/vm");
5
- const WS_URL = process.env.WS_URL;
6
- const TOKEN = process.env.ELWING_TOKEN ?? process.env.SANDBOX_WS_TOKEN;
7
- const MAX_CHUNK = 32 * 1024;
8
- let shuttingDown = false;
9
- let exitCode = 1;
10
- function buildEnv() {
11
- const env = [];
12
- if (process.env.TERM)
13
- env.push(`TERM=${process.env.TERM}`);
14
- return env;
15
- }
16
- function redirectExampleDotCom(input) {
17
- const rawUrl = typeof input === "string" || input instanceof URL ? input.toString() : input.url;
18
- let parsed;
19
- try {
20
- parsed = new URL(rawUrl);
21
- }
22
- catch {
23
- return input;
24
- }
25
- if (parsed.hostname !== "example.com") {
26
- return input;
27
- }
28
- const replacement = new URL("https://icanhazip.com/");
29
- if (typeof input === "string" || input instanceof URL) {
30
- return replacement.toString();
31
- }
32
- return new Request(replacement.toString(), input);
33
- }
34
- const redirectingFetch = async (input, init) => {
35
- const redirectedInput = redirectExampleDotCom(input);
36
- return fetch(redirectedInput, init);
37
- };
38
- function wireStdin(exec) {
39
- if (process.stdin.isTTY) {
40
- process.stdin.setRawMode(true);
41
- }
42
- process.stdin.resume();
43
- process.stdin.on("data", (chunk) => {
44
- if (shuttingDown)
45
- return;
46
- for (let offset = 0; offset < chunk.length; offset += MAX_CHUNK) {
47
- const slice = chunk.subarray(offset, offset + MAX_CHUNK);
48
- void exec.sendStdin(slice);
49
- }
50
- });
51
- process.stdin.on("end", () => {
52
- if (shuttingDown)
53
- return;
54
- void exec.endStdin();
55
- });
56
- }
57
- function cleanup() {
58
- if (process.stdin.isTTY) {
59
- process.stdin.setRawMode(false);
60
- }
61
- process.stdin.pause();
62
- }
63
- async function shutdown(vm) {
64
- if (shuttingDown)
65
- return;
66
- shuttingDown = true;
67
- cleanup();
68
- await vm.stop();
69
- process.exit(exitCode);
70
- }
71
- async function runBadBash() {
72
- const vm = new vm_1.VM({
73
- url: WS_URL ?? undefined,
74
- token: TOKEN ?? undefined,
75
- fetch: redirectingFetch,
76
- });
77
- try {
78
- const exec = await vm.execStream(["bash", "-i"], {
79
- env: buildEnv(),
80
- stdin: true,
81
- pty: true,
82
- buffer: false,
83
- });
84
- exec.stdout.on("data", (chunk) => {
85
- process.stdout.write(chunk);
86
- });
87
- exec.stderr.on("data", (chunk) => {
88
- process.stderr.write(chunk);
89
- });
90
- wireStdin(exec);
91
- const result = await exec.result;
92
- exitCode = result.exitCode ?? 1;
93
- if (result.signal !== undefined) {
94
- process.stderr.write(`process exited due to signal ${result.signal}\n`);
95
- }
96
- }
97
- catch (err) {
98
- const message = err instanceof Error ? err.message : String(err);
99
- process.stderr.write(`${message}\n`);
100
- exitCode = 1;
101
- }
102
- finally {
103
- await shutdown(vm);
104
- }
105
- }
106
- if (require.main === module) {
107
- runBadBash().catch((err) => {
108
- console.error(err.message);
109
- process.exit(1);
110
- });
111
- }
package/dist/bin/bash.js DELETED
@@ -1,85 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runBash = runBash;
4
- const vm_1 = require("../src/vm");
5
- const WS_URL = process.env.WS_URL;
6
- const TOKEN = process.env.ELWING_TOKEN ?? process.env.SANDBOX_WS_TOKEN;
7
- const MAX_CHUNK = 32 * 1024;
8
- let shuttingDown = false;
9
- let exitCode = 1;
10
- function buildEnv() {
11
- const env = [];
12
- if (process.env.TERM)
13
- env.push(`TERM=${process.env.TERM}`);
14
- return env;
15
- }
16
- function wireStdin(exec) {
17
- if (process.stdin.isTTY) {
18
- process.stdin.setRawMode(true);
19
- }
20
- process.stdin.resume();
21
- process.stdin.on("data", (chunk) => {
22
- if (shuttingDown)
23
- return;
24
- for (let offset = 0; offset < chunk.length; offset += MAX_CHUNK) {
25
- const slice = chunk.subarray(offset, offset + MAX_CHUNK);
26
- void exec.sendStdin(slice);
27
- }
28
- });
29
- process.stdin.on("end", () => {
30
- if (shuttingDown)
31
- return;
32
- void exec.endStdin();
33
- });
34
- }
35
- function cleanup() {
36
- if (process.stdin.isTTY) {
37
- process.stdin.setRawMode(false);
38
- }
39
- process.stdin.pause();
40
- }
41
- async function shutdown(vm) {
42
- if (shuttingDown)
43
- return;
44
- shuttingDown = true;
45
- cleanup();
46
- await vm.stop();
47
- process.exit(exitCode);
48
- }
49
- async function runBash() {
50
- const vm = new vm_1.VM({ url: WS_URL ?? undefined, token: TOKEN ?? undefined });
51
- try {
52
- const exec = await vm.execStream(["bash", "-i"], {
53
- env: buildEnv(),
54
- stdin: true,
55
- pty: true,
56
- buffer: false,
57
- });
58
- exec.stdout.on("data", (chunk) => {
59
- process.stdout.write(chunk);
60
- });
61
- exec.stderr.on("data", (chunk) => {
62
- process.stderr.write(chunk);
63
- });
64
- wireStdin(exec);
65
- const result = await exec.result;
66
- exitCode = result.exitCode ?? 1;
67
- if (result.signal !== undefined) {
68
- process.stderr.write(`process exited due to signal ${result.signal}\n`);
69
- }
70
- }
71
- catch (err) {
72
- const message = err instanceof Error ? err.message : String(err);
73
- process.stderr.write(`${message}\n`);
74
- exitCode = 1;
75
- }
76
- finally {
77
- await shutdown(vm);
78
- }
79
- }
80
- if (require.main === module) {
81
- runBash().catch((err) => {
82
- console.error(err.message);
83
- process.exit(1);
84
- });
85
- }
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const bash_1 = require("./bash");
4
- const exec_1 = require("./exec");
5
- const ws_server_1 = require("./ws-server");
6
- function usage() {
7
- console.log("Usage: eregion <command> [options]");
8
- console.log("Commands:");
9
- console.log(" exec Run a command via the virtio socket");
10
- console.log(" ws-server Start the WebSocket bridge server");
11
- console.log(" bash Start an interactive bash session over WS");
12
- console.log(" help Show this help");
13
- console.log("\nRun eregion <command> --help for command-specific flags.");
14
- }
15
- async function main() {
16
- const [command, ...args] = process.argv.slice(2);
17
- if (!command || command === "help" || command === "--help" || command === "-h") {
18
- usage();
19
- process.exit(command ? 0 : 1);
20
- }
21
- switch (command) {
22
- case "exec":
23
- (0, exec_1.runExec)(args);
24
- return;
25
- case "ws-server":
26
- case "server":
27
- await (0, ws_server_1.runWsServer)(args);
28
- return;
29
- case "bash":
30
- await (0, bash_1.runBash)();
31
- return;
32
- default:
33
- console.error(`Unknown command: ${command}`);
34
- usage();
35
- process.exit(1);
36
- }
37
- }
38
- main().catch((err) => {
39
- console.error(err.message);
40
- process.exit(1);
41
- });
package/dist/bin/exec.js DELETED
@@ -1,212 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.runExec = runExec;
7
- const net_1 = __importDefault(require("net"));
8
- const virtio_protocol_1 = require("../src/virtio-protocol");
9
- function parseArgs(argv) {
10
- const args = { commands: [] };
11
- let current = null;
12
- let nextId = 1;
13
- const fail = (message) => {
14
- console.error(message);
15
- usage();
16
- process.exit(1);
17
- };
18
- const parseId = (value) => {
19
- const id = Number(value);
20
- if (!Number.isFinite(id))
21
- fail("--id must be a number");
22
- if (id >= nextId)
23
- nextId = id + 1;
24
- return id;
25
- };
26
- const separatorIndex = argv.indexOf("--");
27
- if (separatorIndex !== -1) {
28
- const optionArgs = argv.slice(0, separatorIndex);
29
- const commandArgs = argv.slice(separatorIndex + 1);
30
- if (commandArgs.length === 0)
31
- fail("missing command after --");
32
- current = {
33
- cmd: commandArgs[0],
34
- argv: commandArgs.slice(1),
35
- env: [],
36
- id: nextId++,
37
- };
38
- args.commands.push(current);
39
- for (let i = 0; i < optionArgs.length; i += 1) {
40
- const arg = optionArgs[i];
41
- switch (arg) {
42
- case "--sock":
43
- args.sock = optionArgs[++i];
44
- break;
45
- case "--env":
46
- current.env.push(optionArgs[++i]);
47
- break;
48
- case "--cwd":
49
- current.cwd = optionArgs[++i];
50
- break;
51
- case "--id":
52
- current.id = parseId(optionArgs[++i]);
53
- break;
54
- case "--help":
55
- case "-h":
56
- usage();
57
- process.exit(0);
58
- default:
59
- fail(`Unknown argument: ${arg}`);
60
- }
61
- }
62
- return args;
63
- }
64
- const requireCurrent = (flag) => {
65
- if (!current)
66
- fail(`${flag} requires --cmd`);
67
- return current;
68
- };
69
- for (let i = 0; i < argv.length; i += 1) {
70
- const arg = argv[i];
71
- switch (arg) {
72
- case "--sock":
73
- args.sock = argv[++i];
74
- break;
75
- case "--cmd":
76
- current = { cmd: argv[++i], argv: [], env: [], id: nextId++ };
77
- args.commands.push(current);
78
- break;
79
- case "--arg": {
80
- const command = requireCurrent("--arg");
81
- command.argv.push(argv[++i]);
82
- break;
83
- }
84
- case "--env": {
85
- const command = requireCurrent("--env");
86
- command.env.push(argv[++i]);
87
- break;
88
- }
89
- case "--cwd": {
90
- const command = requireCurrent("--cwd");
91
- command.cwd = argv[++i];
92
- break;
93
- }
94
- case "--id": {
95
- const command = requireCurrent("--id");
96
- command.id = parseId(argv[++i]);
97
- break;
98
- }
99
- case "--help":
100
- case "-h":
101
- usage();
102
- process.exit(0);
103
- default:
104
- fail(`Unknown argument: ${arg}`);
105
- }
106
- }
107
- return args;
108
- }
109
- function usage() {
110
- console.log("Usage:");
111
- console.log(" gondolin exec --sock PATH -- CMD [ARGS...]");
112
- console.log(" gondolin exec --sock PATH --cmd CMD [--arg ARG] [--env KEY=VALUE] [--cwd PATH] [--cmd CMD ...]");
113
- console.log("Use -- to pass a command and its arguments directly.");
114
- console.log("Arguments apply to the most recent --cmd.");
115
- }
116
- function buildCommandPayload(command) {
117
- const payload = {
118
- cmd: command.cmd,
119
- };
120
- if (command.argv.length > 0)
121
- payload.argv = command.argv;
122
- if (command.env.length > 0)
123
- payload.env = command.env;
124
- if (command.cwd)
125
- payload.cwd = command.cwd;
126
- return payload;
127
- }
128
- function runExec(argv = process.argv.slice(2)) {
129
- const args = parseArgs(argv);
130
- if (!args.sock || args.commands.length === 0) {
131
- usage();
132
- process.exit(1);
133
- }
134
- const socket = net_1.default.createConnection({ path: args.sock });
135
- const reader = new virtio_protocol_1.FrameReader();
136
- let currentIndex = 0;
137
- let inflightId = null;
138
- let exitCode = 0;
139
- let closing = false;
140
- const sendNext = () => {
141
- const command = args.commands[currentIndex];
142
- inflightId = command.id;
143
- const payload = buildCommandPayload(command);
144
- const message = (0, virtio_protocol_1.buildExecRequest)(command.id, payload);
145
- socket.write((0, virtio_protocol_1.encodeFrame)(message));
146
- };
147
- const finish = (code) => {
148
- if (code !== undefined && exitCode === 0)
149
- exitCode = code;
150
- if (closing)
151
- return;
152
- closing = true;
153
- socket.end();
154
- };
155
- socket.on("connect", () => {
156
- console.log(`connected to ${args.sock}`);
157
- sendNext();
158
- });
159
- socket.on("data", (chunk) => {
160
- reader.push(chunk, (frame) => {
161
- const message = (0, virtio_protocol_1.decodeMessage)(frame);
162
- if (message.t === "exec_output") {
163
- const data = message.p.data;
164
- if (message.p.stream === "stdout") {
165
- process.stdout.write(data);
166
- }
167
- else {
168
- process.stderr.write(data);
169
- }
170
- }
171
- else if (message.t === "exec_response") {
172
- if (inflightId !== null && message.id !== inflightId) {
173
- console.error(`unexpected response id ${message.id} (expected ${inflightId})`);
174
- finish(1);
175
- return;
176
- }
177
- const code = message.p.exit_code ?? 1;
178
- const signal = message.p.signal;
179
- if (signal !== undefined) {
180
- console.error(`process exited due to signal ${signal}`);
181
- }
182
- if (code !== 0 && exitCode === 0)
183
- exitCode = code;
184
- currentIndex += 1;
185
- if (currentIndex < args.commands.length) {
186
- sendNext();
187
- }
188
- else {
189
- finish();
190
- }
191
- }
192
- else if (message.t === "error") {
193
- console.error(`error ${message.p.code}: ${message.p.message}`);
194
- finish(1);
195
- }
196
- });
197
- });
198
- socket.on("error", (err) => {
199
- console.error(`socket error: ${err.message}`);
200
- finish(1);
201
- });
202
- socket.on("end", () => {
203
- if (!closing && exitCode === 0)
204
- exitCode = 1;
205
- });
206
- socket.on("close", () => {
207
- process.exit(exitCode);
208
- });
209
- }
210
- if (require.main === module) {
211
- runExec();
212
- }
@@ -1,147 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runWsServer = runWsServer;
4
- const sandbox_ws_server_1 = require("../src/sandbox-ws-server");
5
- function parseArgs(argv) {
6
- const args = {};
7
- const fail = (message) => {
8
- console.error(message);
9
- usage();
10
- process.exit(1);
11
- };
12
- for (let i = 0; i < argv.length; i += 1) {
13
- const arg = argv[i];
14
- if (arg === "--") {
15
- continue;
16
- }
17
- switch (arg) {
18
- case "--host":
19
- args.host = argv[++i] ?? args.host;
20
- break;
21
- case "--port":
22
- args.port = Number(argv[++i]);
23
- if (!Number.isFinite(args.port))
24
- fail("--port must be a number");
25
- break;
26
- case "--qemu":
27
- args.qemuPath = argv[++i];
28
- break;
29
- case "--kernel":
30
- args.kernelPath = argv[++i];
31
- break;
32
- case "--initrd":
33
- args.initrdPath = argv[++i];
34
- break;
35
- case "--memory":
36
- args.memory = argv[++i];
37
- break;
38
- case "--cpus":
39
- args.cpus = Number(argv[++i]);
40
- if (!Number.isFinite(args.cpus))
41
- fail("--cpus must be a number");
42
- break;
43
- case "--virtio-sock":
44
- args.virtioSocketPath = argv[++i];
45
- break;
46
- case "--net-sock":
47
- args.netSocketPath = argv[++i];
48
- break;
49
- case "--virtio-fs-sock":
50
- args.virtioFsSocketPath = argv[++i];
51
- break;
52
- case "--net-mac":
53
- args.netMac = argv[++i];
54
- break;
55
- case "--no-net":
56
- args.netEnabled = false;
57
- break;
58
- case "--net-debug":
59
- args.netDebug = true;
60
- break;
61
- case "--machine":
62
- args.machineType = argv[++i];
63
- break;
64
- case "--accel":
65
- args.accel = argv[++i];
66
- break;
67
- case "--cpu":
68
- args.cpu = argv[++i];
69
- break;
70
- case "--console":
71
- args.console = argv[++i] === "none" ? "none" : "stdio";
72
- break;
73
- case "--token":
74
- args.token = argv[++i];
75
- break;
76
- case "--no-restart":
77
- args.autoRestart = false;
78
- break;
79
- case "--help":
80
- case "-h":
81
- usage();
82
- process.exit(0);
83
- default:
84
- fail(`Unknown argument: ${arg}`);
85
- }
86
- }
87
- return args;
88
- }
89
- function usage() {
90
- const defaults = (0, sandbox_ws_server_1.resolveSandboxWsServerOptions)();
91
- console.log("Usage: gondolin ws-server [options]");
92
- console.log("Options:");
93
- console.log(` --host HOST Host to bind (default ${defaults.host})`);
94
- console.log(` --port PORT Port to bind (default ${defaults.port})`);
95
- console.log(` --qemu PATH QEMU binary (default ${defaults.qemuPath})`);
96
- console.log(" --kernel PATH Kernel path");
97
- console.log(" --initrd PATH Initrd path");
98
- console.log(` --memory SIZE Memory size (default ${defaults.memory})`);
99
- console.log(` --cpus N vCPU count (default ${defaults.cpus})`);
100
- console.log(" --virtio-sock PATH Virtio serial socket path");
101
- console.log(" --virtio-fs-sock PATH Virtio filesystem socket path");
102
- console.log(" --net-sock PATH QEMU net socket path");
103
- console.log(" --net-mac MAC MAC address for virtio-net");
104
- console.log(" --no-net Disable QEMU net backend");
105
- console.log(" --net-debug Enable net backend debug logging");
106
- console.log(" (or set GONDOLIN_DEBUG=net)");
107
- console.log(" --machine TYPE Override QEMU machine type");
108
- console.log(" --accel TYPE Override QEMU accel (kvm/hvf/tcg)");
109
- console.log(" --cpu TYPE Override QEMU CPU type");
110
- console.log(" --console stdio|none Console output");
111
- console.log(" --token TOKEN Require token in Authorization header");
112
- console.log(" --no-restart Disable auto restart on exit");
113
- }
114
- function formatLog(message) {
115
- if (message.endsWith("\n"))
116
- return message;
117
- return `${message}\n`;
118
- }
119
- async function runWsServer(argv = process.argv.slice(2)) {
120
- const args = parseArgs(argv);
121
- const server = new sandbox_ws_server_1.SandboxWsServer(args);
122
- server.on("log", (message) => {
123
- process.stdout.write(formatLog(message));
124
- });
125
- server.on("error", (err) => {
126
- const message = err instanceof Error ? err.message : String(err);
127
- process.stdout.write(formatLog(message));
128
- });
129
- const address = await server.start();
130
- console.log(`WebSocket server listening on ${address.url}`);
131
- const shutdown = async () => {
132
- await server.stop();
133
- process.exit(0);
134
- };
135
- process.on("SIGINT", () => {
136
- void shutdown();
137
- });
138
- process.on("SIGTERM", () => {
139
- void shutdown();
140
- });
141
- }
142
- if (require.main === module) {
143
- runWsServer().catch((err) => {
144
- console.error(err.message);
145
- process.exit(1);
146
- });
147
- }