isol8 0.10.2 → 0.11.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/README.md +16 -0
- package/dist/cli.js +477 -50
- package/dist/index.js +268 -19
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/engine/code-fetcher.d.ts +21 -0
- package/dist/src/engine/code-fetcher.d.ts.map +1 -0
- package/dist/src/engine/docker.d.ts +2 -0
- package/dist/src/engine/docker.d.ts.map +1 -1
- package/dist/src/engine/image-builder.d.ts +12 -2
- package/dist/src/engine/image-builder.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +44 -2
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/schema/isol8.config.schema.json +59 -0
package/dist/cli.js
CHANGED
|
@@ -54803,6 +54803,13 @@ function mergeConfig(defaults, overrides) {
|
|
|
54803
54803
|
seccomp: overrides.security?.seccomp ?? defaults.security.seccomp,
|
|
54804
54804
|
customProfilePath: overrides.security?.customProfilePath ?? defaults.security.customProfilePath
|
|
54805
54805
|
},
|
|
54806
|
+
remoteCode: {
|
|
54807
|
+
...defaults.remoteCode,
|
|
54808
|
+
...overrides.remoteCode,
|
|
54809
|
+
allowedSchemes: overrides.remoteCode?.allowedSchemes ?? defaults.remoteCode.allowedSchemes,
|
|
54810
|
+
allowedHosts: overrides.remoteCode?.allowedHosts ?? defaults.remoteCode.allowedHosts,
|
|
54811
|
+
blockedHosts: overrides.remoteCode?.blockedHosts ?? defaults.remoteCode.blockedHosts
|
|
54812
|
+
},
|
|
54806
54813
|
audit: {
|
|
54807
54814
|
...defaults.audit,
|
|
54808
54815
|
...overrides.audit
|
|
@@ -54834,6 +54841,28 @@ var init_config = __esm(() => {
|
|
|
54834
54841
|
security: {
|
|
54835
54842
|
seccomp: "strict"
|
|
54836
54843
|
},
|
|
54844
|
+
remoteCode: {
|
|
54845
|
+
enabled: false,
|
|
54846
|
+
allowedSchemes: ["https"],
|
|
54847
|
+
allowedHosts: [],
|
|
54848
|
+
blockedHosts: [
|
|
54849
|
+
"^localhost$",
|
|
54850
|
+
"^127(?:\\.[0-9]{1,3}){3}$",
|
|
54851
|
+
"^\\[::1\\]$",
|
|
54852
|
+
"^::1$",
|
|
54853
|
+
"^10(?:\\.[0-9]{1,3}){3}$",
|
|
54854
|
+
"^172\\.(?:1[6-9]|2[0-9]|3[0-1])(?:\\.[0-9]{1,3}){2}$",
|
|
54855
|
+
"^192\\.168(?:\\.[0-9]{1,3}){2}$",
|
|
54856
|
+
"^169\\.254(?:\\.[0-9]{1,3}){2}$",
|
|
54857
|
+
"^metadata\\.google\\.internal$",
|
|
54858
|
+
"^169\\.254\\.169\\.254$"
|
|
54859
|
+
],
|
|
54860
|
+
maxCodeSize: 10 * 1024 * 1024,
|
|
54861
|
+
fetchTimeoutMs: 30000,
|
|
54862
|
+
requireHash: false,
|
|
54863
|
+
enableCache: true,
|
|
54864
|
+
cacheTtl: 3600
|
|
54865
|
+
},
|
|
54837
54866
|
audit: {
|
|
54838
54867
|
enabled: false,
|
|
54839
54868
|
destination: "filesystem",
|
|
@@ -55174,6 +55203,180 @@ var init_audit = __esm(() => {
|
|
|
55174
55203
|
init_logger();
|
|
55175
55204
|
});
|
|
55176
55205
|
|
|
55206
|
+
// src/engine/code-fetcher.ts
|
|
55207
|
+
import { createHash } from "node:crypto";
|
|
55208
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
55209
|
+
import { isIP } from "node:net";
|
|
55210
|
+
function sha256Hex(input) {
|
|
55211
|
+
return createHash("sha256").update(input, "utf-8").digest("hex");
|
|
55212
|
+
}
|
|
55213
|
+
function normalizeScheme(url) {
|
|
55214
|
+
return url.protocol.replace(/:$/, "").toLowerCase();
|
|
55215
|
+
}
|
|
55216
|
+
function isBlockedByPattern(host, patterns) {
|
|
55217
|
+
return patterns.some((pattern) => new RegExp(pattern, "i").test(host));
|
|
55218
|
+
}
|
|
55219
|
+
function isAllowedByPattern(host, patterns) {
|
|
55220
|
+
if (patterns.length === 0) {
|
|
55221
|
+
return true;
|
|
55222
|
+
}
|
|
55223
|
+
return patterns.some((pattern) => new RegExp(pattern, "i").test(host));
|
|
55224
|
+
}
|
|
55225
|
+
function isPrivateIpv4(ip) {
|
|
55226
|
+
const parts = ip.split(IPV4_SEPARATOR).map((v) => Number.parseInt(v, 10));
|
|
55227
|
+
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p))) {
|
|
55228
|
+
return false;
|
|
55229
|
+
}
|
|
55230
|
+
const a = parts[0];
|
|
55231
|
+
const b = parts[1];
|
|
55232
|
+
if (a === 10 || a === 127 || a === 0) {
|
|
55233
|
+
return true;
|
|
55234
|
+
}
|
|
55235
|
+
if (a === 169 && b === 254) {
|
|
55236
|
+
return true;
|
|
55237
|
+
}
|
|
55238
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
55239
|
+
return true;
|
|
55240
|
+
}
|
|
55241
|
+
if (a === 192 && b === 168) {
|
|
55242
|
+
return true;
|
|
55243
|
+
}
|
|
55244
|
+
if (a === 100 && b >= 64 && b <= 127) {
|
|
55245
|
+
return true;
|
|
55246
|
+
}
|
|
55247
|
+
return false;
|
|
55248
|
+
}
|
|
55249
|
+
function isPrivateIpv6(ip) {
|
|
55250
|
+
const normalized = ip.toLowerCase();
|
|
55251
|
+
if (normalized === IPV6_LOOPBACK) {
|
|
55252
|
+
return true;
|
|
55253
|
+
}
|
|
55254
|
+
return normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
|
|
55255
|
+
}
|
|
55256
|
+
function isPrivateIp(ip) {
|
|
55257
|
+
const family = isIP(ip);
|
|
55258
|
+
if (family === 4) {
|
|
55259
|
+
return isPrivateIpv4(ip);
|
|
55260
|
+
}
|
|
55261
|
+
if (family === 6) {
|
|
55262
|
+
return isPrivateIpv6(ip);
|
|
55263
|
+
}
|
|
55264
|
+
return false;
|
|
55265
|
+
}
|
|
55266
|
+
async function assertHostResolvesPublic(host, lookupFn) {
|
|
55267
|
+
if (isIP(host) && isPrivateIp(host)) {
|
|
55268
|
+
throw new Error(`Blocked code URL host: ${host}`);
|
|
55269
|
+
}
|
|
55270
|
+
try {
|
|
55271
|
+
const records = await lookupFn(host);
|
|
55272
|
+
for (const record of records) {
|
|
55273
|
+
if (isPrivateIp(record.address)) {
|
|
55274
|
+
throw new Error(`Blocked code URL host: ${host}`);
|
|
55275
|
+
}
|
|
55276
|
+
}
|
|
55277
|
+
} catch (err) {
|
|
55278
|
+
if (err instanceof Error && err.message.startsWith("Blocked code URL host:")) {
|
|
55279
|
+
throw err;
|
|
55280
|
+
}
|
|
55281
|
+
throw new Error(`Failed to resolve code URL host: ${host}`);
|
|
55282
|
+
}
|
|
55283
|
+
}
|
|
55284
|
+
function decodeUtf8(content) {
|
|
55285
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
55286
|
+
const text = decoder.decode(content);
|
|
55287
|
+
if (text.includes("\x00")) {
|
|
55288
|
+
throw new Error("Fetched code appears to be binary content");
|
|
55289
|
+
}
|
|
55290
|
+
return text;
|
|
55291
|
+
}
|
|
55292
|
+
async function fetchRemoteCode(request, policy, deps = {}) {
|
|
55293
|
+
if (!policy.enabled) {
|
|
55294
|
+
throw new Error("Remote code fetching is disabled. Set remoteCode.enabled=true to allow it.");
|
|
55295
|
+
}
|
|
55296
|
+
const fetchFn = deps.fetchFn ?? globalThis.fetch;
|
|
55297
|
+
const lookupFn = deps.lookupFn ?? (async (hostname) => {
|
|
55298
|
+
const records = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
55299
|
+
return records;
|
|
55300
|
+
});
|
|
55301
|
+
if (!request.codeUrl) {
|
|
55302
|
+
throw new Error("codeUrl is required for remote code fetching");
|
|
55303
|
+
}
|
|
55304
|
+
const url = new URL(request.codeUrl);
|
|
55305
|
+
const scheme = normalizeScheme(url);
|
|
55306
|
+
if (scheme === "http" && !request.allowInsecureCodeUrl) {
|
|
55307
|
+
throw new Error("Insecure code URL blocked. Use allowInsecureCodeUrl=true to allow HTTP.");
|
|
55308
|
+
}
|
|
55309
|
+
if (!policy.allowedSchemes.map((s) => s.toLowerCase()).includes(scheme)) {
|
|
55310
|
+
throw new Error(`URL scheme not allowed: ${scheme}`);
|
|
55311
|
+
}
|
|
55312
|
+
const host = url.hostname.toLowerCase();
|
|
55313
|
+
if (!isAllowedByPattern(host, policy.allowedHosts) || isBlockedByPattern(host, policy.blockedHosts)) {
|
|
55314
|
+
throw new Error(`Blocked code URL host: ${host}`);
|
|
55315
|
+
}
|
|
55316
|
+
await assertHostResolvesPublic(host, lookupFn);
|
|
55317
|
+
if (policy.requireHash && !request.codeHash) {
|
|
55318
|
+
throw new Error("Hash verification required: provide codeHash for remote code execution.");
|
|
55319
|
+
}
|
|
55320
|
+
const controller = new AbortController;
|
|
55321
|
+
const timeout = setTimeout(() => controller.abort(), policy.fetchTimeoutMs);
|
|
55322
|
+
let response;
|
|
55323
|
+
try {
|
|
55324
|
+
response = await fetchFn(url.toString(), {
|
|
55325
|
+
method: "GET",
|
|
55326
|
+
redirect: "follow",
|
|
55327
|
+
signal: controller.signal
|
|
55328
|
+
});
|
|
55329
|
+
} catch (err) {
|
|
55330
|
+
throw new Error(err instanceof Error && err.name === "AbortError" ? `Remote code fetch timed out after ${policy.fetchTimeoutMs}ms` : `Failed to fetch remote code: ${err instanceof Error ? err.message : String(err)}`);
|
|
55331
|
+
} finally {
|
|
55332
|
+
clearTimeout(timeout);
|
|
55333
|
+
}
|
|
55334
|
+
if (!response.ok) {
|
|
55335
|
+
throw new Error(`Failed to fetch remote code: HTTP ${response.status}`);
|
|
55336
|
+
}
|
|
55337
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
55338
|
+
if (contentLengthHeader) {
|
|
55339
|
+
const parsedLength = Number.parseInt(contentLengthHeader, 10);
|
|
55340
|
+
if (!Number.isNaN(parsedLength) && parsedLength > policy.maxCodeSize) {
|
|
55341
|
+
throw new Error(`Remote code exceeds maxCodeSize (${policy.maxCodeSize} bytes): ${parsedLength} bytes`);
|
|
55342
|
+
}
|
|
55343
|
+
}
|
|
55344
|
+
if (!response.body) {
|
|
55345
|
+
throw new Error("Remote code response body is empty");
|
|
55346
|
+
}
|
|
55347
|
+
const reader = response.body.getReader();
|
|
55348
|
+
const chunks = [];
|
|
55349
|
+
let totalBytes = 0;
|
|
55350
|
+
while (true) {
|
|
55351
|
+
const { done, value } = await reader.read();
|
|
55352
|
+
if (done) {
|
|
55353
|
+
break;
|
|
55354
|
+
}
|
|
55355
|
+
if (!value) {
|
|
55356
|
+
continue;
|
|
55357
|
+
}
|
|
55358
|
+
totalBytes += value.byteLength;
|
|
55359
|
+
if (totalBytes > policy.maxCodeSize) {
|
|
55360
|
+
throw new Error(`Remote code exceeds maxCodeSize (${policy.maxCodeSize} bytes)`);
|
|
55361
|
+
}
|
|
55362
|
+
chunks.push(value);
|
|
55363
|
+
}
|
|
55364
|
+
const buffer = new Uint8Array(totalBytes);
|
|
55365
|
+
let offset = 0;
|
|
55366
|
+
for (const chunk of chunks) {
|
|
55367
|
+
buffer.set(chunk, offset);
|
|
55368
|
+
offset += chunk.byteLength;
|
|
55369
|
+
}
|
|
55370
|
+
const code = decodeUtf8(buffer);
|
|
55371
|
+
const hash = sha256Hex(code);
|
|
55372
|
+
if (request.codeHash && hash.toLowerCase() !== request.codeHash.toLowerCase()) {
|
|
55373
|
+
throw new Error("Remote code hash mismatch");
|
|
55374
|
+
}
|
|
55375
|
+
return { code, url: url.toString(), hash };
|
|
55376
|
+
}
|
|
55377
|
+
var IPV4_SEPARATOR = ".", IPV6_LOOPBACK = "::1";
|
|
55378
|
+
var init_code_fetcher = () => {};
|
|
55379
|
+
|
|
55177
55380
|
// src/engine/concurrency.ts
|
|
55178
55381
|
class Semaphore {
|
|
55179
55382
|
max;
|
|
@@ -55819,10 +56022,30 @@ class DockerIsol8 {
|
|
|
55819
56022
|
poolStrategy;
|
|
55820
56023
|
poolSize;
|
|
55821
56024
|
auditLogger;
|
|
56025
|
+
remoteCodePolicy;
|
|
55822
56026
|
container = null;
|
|
55823
56027
|
persistentRuntime = null;
|
|
55824
56028
|
pool = null;
|
|
55825
56029
|
imageCache = new Map;
|
|
56030
|
+
async resolveExecutionRequest(req) {
|
|
56031
|
+
const inlineCode = req.code?.trim();
|
|
56032
|
+
const codeUrl = req.codeUrl?.trim();
|
|
56033
|
+
if (inlineCode && codeUrl) {
|
|
56034
|
+
throw new Error("ExecutionRequest.code and ExecutionRequest.codeUrl are mutually exclusive.");
|
|
56035
|
+
}
|
|
56036
|
+
if (!(inlineCode || codeUrl)) {
|
|
56037
|
+
throw new Error("ExecutionRequest must include either code or codeUrl.");
|
|
56038
|
+
}
|
|
56039
|
+
if (inlineCode) {
|
|
56040
|
+
return { ...req, code: req.code };
|
|
56041
|
+
}
|
|
56042
|
+
const fetched = await fetchRemoteCode({
|
|
56043
|
+
codeUrl,
|
|
56044
|
+
codeHash: req.codeHash,
|
|
56045
|
+
allowInsecureCodeUrl: req.allowInsecureCodeUrl
|
|
56046
|
+
}, this.remoteCodePolicy);
|
|
56047
|
+
return { ...req, code: fetched.code };
|
|
56048
|
+
}
|
|
55826
56049
|
constructor(options = {}, maxConcurrent = 10) {
|
|
55827
56050
|
this.docker = options.docker ?? new import_dockerode.default;
|
|
55828
56051
|
this.mode = options.mode ?? "ephemeral";
|
|
@@ -55844,6 +56067,17 @@ class DockerIsol8 {
|
|
|
55844
56067
|
this.logNetwork = options.logNetwork ?? false;
|
|
55845
56068
|
this.poolStrategy = options.poolStrategy ?? "fast";
|
|
55846
56069
|
this.poolSize = options.poolSize ?? { clean: 1, dirty: 1 };
|
|
56070
|
+
this.remoteCodePolicy = options.remoteCode ?? {
|
|
56071
|
+
enabled: false,
|
|
56072
|
+
allowedSchemes: ["https"],
|
|
56073
|
+
allowedHosts: [],
|
|
56074
|
+
blockedHosts: [],
|
|
56075
|
+
maxCodeSize: 10 * 1024 * 1024,
|
|
56076
|
+
fetchTimeoutMs: 30000,
|
|
56077
|
+
requireHash: false,
|
|
56078
|
+
enableCache: true,
|
|
56079
|
+
cacheTtl: 3600
|
|
56080
|
+
};
|
|
55847
56081
|
if (options.audit) {
|
|
55848
56082
|
this.auditLogger = new AuditLogger(options.audit);
|
|
55849
56083
|
}
|
|
@@ -55872,7 +56106,8 @@ class DockerIsol8 {
|
|
|
55872
56106
|
await this.semaphore.acquire();
|
|
55873
56107
|
const startTime = Date.now();
|
|
55874
56108
|
try {
|
|
55875
|
-
const
|
|
56109
|
+
const request = await this.resolveExecutionRequest(req);
|
|
56110
|
+
const result = this.mode === "persistent" ? await this.executePersistent(request, startTime) : await this.executeEphemeral(request, startTime);
|
|
55876
56111
|
return result;
|
|
55877
56112
|
} finally {
|
|
55878
56113
|
this.semaphore.release();
|
|
@@ -56026,8 +56261,9 @@ class DockerIsol8 {
|
|
|
56026
56261
|
async* executeStream(req) {
|
|
56027
56262
|
await this.semaphore.acquire();
|
|
56028
56263
|
try {
|
|
56029
|
-
const
|
|
56030
|
-
const
|
|
56264
|
+
const request = await this.resolveExecutionRequest(req);
|
|
56265
|
+
const adapter = this.getAdapter(request.runtime);
|
|
56266
|
+
const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
|
|
56031
56267
|
const image = await this.resolveImage(adapter);
|
|
56032
56268
|
const container = await this.docker.createContainer({
|
|
56033
56269
|
Image: image,
|
|
@@ -56044,23 +56280,23 @@ class DockerIsol8 {
|
|
|
56044
56280
|
await startProxy(container, this.networkFilter);
|
|
56045
56281
|
await setupIptables(container);
|
|
56046
56282
|
}
|
|
56047
|
-
const ext =
|
|
56283
|
+
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
56048
56284
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
56049
|
-
await writeFileViaExec(container, filePath,
|
|
56050
|
-
if (
|
|
56051
|
-
await installPackages(container,
|
|
56285
|
+
await writeFileViaExec(container, filePath, request.code);
|
|
56286
|
+
if (request.installPackages?.length) {
|
|
56287
|
+
await installPackages(container, request.runtime, request.installPackages);
|
|
56052
56288
|
}
|
|
56053
|
-
if (
|
|
56054
|
-
for (const [fPath, fContent] of Object.entries(
|
|
56289
|
+
if (request.files) {
|
|
56290
|
+
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
56055
56291
|
await writeFileViaExec(container, fPath, fContent);
|
|
56056
56292
|
}
|
|
56057
56293
|
}
|
|
56058
|
-
const rawCmd = adapter.getCommand(
|
|
56294
|
+
const rawCmd = adapter.getCommand(request.code, filePath);
|
|
56059
56295
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
56060
56296
|
let cmd;
|
|
56061
|
-
if (
|
|
56297
|
+
if (request.stdin) {
|
|
56062
56298
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
56063
|
-
await writeFileViaExec(container, stdinPath,
|
|
56299
|
+
await writeFileViaExec(container, stdinPath, request.stdin);
|
|
56064
56300
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
56065
56301
|
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
56066
56302
|
} else {
|
|
@@ -56068,7 +56304,7 @@ class DockerIsol8 {
|
|
|
56068
56304
|
}
|
|
56069
56305
|
const exec = await container.exec({
|
|
56070
56306
|
Cmd: cmd,
|
|
56071
|
-
Env: this.buildEnv(
|
|
56307
|
+
Env: this.buildEnv(request.env),
|
|
56072
56308
|
AttachStdout: true,
|
|
56073
56309
|
AttachStderr: true,
|
|
56074
56310
|
WorkingDir: SANDBOX_WORKDIR,
|
|
@@ -56627,6 +56863,7 @@ var init_docker = __esm(() => {
|
|
|
56627
56863
|
init_runtime();
|
|
56628
56864
|
init_logger();
|
|
56629
56865
|
init_audit();
|
|
56866
|
+
init_code_fetcher();
|
|
56630
56867
|
init_pool();
|
|
56631
56868
|
import_dockerode = __toESM(require_docker(), 1);
|
|
56632
56869
|
MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
@@ -56637,7 +56874,7 @@ var package_default;
|
|
|
56637
56874
|
var init_package = __esm(() => {
|
|
56638
56875
|
package_default = {
|
|
56639
56876
|
name: "isol8",
|
|
56640
|
-
version: "0.10.
|
|
56877
|
+
version: "0.10.3",
|
|
56641
56878
|
description: "Secure code execution engine for AI agents",
|
|
56642
56879
|
author: "Illusion47586",
|
|
56643
56880
|
license: "MIT",
|
|
@@ -58388,7 +58625,7 @@ async function createServer(options) {
|
|
|
58388
58625
|
app.post("/execute", async (c) => {
|
|
58389
58626
|
const body = await c.req.json();
|
|
58390
58627
|
logger.debug(`[Server] POST /execute runtime=${body.request.runtime} sessionId=${body.sessionId ?? "ephemeral"}`);
|
|
58391
|
-
logger.debug(`[Server] Code
|
|
58628
|
+
logger.debug(`[Server] Code source: ${body.request.codeUrl ? `url=${body.request.codeUrl}` : `inline (${body.request.code?.length ?? 0} chars)`}`);
|
|
58392
58629
|
const engineOptions = {
|
|
58393
58630
|
network: config.defaults.network,
|
|
58394
58631
|
memoryLimit: config.defaults.memoryLimit,
|
|
@@ -58396,6 +58633,7 @@ async function createServer(options) {
|
|
|
58396
58633
|
timeoutMs: config.defaults.timeoutMs,
|
|
58397
58634
|
sandboxSize: config.defaults.sandboxSize,
|
|
58398
58635
|
tmpSize: config.defaults.tmpSize,
|
|
58636
|
+
remoteCode: config.remoteCode,
|
|
58399
58637
|
...body.options,
|
|
58400
58638
|
mode: body.sessionId ? "persistent" : "ephemeral",
|
|
58401
58639
|
audit: config.audit
|
|
@@ -58407,11 +58645,12 @@ async function createServer(options) {
|
|
|
58407
58645
|
logger.debug(`[Server] Reusing existing session: ${body.sessionId}`);
|
|
58408
58646
|
engine = session.engine;
|
|
58409
58647
|
session.lastAccessedAt = Date.now();
|
|
58648
|
+
session.isActive = true;
|
|
58410
58649
|
} else {
|
|
58411
58650
|
logger.debug(`[Server] Creating new session: ${body.sessionId}`);
|
|
58412
58651
|
engine = new DockerIsol82(engineOptions, config.maxConcurrent);
|
|
58413
58652
|
await engine.start();
|
|
58414
|
-
sessions.set(body.sessionId, { engine, lastAccessedAt: Date.now() });
|
|
58653
|
+
sessions.set(body.sessionId, { engine, lastAccessedAt: Date.now(), isActive: true });
|
|
58415
58654
|
}
|
|
58416
58655
|
} else {
|
|
58417
58656
|
logger.debug("[Server] Creating ephemeral engine");
|
|
@@ -58433,7 +58672,13 @@ async function createServer(options) {
|
|
|
58433
58672
|
logger.debug(`[Server] Execution error: ${message}`);
|
|
58434
58673
|
return c.json({ error: message }, 500);
|
|
58435
58674
|
} finally {
|
|
58436
|
-
if (
|
|
58675
|
+
if (body.sessionId) {
|
|
58676
|
+
const session = sessions.get(body.sessionId);
|
|
58677
|
+
if (session) {
|
|
58678
|
+
session.isActive = false;
|
|
58679
|
+
session.lastAccessedAt = Date.now();
|
|
58680
|
+
}
|
|
58681
|
+
} else {
|
|
58437
58682
|
logger.debug("[Server] Cleaning up ephemeral engine");
|
|
58438
58683
|
await engine.stop();
|
|
58439
58684
|
}
|
|
@@ -58442,7 +58687,7 @@ async function createServer(options) {
|
|
|
58442
58687
|
app.post("/execute/stream", async (c) => {
|
|
58443
58688
|
const body = await c.req.json();
|
|
58444
58689
|
logger.debug(`[Server] POST /execute/stream runtime=${body.request.runtime}`);
|
|
58445
|
-
logger.debug(`[Server] Code
|
|
58690
|
+
logger.debug(`[Server] Code source: ${body.request.codeUrl ? `url=${body.request.codeUrl}` : `inline (${body.request.code?.length ?? 0} chars)`}`);
|
|
58446
58691
|
const engineOptions = {
|
|
58447
58692
|
network: config.defaults.network,
|
|
58448
58693
|
memoryLimit: config.defaults.memoryLimit,
|
|
@@ -58450,6 +58695,7 @@ async function createServer(options) {
|
|
|
58450
58695
|
timeoutMs: config.defaults.timeoutMs,
|
|
58451
58696
|
sandboxSize: config.defaults.sandboxSize,
|
|
58452
58697
|
tmpSize: config.defaults.tmpSize,
|
|
58698
|
+
remoteCode: config.remoteCode,
|
|
58453
58699
|
...body.options,
|
|
58454
58700
|
mode: "ephemeral"
|
|
58455
58701
|
};
|
|
@@ -58543,6 +58789,9 @@ async function createServer(options) {
|
|
|
58543
58789
|
const maxAge = config.cleanup.maxContainerAgeMs;
|
|
58544
58790
|
const now = Date.now();
|
|
58545
58791
|
for (const [id, session] of sessions) {
|
|
58792
|
+
if (session.isActive) {
|
|
58793
|
+
continue;
|
|
58794
|
+
}
|
|
58546
58795
|
if (now - session.lastAccessedAt > maxAge) {
|
|
58547
58796
|
logger.debug(`[Server] Auto-pruning stale session: ${id}`);
|
|
58548
58797
|
await session.engine.stop();
|
|
@@ -58571,13 +58820,13 @@ import {
|
|
|
58571
58820
|
chmodSync,
|
|
58572
58821
|
existsSync as existsSync5,
|
|
58573
58822
|
mkdirSync as mkdirSync2,
|
|
58574
|
-
readFileSync as
|
|
58823
|
+
readFileSync as readFileSync4,
|
|
58575
58824
|
renameSync,
|
|
58576
58825
|
unlinkSync as unlinkSync2,
|
|
58577
58826
|
writeFileSync
|
|
58578
58827
|
} from "node:fs";
|
|
58579
58828
|
import { arch, homedir as homedir2, platform } from "node:os";
|
|
58580
|
-
import { join as
|
|
58829
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
58581
58830
|
|
|
58582
58831
|
// node_modules/commander/esm.mjs
|
|
58583
58832
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -61971,7 +62220,10 @@ init_docker();
|
|
|
61971
62220
|
|
|
61972
62221
|
// src/engine/image-builder.ts
|
|
61973
62222
|
init_runtime();
|
|
61974
|
-
|
|
62223
|
+
init_logger();
|
|
62224
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
62225
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
62226
|
+
import { join as join3 } from "node:path";
|
|
61975
62227
|
function resolveDockerDir() {
|
|
61976
62228
|
const fromBundled = new URL("./docker", import.meta.url).pathname;
|
|
61977
62229
|
if (existsSync4(fromBundled)) {
|
|
@@ -61980,16 +62232,82 @@ function resolveDockerDir() {
|
|
|
61980
62232
|
return new URL("../../docker", import.meta.url).pathname;
|
|
61981
62233
|
}
|
|
61982
62234
|
var DOCKERFILE_DIR = resolveDockerDir();
|
|
61983
|
-
|
|
62235
|
+
var LABELS = {
|
|
62236
|
+
dockerHash: "org.isol8.build.hash",
|
|
62237
|
+
depsHash: "org.isol8.deps.hash"
|
|
62238
|
+
};
|
|
62239
|
+
var DOCKER_BUILD_FILES = ["Dockerfile", "proxy.sh", "proxy-handler.sh"];
|
|
62240
|
+
function computeDockerDirHash() {
|
|
62241
|
+
const hash = createHash2("sha256");
|
|
62242
|
+
const files = [...DOCKER_BUILD_FILES].sort();
|
|
62243
|
+
for (const file of files) {
|
|
62244
|
+
const filePath = join3(DOCKERFILE_DIR, file);
|
|
62245
|
+
if (existsSync4(filePath)) {
|
|
62246
|
+
const content = readFileSync3(filePath);
|
|
62247
|
+
hash.update(file);
|
|
62248
|
+
hash.update(content);
|
|
62249
|
+
}
|
|
62250
|
+
}
|
|
62251
|
+
return hash.digest("hex");
|
|
62252
|
+
}
|
|
62253
|
+
function computeDepsHash(runtime, packages) {
|
|
62254
|
+
const hash = createHash2("sha256");
|
|
62255
|
+
hash.update(runtime);
|
|
62256
|
+
for (const pkg of [...packages].sort()) {
|
|
62257
|
+
hash.update(pkg);
|
|
62258
|
+
}
|
|
62259
|
+
return hash.digest("hex");
|
|
62260
|
+
}
|
|
62261
|
+
async function getImageLabels(docker, imageName) {
|
|
62262
|
+
try {
|
|
62263
|
+
const image = docker.getImage(imageName);
|
|
62264
|
+
const inspect = await image.inspect();
|
|
62265
|
+
return inspect.Config?.Labels ?? {};
|
|
62266
|
+
} catch {
|
|
62267
|
+
return null;
|
|
62268
|
+
}
|
|
62269
|
+
}
|
|
62270
|
+
async function removeImage(docker, imageId) {
|
|
62271
|
+
try {
|
|
62272
|
+
const image = docker.getImage(imageId);
|
|
62273
|
+
await image.remove();
|
|
62274
|
+
logger.debug(`[ImageBuilder] Removed old image: ${imageId.slice(0, 12)}`);
|
|
62275
|
+
} catch (err) {
|
|
62276
|
+
logger.debug(`[ImageBuilder] Could not remove image ${imageId.slice(0, 12)}: ${err}`);
|
|
62277
|
+
}
|
|
62278
|
+
}
|
|
62279
|
+
async function buildBaseImages(docker, onProgress, force = false) {
|
|
61984
62280
|
const runtimes = RuntimeRegistry.list();
|
|
62281
|
+
const dockerHash = computeDockerDirHash();
|
|
62282
|
+
logger.debug(`[ImageBuilder] Docker directory hash: ${dockerHash.slice(0, 16)}...`);
|
|
61985
62283
|
for (const adapter of runtimes) {
|
|
61986
62284
|
const target = adapter.name;
|
|
62285
|
+
const imageName = adapter.image;
|
|
62286
|
+
if (!force) {
|
|
62287
|
+
const labels = await getImageLabels(docker, imageName);
|
|
62288
|
+
if (labels && labels[LABELS.dockerHash] === dockerHash) {
|
|
62289
|
+
logger.debug(`[ImageBuilder] Base image ${target} is up to date, skipping build`);
|
|
62290
|
+
onProgress?.({ runtime: target, status: "done", message: "Up to date" });
|
|
62291
|
+
continue;
|
|
62292
|
+
}
|
|
62293
|
+
}
|
|
62294
|
+
let oldImageId = null;
|
|
62295
|
+
try {
|
|
62296
|
+
const oldImage = await docker.getImage(imageName).inspect();
|
|
62297
|
+
oldImageId = oldImage.Id;
|
|
62298
|
+
logger.debug(`[ImageBuilder] Existing image ${target} ID: ${oldImageId.slice(0, 12)}`);
|
|
62299
|
+
} catch {
|
|
62300
|
+
logger.debug(`[ImageBuilder] No existing image for ${target}`);
|
|
62301
|
+
}
|
|
61987
62302
|
onProgress?.({ runtime: target, status: "building" });
|
|
61988
62303
|
try {
|
|
61989
|
-
const stream = await docker.buildImage({ context: DOCKERFILE_DIR, src:
|
|
61990
|
-
t:
|
|
62304
|
+
const stream = await docker.buildImage({ context: DOCKERFILE_DIR, src: DOCKER_BUILD_FILES }, {
|
|
62305
|
+
t: imageName,
|
|
61991
62306
|
target,
|
|
61992
|
-
dockerfile: "Dockerfile"
|
|
62307
|
+
dockerfile: "Dockerfile",
|
|
62308
|
+
labels: {
|
|
62309
|
+
[LABELS.dockerHash]: dockerHash
|
|
62310
|
+
}
|
|
61993
62311
|
});
|
|
61994
62312
|
await new Promise((resolve2, reject) => {
|
|
61995
62313
|
docker.modem.followProgress(stream, (err) => {
|
|
@@ -62000,6 +62318,9 @@ async function buildBaseImages(docker, onProgress) {
|
|
|
62000
62318
|
}
|
|
62001
62319
|
});
|
|
62002
62320
|
});
|
|
62321
|
+
if (oldImageId) {
|
|
62322
|
+
await removeImage(docker, oldImageId);
|
|
62323
|
+
}
|
|
62003
62324
|
onProgress?.({ runtime: target, status: "done" });
|
|
62004
62325
|
} catch (err) {
|
|
62005
62326
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -62008,26 +62329,44 @@ async function buildBaseImages(docker, onProgress) {
|
|
|
62008
62329
|
}
|
|
62009
62330
|
}
|
|
62010
62331
|
}
|
|
62011
|
-
async function buildCustomImages(docker, config, onProgress) {
|
|
62332
|
+
async function buildCustomImages(docker, config, onProgress, force = false) {
|
|
62012
62333
|
const deps = config.dependencies;
|
|
62013
62334
|
if (deps.python?.length) {
|
|
62014
|
-
await buildCustomImage(docker, "python", deps.python, onProgress);
|
|
62335
|
+
await buildCustomImage(docker, "python", deps.python, onProgress, force);
|
|
62015
62336
|
}
|
|
62016
62337
|
if (deps.node?.length) {
|
|
62017
|
-
await buildCustomImage(docker, "node", deps.node, onProgress);
|
|
62338
|
+
await buildCustomImage(docker, "node", deps.node, onProgress, force);
|
|
62018
62339
|
}
|
|
62019
62340
|
if (deps.bun?.length) {
|
|
62020
|
-
await buildCustomImage(docker, "bun", deps.bun, onProgress);
|
|
62341
|
+
await buildCustomImage(docker, "bun", deps.bun, onProgress, force);
|
|
62021
62342
|
}
|
|
62022
62343
|
if (deps.deno?.length) {
|
|
62023
|
-
await buildCustomImage(docker, "deno", deps.deno, onProgress);
|
|
62344
|
+
await buildCustomImage(docker, "deno", deps.deno, onProgress, force);
|
|
62024
62345
|
}
|
|
62025
62346
|
if (deps.bash?.length) {
|
|
62026
|
-
await buildCustomImage(docker, "bash", deps.bash, onProgress);
|
|
62347
|
+
await buildCustomImage(docker, "bash", deps.bash, onProgress, force);
|
|
62027
62348
|
}
|
|
62028
62349
|
}
|
|
62029
|
-
async function buildCustomImage(docker, runtime, packages, onProgress) {
|
|
62350
|
+
async function buildCustomImage(docker, runtime, packages, onProgress, force = false) {
|
|
62030
62351
|
const tag = `isol8:${runtime}-custom`;
|
|
62352
|
+
const depsHash = computeDepsHash(runtime, packages);
|
|
62353
|
+
logger.debug(`[ImageBuilder] ${runtime} custom deps hash: ${depsHash.slice(0, 16)}...`);
|
|
62354
|
+
if (!force) {
|
|
62355
|
+
const labels = await getImageLabels(docker, tag);
|
|
62356
|
+
if (labels && labels[LABELS.depsHash] === depsHash) {
|
|
62357
|
+
logger.debug(`[ImageBuilder] Custom image ${runtime} is up to date, skipping build`);
|
|
62358
|
+
onProgress?.({ runtime, status: "done", message: "Up to date" });
|
|
62359
|
+
return;
|
|
62360
|
+
}
|
|
62361
|
+
}
|
|
62362
|
+
let oldImageId = null;
|
|
62363
|
+
try {
|
|
62364
|
+
const oldImage = await docker.getImage(tag).inspect();
|
|
62365
|
+
oldImageId = oldImage.Id;
|
|
62366
|
+
logger.debug(`[ImageBuilder] Existing custom image ${runtime} ID: ${oldImageId.slice(0, 12)}`);
|
|
62367
|
+
} catch {
|
|
62368
|
+
logger.debug(`[ImageBuilder] No existing custom image for ${runtime}`);
|
|
62369
|
+
}
|
|
62031
62370
|
onProgress?.({ runtime, status: "building", message: `Custom: ${packages.join(", ")}` });
|
|
62032
62371
|
let installCmd;
|
|
62033
62372
|
switch (runtime) {
|
|
@@ -62059,7 +62398,10 @@ ${installCmd}
|
|
|
62059
62398
|
const tarBuffer = createTarBuffer2("Dockerfile", dockerfileContent);
|
|
62060
62399
|
const stream = await docker.buildImage(Readable.from(tarBuffer), {
|
|
62061
62400
|
t: tag,
|
|
62062
|
-
dockerfile: "Dockerfile"
|
|
62401
|
+
dockerfile: "Dockerfile",
|
|
62402
|
+
labels: {
|
|
62403
|
+
[LABELS.depsHash]: depsHash
|
|
62404
|
+
}
|
|
62063
62405
|
});
|
|
62064
62406
|
await new Promise((resolve2, reject) => {
|
|
62065
62407
|
docker.modem.followProgress(stream, (err) => {
|
|
@@ -62070,6 +62412,9 @@ ${installCmd}
|
|
|
62070
62412
|
}
|
|
62071
62413
|
});
|
|
62072
62414
|
});
|
|
62415
|
+
if (oldImageId) {
|
|
62416
|
+
await removeImage(docker, oldImageId);
|
|
62417
|
+
}
|
|
62073
62418
|
onProgress?.({ runtime, status: "done" });
|
|
62074
62419
|
}
|
|
62075
62420
|
|
|
@@ -62087,7 +62432,7 @@ program2.name("isol8").description("Secure code execution engine").version(VERSI
|
|
|
62087
62432
|
logger.debug(`[CLI] Version: ${VERSION}`);
|
|
62088
62433
|
logger.debug(`[CLI] Platform: ${platform()} ${arch()}`);
|
|
62089
62434
|
});
|
|
62090
|
-
program2.command("setup").description("Check Docker and build isol8 images").option("--python <packages>", "Additional Python packages (comma-separated)").option("--node <packages>", "Additional Node.js packages (comma-separated)").option("--bun <packages>", "Additional Bun packages (comma-separated)").option("--deno <packages>", "Additional Deno packages (comma-separated)").option("--bash <packages>", "Additional Bash packages (comma-separated)").action(async (opts) => {
|
|
62435
|
+
program2.command("setup").description("Check Docker and build isol8 images").option("--python <packages>", "Additional Python packages (comma-separated)").option("--node <packages>", "Additional Node.js packages (comma-separated)").option("--bun <packages>", "Additional Bun packages (comma-separated)").option("--deno <packages>", "Additional Deno packages (comma-separated)").option("--bash <packages>", "Additional Bash packages (comma-separated)").option("--force", "Force rebuild even if images are up to date").action(async (opts) => {
|
|
62091
62436
|
const docker = new import_dockerode2.default;
|
|
62092
62437
|
logger.debug("[Setup] Connecting to Docker daemon");
|
|
62093
62438
|
const spinner = ora("Checking Docker...").start();
|
|
@@ -62102,7 +62447,7 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
|
|
|
62102
62447
|
process.exit(1);
|
|
62103
62448
|
}
|
|
62104
62449
|
spinner.start("Building isol8 images...");
|
|
62105
|
-
logger.debug(
|
|
62450
|
+
logger.debug(`[Setup] Building base images (force=${opts.force ?? false})`);
|
|
62106
62451
|
await buildBaseImages(docker, (progress) => {
|
|
62107
62452
|
const status = progress.status === "error" ? "[ERR]" : progress.status === "done" ? "[OK]" : "[..]";
|
|
62108
62453
|
if (progress.status === "building") {
|
|
@@ -62118,7 +62463,7 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
|
|
|
62118
62463
|
spinner.start();
|
|
62119
62464
|
}
|
|
62120
62465
|
}
|
|
62121
|
-
});
|
|
62466
|
+
}, opts.force ?? false);
|
|
62122
62467
|
if (spinner.isSpinning) {
|
|
62123
62468
|
spinner.stop();
|
|
62124
62469
|
}
|
|
@@ -62166,7 +62511,7 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
|
|
|
62166
62511
|
spinner.start();
|
|
62167
62512
|
}
|
|
62168
62513
|
}
|
|
62169
|
-
});
|
|
62514
|
+
}, opts.force ?? false);
|
|
62170
62515
|
if (spinner.isSpinning) {
|
|
62171
62516
|
spinner.stop();
|
|
62172
62517
|
}
|
|
@@ -62174,12 +62519,25 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
|
|
|
62174
62519
|
console.log(`
|
|
62175
62520
|
[DONE] Setup complete!`);
|
|
62176
62521
|
});
|
|
62177
|
-
program2.command("run").description("Execute code in isol8").argument("[file]", "Script file to execute").option("-e, --eval <code>", "Execute inline code string").option("-r, --runtime <name>", "Force runtime (python, node, bun, deno, bash)").option("--net <mode>", "Network mode: none, host, filtered", "none").option("--allow <regex>", "Whitelist regex for filtered mode (repeatable)", collect, []).option("--deny <regex>", "Blacklist regex for filtered mode (repeatable)", collect, []).option("--out <file>", "Write output to file").option("--persistent", "Use persistent container").option("--timeout <ms>", "Execution timeout in milliseconds").option("--memory <limit>", "Memory limit (e.g. 512m, 1g)").option("--cpu <limit>", "CPU limit as fraction (e.g. 0.5, 2.0)").option("--image <name>", "Override Docker image").option("--pids-limit <n>", "Maximum number of processes").option("--writable", "Disable read-only root filesystem").option("--max-output <bytes>", "Maximum output size in bytes").option("--secret <KEY=VALUE>", "Secret env var (repeatable, values masked)", collect, []).option("--sandbox-size <size>", "Sandbox tmpfs size (e.g. 128m, 512m)").option("--tmp-size <size>", "Tmp tmpfs size (e.g. 256m, 512m)").option("--stdin <data>", "Data to pipe to stdin").option("--install <package>", "Install package for runtime (repeatable)", collect, []).option("--host <url>", "Execute on remote server").option("--key <key>", "API key for remote server").option("--no-stream", "Disable real-time output streaming").option("--debug", "Enable debug logging").option("--persist", "Keep container running after execution for inspection").option("--log-network", "Log all network requests (requires --net filtered)").option("--pool-strategy <mode>", "Pool strategy: fast (default) or secure", "fast").option("--pool-size <size>", "Pool size (number or 'clean,dirty' for fast mode)", "1,1").action(async (file, opts) => {
|
|
62178
|
-
const {
|
|
62522
|
+
program2.command("run").description("Execute code in isol8").argument("[file]", "Script file to execute").option("-e, --eval <code>", "Execute inline code string").option("-r, --runtime <name>", "Force runtime (python, node, bun, deno, bash)").option("--net <mode>", "Network mode: none, host, filtered", "none").option("--allow <regex>", "Whitelist regex for filtered mode (repeatable)", collect, []).option("--deny <regex>", "Blacklist regex for filtered mode (repeatable)", collect, []).option("--out <file>", "Write output to file").option("--persistent", "Use persistent container").option("--timeout <ms>", "Execution timeout in milliseconds").option("--memory <limit>", "Memory limit (e.g. 512m, 1g)").option("--cpu <limit>", "CPU limit as fraction (e.g. 0.5, 2.0)").option("--image <name>", "Override Docker image").option("--pids-limit <n>", "Maximum number of processes").option("--writable", "Disable read-only root filesystem").option("--max-output <bytes>", "Maximum output size in bytes").option("--secret <KEY=VALUE>", "Secret env var (repeatable, values masked)", collect, []).option("--sandbox-size <size>", "Sandbox tmpfs size (e.g. 128m, 512m)").option("--tmp-size <size>", "Tmp tmpfs size (e.g. 256m, 512m)").option("--stdin <data>", "Data to pipe to stdin").option("--install <package>", "Install package for runtime (repeatable)", collect, []).option("--url <url>", "Fetch code from URL").option("--github <path>", "GitHub shorthand: owner/repo/ref/path/to/file").option("--gist <path>", "Gist shorthand: gistId/file.ext").option("--hash <sha256>", "Expected SHA-256 hash of fetched code").option("--allow-insecure-code-url", "Allow insecure HTTP code URLs").option("--host <url>", "Execute on remote server").option("--key <key>", "API key for remote server").option("--no-stream", "Disable real-time output streaming").option("--debug", "Enable debug logging").option("--persist", "Keep container running after execution for inspection").option("--log-network", "Log all network requests (requires --net filtered)").option("--pool-strategy <mode>", "Pool strategy: fast (default) or secure", "fast").option("--pool-size <size>", "Pool size (number or 'clean,dirty' for fast mode)", "1,1").action(async (file, opts) => {
|
|
62523
|
+
const {
|
|
62524
|
+
code,
|
|
62525
|
+
codeUrl,
|
|
62526
|
+
codeHash,
|
|
62527
|
+
allowInsecureCodeUrl,
|
|
62528
|
+
runtime,
|
|
62529
|
+
engineOptions,
|
|
62530
|
+
engine,
|
|
62531
|
+
stdinData,
|
|
62532
|
+
fileExtension
|
|
62533
|
+
} = await resolveRunInput(file, opts);
|
|
62179
62534
|
logger.debug(`[Run] Runtime: ${runtime}, mode: ${engineOptions.mode}`);
|
|
62180
62535
|
logger.debug(`[Run] Network: ${engineOptions.network}, timeout: ${engineOptions.timeoutMs}ms`);
|
|
62181
62536
|
logger.debug(`[Run] Memory: ${engineOptions.memoryLimit}, CPU: ${engineOptions.cpuLimit}`);
|
|
62182
|
-
logger.debug(`[Run] Code
|
|
62537
|
+
logger.debug(`[Run] Code source: ${codeUrl ? `url=${codeUrl}` : "inline/file/stdin"}`);
|
|
62538
|
+
if (code) {
|
|
62539
|
+
logger.debug(`[Run] Code length: ${code.length} chars`);
|
|
62540
|
+
}
|
|
62183
62541
|
if (stdinData) {
|
|
62184
62542
|
logger.debug(`[Run] Stdin data provided (${stdinData.length} chars)`);
|
|
62185
62543
|
}
|
|
@@ -62205,9 +62563,12 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
|
|
|
62205
62563
|
logger.debug("[Run] Engine started");
|
|
62206
62564
|
spinner.text = "Running code...";
|
|
62207
62565
|
const req = {
|
|
62208
|
-
code,
|
|
62209
62566
|
runtime,
|
|
62210
62567
|
timeoutMs: engineOptions.timeoutMs,
|
|
62568
|
+
...code ? { code } : {},
|
|
62569
|
+
...codeUrl ? { codeUrl } : {},
|
|
62570
|
+
...codeHash ? { codeHash } : {},
|
|
62571
|
+
...allowInsecureCodeUrl ? { allowInsecureCodeUrl } : {},
|
|
62211
62572
|
...stdinData ? { stdin: stdinData } : {},
|
|
62212
62573
|
...opts.install.length > 0 ? { installPackages: opts.install } : {},
|
|
62213
62574
|
fileExtension
|
|
@@ -62367,7 +62728,7 @@ async function downloadServerBinary(binaryPath) {
|
|
|
62367
62728
|
}
|
|
62368
62729
|
process.exit(1);
|
|
62369
62730
|
}
|
|
62370
|
-
const binDir =
|
|
62731
|
+
const binDir = join4(homedir2(), ".isol8", "bin");
|
|
62371
62732
|
mkdirSync2(binDir, { recursive: true });
|
|
62372
62733
|
const tmpPath = `${binaryPath}.tmp`;
|
|
62373
62734
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
@@ -62399,8 +62760,8 @@ async function promptYesNo(question) {
|
|
|
62399
62760
|
return normalized === "" || normalized === "y" || normalized === "yes";
|
|
62400
62761
|
}
|
|
62401
62762
|
async function ensureServerBinary(forceUpdate) {
|
|
62402
|
-
const binDir =
|
|
62403
|
-
const binaryPath =
|
|
62763
|
+
const binDir = join4(homedir2(), ".isol8", "bin");
|
|
62764
|
+
const binaryPath = join4(binDir, "isol8-server");
|
|
62404
62765
|
logger.debug(`[Serve] Binary path: ${binaryPath}, forceUpdate: ${forceUpdate}`);
|
|
62405
62766
|
if (forceUpdate) {
|
|
62406
62767
|
logger.debug("[Serve] Force update requested");
|
|
@@ -62430,8 +62791,8 @@ async function ensureServerBinary(forceUpdate) {
|
|
|
62430
62791
|
program2.command("config").description("Show the resolved isol8 configuration").option("--json", "Output as raw JSON").action((opts) => {
|
|
62431
62792
|
const config = loadConfig();
|
|
62432
62793
|
const searchPaths = [
|
|
62433
|
-
|
|
62434
|
-
|
|
62794
|
+
join4(resolve2(process.cwd()), "isol8.config.json"),
|
|
62795
|
+
join4(homedir2(), ".isol8", "config.json")
|
|
62435
62796
|
];
|
|
62436
62797
|
const loadedFrom = searchPaths.find((p) => existsSync5(p));
|
|
62437
62798
|
logger.debug(`[Config] Config source: ${loadedFrom ?? "defaults"}`);
|
|
@@ -62466,6 +62827,13 @@ Isol8 Configuration
|
|
|
62466
62827
|
} else {
|
|
62467
62828
|
console.log(" Whitelist: (none)");
|
|
62468
62829
|
}
|
|
62830
|
+
console.log("");
|
|
62831
|
+
console.log(" ── Remote Code ──");
|
|
62832
|
+
console.log(` Enabled: ${config.remoteCode.enabled ? "yes" : "no"}`);
|
|
62833
|
+
console.log(` Schemes: ${config.remoteCode.allowedSchemes.join(", ")}`);
|
|
62834
|
+
console.log(` Max code size: ${config.remoteCode.maxCodeSize} bytes`);
|
|
62835
|
+
console.log(` Fetch timeout: ${config.remoteCode.fetchTimeoutMs}ms`);
|
|
62836
|
+
console.log(` Require hash: ${config.remoteCode.requireHash ? "yes" : "no"}`);
|
|
62469
62837
|
if (config.network.blacklist.length > 0) {
|
|
62470
62838
|
console.log(` Blacklist: ${config.network.blacklist.join(", ")}`);
|
|
62471
62839
|
} else {
|
|
@@ -62560,8 +62928,25 @@ async function resolveRunInput(file, opts) {
|
|
|
62560
62928
|
const config = loadConfig();
|
|
62561
62929
|
logger.debug("[Run] Config loaded");
|
|
62562
62930
|
let code;
|
|
62931
|
+
let codeUrl;
|
|
62932
|
+
let codeHash;
|
|
62933
|
+
let allowInsecureCodeUrl = false;
|
|
62563
62934
|
let runtime;
|
|
62564
|
-
if (opts.
|
|
62935
|
+
if (opts.url || opts.github || opts.gist) {
|
|
62936
|
+
if (file || opts.eval) {
|
|
62937
|
+
console.error("[ERR] --url/--github/--gist cannot be used with file input or --eval.");
|
|
62938
|
+
process.exit(1);
|
|
62939
|
+
}
|
|
62940
|
+
codeUrl = resolveCodeUrl(opts);
|
|
62941
|
+
codeHash = opts.hash ?? undefined;
|
|
62942
|
+
allowInsecureCodeUrl = opts.allowInsecureCodeUrl ?? false;
|
|
62943
|
+
runtime = opts.runtime ?? detectRuntimeFromPath(new URL(codeUrl).pathname);
|
|
62944
|
+
if (!runtime) {
|
|
62945
|
+
console.error("[ERR] Cannot detect runtime from URL path. Use --runtime to specify.");
|
|
62946
|
+
process.exit(1);
|
|
62947
|
+
}
|
|
62948
|
+
logger.debug(`[Run] Remote code URL: ${codeUrl}`);
|
|
62949
|
+
} else if (opts.eval) {
|
|
62565
62950
|
code = opts.eval;
|
|
62566
62951
|
runtime = opts.runtime ?? "python";
|
|
62567
62952
|
logger.debug(`[Run] Inline eval, runtime: ${runtime}`);
|
|
@@ -62572,7 +62957,7 @@ async function resolveRunInput(file, opts) {
|
|
|
62572
62957
|
console.error(`[ERR] File not found: ${file}`);
|
|
62573
62958
|
process.exit(1);
|
|
62574
62959
|
}
|
|
62575
|
-
code =
|
|
62960
|
+
code = readFileSync4(filePath, "utf-8");
|
|
62576
62961
|
if (opts.runtime) {
|
|
62577
62962
|
runtime = opts.runtime;
|
|
62578
62963
|
logger.debug(`[Run] Runtime specified: ${runtime}`);
|
|
@@ -62612,6 +62997,7 @@ async function resolveRunInput(file, opts) {
|
|
|
62612
62997
|
debug: opts.debug ?? config.debug,
|
|
62613
62998
|
persist: opts.persist ?? false,
|
|
62614
62999
|
...opts.logNetwork ? { logNetwork: true } : {},
|
|
63000
|
+
remoteCode: config.remoteCode,
|
|
62615
63001
|
poolStrategy: opts.poolStrategy === "secure" ? "secure" : "fast",
|
|
62616
63002
|
poolSize: opts.poolSize ? opts.poolSize.includes(",") ? {
|
|
62617
63003
|
clean: Number.parseInt(opts.poolSize.split(",")[0], 10),
|
|
@@ -62650,7 +63036,48 @@ async function resolveRunInput(file, opts) {
|
|
|
62650
63036
|
logger.debug("[Run] Using local Docker engine");
|
|
62651
63037
|
engine = new DockerIsol8(engineOptions, config.maxConcurrent);
|
|
62652
63038
|
}
|
|
62653
|
-
return {
|
|
63039
|
+
return {
|
|
63040
|
+
code,
|
|
63041
|
+
codeUrl,
|
|
63042
|
+
codeHash,
|
|
63043
|
+
allowInsecureCodeUrl,
|
|
63044
|
+
runtime,
|
|
63045
|
+
engineOptions,
|
|
63046
|
+
engine,
|
|
63047
|
+
stdinData,
|
|
63048
|
+
fileExtension
|
|
63049
|
+
};
|
|
63050
|
+
}
|
|
63051
|
+
function resolveCodeUrl(opts) {
|
|
63052
|
+
if (typeof opts.url === "string") {
|
|
63053
|
+
return opts.url;
|
|
63054
|
+
}
|
|
63055
|
+
if (typeof opts.github === "string") {
|
|
63056
|
+
const parts = opts.github.split("/");
|
|
63057
|
+
if (parts.length < 4) {
|
|
63058
|
+
console.error("[ERR] --github format must be owner/repo/ref/path/to/file");
|
|
63059
|
+
process.exit(1);
|
|
63060
|
+
}
|
|
63061
|
+
const [owner, repo, ref, ...pathParts] = parts;
|
|
63062
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${pathParts.join("/")}`;
|
|
63063
|
+
}
|
|
63064
|
+
if (typeof opts.gist === "string") {
|
|
63065
|
+
const [gistId, ...fileParts] = opts.gist.split("/");
|
|
63066
|
+
if (!gistId || fileParts.length === 0) {
|
|
63067
|
+
console.error("[ERR] --gist format must be gistId/file.ext");
|
|
63068
|
+
process.exit(1);
|
|
63069
|
+
}
|
|
63070
|
+
return `https://gist.githubusercontent.com/${gistId}/raw/${fileParts.join("/")}`;
|
|
63071
|
+
}
|
|
63072
|
+
console.error("[ERR] Missing code URL source.");
|
|
63073
|
+
process.exit(1);
|
|
63074
|
+
}
|
|
63075
|
+
function detectRuntimeFromPath(pathValue) {
|
|
63076
|
+
try {
|
|
63077
|
+
return RuntimeRegistry.detect(pathValue).name;
|
|
63078
|
+
} catch {
|
|
63079
|
+
return;
|
|
63080
|
+
}
|
|
62654
63081
|
}
|
|
62655
63082
|
function collect(value, previous) {
|
|
62656
63083
|
return previous.concat([value]);
|
|
@@ -62661,4 +63088,4 @@ if (!process.argv.slice(2).length) {
|
|
|
62661
63088
|
}
|
|
62662
63089
|
program2.parse();
|
|
62663
63090
|
|
|
62664
|
-
//# debugId=
|
|
63091
|
+
//# debugId=280ED4C71DBDC32964756E2164756E21
|