isol8 0.8.3 → 0.9.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/dist/cli.js +354 -43
- package/dist/docker/proxy-handler.sh +8 -0
- package/dist/docker/proxy.sh +3 -0
- package/dist/index.js +324 -13
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/engine/audit.d.ts +31 -0
- package/dist/src/engine/audit.d.ts.map +1 -0
- package/dist/src/engine/docker.d.ts +9 -0
- package/dist/src/engine/docker.d.ts.map +1 -1
- package/dist/src/engine/stats.d.ts +35 -0
- package/dist/src/engine/stats.d.ts.map +1 -0
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +85 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/schema/isol8.config.schema.json +47 -0
package/dist/cli.js
CHANGED
|
@@ -5038,23 +5038,23 @@ var require_nacl_fast = __commonJS((exports, module) => {
|
|
|
5038
5038
|
randombytes = fn;
|
|
5039
5039
|
};
|
|
5040
5040
|
(function() {
|
|
5041
|
-
var
|
|
5042
|
-
if (
|
|
5041
|
+
var crypto2 = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
|
|
5042
|
+
if (crypto2 && crypto2.getRandomValues) {
|
|
5043
5043
|
var QUOTA = 65536;
|
|
5044
5044
|
nacl.setPRNG(function(x, n) {
|
|
5045
5045
|
var i, v = new Uint8Array(n);
|
|
5046
5046
|
for (i = 0;i < n; i += QUOTA) {
|
|
5047
|
-
|
|
5047
|
+
crypto2.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
|
|
5048
5048
|
}
|
|
5049
5049
|
for (i = 0;i < n; i++)
|
|
5050
5050
|
x[i] = v[i];
|
|
5051
5051
|
cleanup(v);
|
|
5052
5052
|
});
|
|
5053
5053
|
} else if (true) {
|
|
5054
|
-
|
|
5055
|
-
if (
|
|
5054
|
+
crypto2 = __require("crypto");
|
|
5055
|
+
if (crypto2 && crypto2.randomBytes) {
|
|
5056
5056
|
nacl.setPRNG(function(x, n) {
|
|
5057
|
-
var i, v =
|
|
5057
|
+
var i, v = crypto2.randomBytes(n);
|
|
5058
5058
|
for (i = 0;i < n; i++)
|
|
5059
5059
|
x[i] = v[i];
|
|
5060
5060
|
cleanup(v);
|
|
@@ -6329,14 +6329,14 @@ var require_lib2 = __commonJS((exports, module) => {
|
|
|
6329
6329
|
|
|
6330
6330
|
// node_modules/ssh2/lib/protocol/constants.js
|
|
6331
6331
|
var require_constants = __commonJS((exports, module) => {
|
|
6332
|
-
var
|
|
6332
|
+
var crypto2 = __require("crypto");
|
|
6333
6333
|
var cpuInfo;
|
|
6334
6334
|
try {
|
|
6335
6335
|
cpuInfo = require_lib2()();
|
|
6336
6336
|
} catch {}
|
|
6337
6337
|
var { bindingAvailable, CIPHER_INFO, MAC_INFO } = require_crypto();
|
|
6338
6338
|
var eddsaSupported = (() => {
|
|
6339
|
-
if (typeof
|
|
6339
|
+
if (typeof crypto2.sign === "function" && typeof crypto2.verify === "function") {
|
|
6340
6340
|
const key = `-----BEGIN PRIVATE KEY-----\r
|
|
6341
6341
|
MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
6342
6342
|
-----END PRIVATE KEY-----`;
|
|
@@ -6344,14 +6344,14 @@ MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
|
6344
6344
|
let sig;
|
|
6345
6345
|
let verified;
|
|
6346
6346
|
try {
|
|
6347
|
-
sig =
|
|
6348
|
-
verified =
|
|
6347
|
+
sig = crypto2.sign(null, data, key);
|
|
6348
|
+
verified = crypto2.verify(null, data, key, sig);
|
|
6349
6349
|
} catch {}
|
|
6350
6350
|
return Buffer.isBuffer(sig) && sig.length === 64 && verified === true;
|
|
6351
6351
|
}
|
|
6352
6352
|
return false;
|
|
6353
6353
|
})();
|
|
6354
|
-
var curve25519Supported = typeof
|
|
6354
|
+
var curve25519Supported = typeof crypto2.diffieHellman === "function" && typeof crypto2.generateKeyPairSync === "function" && typeof crypto2.createPublicKey === "function";
|
|
6355
6355
|
var DEFAULT_KEX = [
|
|
6356
6356
|
"ecdh-sha2-nistp256",
|
|
6357
6357
|
"ecdh-sha2-nistp384",
|
|
@@ -6386,7 +6386,7 @@ MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
|
6386
6386
|
"ssh-dss"
|
|
6387
6387
|
]);
|
|
6388
6388
|
var canUseCipher = (() => {
|
|
6389
|
-
const ciphers =
|
|
6389
|
+
const ciphers = crypto2.getCiphers();
|
|
6390
6390
|
return (name) => ciphers.includes(CIPHER_INFO[name].sslName);
|
|
6391
6391
|
})();
|
|
6392
6392
|
var DEFAULT_CIPHER = [
|
|
@@ -6421,7 +6421,7 @@ MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
|
6421
6421
|
"arcfour"
|
|
6422
6422
|
].filter(canUseCipher));
|
|
6423
6423
|
var canUseMAC = (() => {
|
|
6424
|
-
const hashes =
|
|
6424
|
+
const hashes = crypto2.getHashes();
|
|
6425
6425
|
return (name) => hashes.includes(MAC_INFO[name].sslName);
|
|
6426
6426
|
})();
|
|
6427
6427
|
var DEFAULT_MAC = [
|
|
@@ -54803,6 +54803,10 @@ 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
|
+
audit: {
|
|
54807
|
+
...defaults.audit,
|
|
54808
|
+
...overrides.audit
|
|
54809
|
+
},
|
|
54806
54810
|
debug: overrides.debug ?? defaults.debug
|
|
54807
54811
|
};
|
|
54808
54812
|
}
|
|
@@ -54830,6 +54834,16 @@ var init_config = __esm(() => {
|
|
|
54830
54834
|
security: {
|
|
54831
54835
|
seccomp: "strict"
|
|
54832
54836
|
},
|
|
54837
|
+
audit: {
|
|
54838
|
+
enabled: false,
|
|
54839
|
+
destination: "filesystem",
|
|
54840
|
+
logDir: undefined,
|
|
54841
|
+
postLogScript: undefined,
|
|
54842
|
+
trackResources: true,
|
|
54843
|
+
retentionDays: 90,
|
|
54844
|
+
includeCode: false,
|
|
54845
|
+
includeOutput: false
|
|
54846
|
+
},
|
|
54833
54847
|
debug: false
|
|
54834
54848
|
};
|
|
54835
54849
|
});
|
|
@@ -55022,6 +55036,144 @@ var init_logger = __esm(() => {
|
|
|
55022
55036
|
logger = new Logger;
|
|
55023
55037
|
});
|
|
55024
55038
|
|
|
55039
|
+
// src/engine/audit.ts
|
|
55040
|
+
import { spawn } from "node:child_process";
|
|
55041
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
55042
|
+
import { join as join2 } from "node:path";
|
|
55043
|
+
|
|
55044
|
+
class AuditLogger {
|
|
55045
|
+
config;
|
|
55046
|
+
auditFile;
|
|
55047
|
+
constructor(config) {
|
|
55048
|
+
this.config = config;
|
|
55049
|
+
const auditDir = config.logDir ?? process.env.ISOL8_AUDIT_DIR ?? join2(process.cwd(), "./.isol8_audit");
|
|
55050
|
+
this.auditFile = join2(auditDir, "executions.log");
|
|
55051
|
+
if (!existsSync2(auditDir)) {
|
|
55052
|
+
try {
|
|
55053
|
+
mkdirSync(auditDir, { recursive: true });
|
|
55054
|
+
} catch (err) {
|
|
55055
|
+
logger.error("Failed to create audit dir:", err);
|
|
55056
|
+
}
|
|
55057
|
+
}
|
|
55058
|
+
this.cleanupOldLogs();
|
|
55059
|
+
}
|
|
55060
|
+
cleanupOldLogs() {
|
|
55061
|
+
if (!this.config.enabled || this.config.retentionDays <= 0) {
|
|
55062
|
+
return;
|
|
55063
|
+
}
|
|
55064
|
+
try {
|
|
55065
|
+
const auditDir = join2(this.auditFile, "..");
|
|
55066
|
+
if (!existsSync2(auditDir)) {
|
|
55067
|
+
return;
|
|
55068
|
+
}
|
|
55069
|
+
const cutoffTime = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
|
|
55070
|
+
const files = readdirSync(auditDir);
|
|
55071
|
+
let cleanedCount = 0;
|
|
55072
|
+
for (const file of files) {
|
|
55073
|
+
if (file.endsWith(".log") || file.endsWith(".jsonl")) {
|
|
55074
|
+
const filePath = join2(auditDir, file);
|
|
55075
|
+
try {
|
|
55076
|
+
const stats = statSync(filePath);
|
|
55077
|
+
if (stats.mtimeMs < cutoffTime) {
|
|
55078
|
+
unlinkSync(filePath);
|
|
55079
|
+
cleanedCount++;
|
|
55080
|
+
logger.debug(`Cleaned up old audit log: ${file}`);
|
|
55081
|
+
}
|
|
55082
|
+
} catch (err) {
|
|
55083
|
+
logger.debug(`Failed to check/remove old log file ${file}:`, err);
|
|
55084
|
+
}
|
|
55085
|
+
}
|
|
55086
|
+
}
|
|
55087
|
+
if (cleanedCount > 0) {
|
|
55088
|
+
logger.info(`Audit log cleanup: removed ${cleanedCount} old log files`);
|
|
55089
|
+
}
|
|
55090
|
+
} catch (err) {
|
|
55091
|
+
logger.error("Failed to cleanup old audit logs:", err);
|
|
55092
|
+
}
|
|
55093
|
+
}
|
|
55094
|
+
record(audit) {
|
|
55095
|
+
if (!this.config.enabled) {
|
|
55096
|
+
return;
|
|
55097
|
+
}
|
|
55098
|
+
try {
|
|
55099
|
+
const filteredAudit = this.filterAuditData(audit);
|
|
55100
|
+
const line = `${JSON.stringify(filteredAudit)}
|
|
55101
|
+
`;
|
|
55102
|
+
switch (this.config.destination) {
|
|
55103
|
+
case "file":
|
|
55104
|
+
case "filesystem":
|
|
55105
|
+
appendFileSync(this.auditFile, line, { encoding: "utf-8" });
|
|
55106
|
+
break;
|
|
55107
|
+
case "stdout":
|
|
55108
|
+
console.log("AUDIT_LOG:", filteredAudit);
|
|
55109
|
+
break;
|
|
55110
|
+
default:
|
|
55111
|
+
logger.error(`Unsupported audit destination: ${this.config.destination}`);
|
|
55112
|
+
return;
|
|
55113
|
+
}
|
|
55114
|
+
logger.debug("Audit record written:", audit.executionId);
|
|
55115
|
+
if (this.config.postLogScript) {
|
|
55116
|
+
this.runPostLogScript();
|
|
55117
|
+
}
|
|
55118
|
+
} catch (err) {
|
|
55119
|
+
logger.error("Failed to write audit record:", err);
|
|
55120
|
+
}
|
|
55121
|
+
}
|
|
55122
|
+
runPostLogScript() {
|
|
55123
|
+
if (!this.config.postLogScript) {
|
|
55124
|
+
return;
|
|
55125
|
+
}
|
|
55126
|
+
try {
|
|
55127
|
+
const child = spawn(this.config.postLogScript, [this.auditFile], {
|
|
55128
|
+
detached: true,
|
|
55129
|
+
stdio: "ignore"
|
|
55130
|
+
});
|
|
55131
|
+
child.on("error", (err) => {
|
|
55132
|
+
logger.error("Failed to run post-log script:", err);
|
|
55133
|
+
});
|
|
55134
|
+
child.unref();
|
|
55135
|
+
} catch (err) {
|
|
55136
|
+
logger.error("Failed to spawn post-log script:", err);
|
|
55137
|
+
}
|
|
55138
|
+
}
|
|
55139
|
+
filterAuditData(audit) {
|
|
55140
|
+
const result = {
|
|
55141
|
+
executionId: audit.executionId,
|
|
55142
|
+
userId: audit.userId,
|
|
55143
|
+
timestamp: audit.timestamp,
|
|
55144
|
+
runtime: audit.runtime,
|
|
55145
|
+
codeHash: audit.codeHash,
|
|
55146
|
+
containerId: audit.containerId,
|
|
55147
|
+
exitCode: audit.exitCode,
|
|
55148
|
+
durationMs: audit.durationMs
|
|
55149
|
+
};
|
|
55150
|
+
if (audit.resourceUsage !== undefined) {
|
|
55151
|
+
result.resourceUsage = audit.resourceUsage;
|
|
55152
|
+
}
|
|
55153
|
+
if (audit.securityEvents !== undefined) {
|
|
55154
|
+
result.securityEvents = audit.securityEvents;
|
|
55155
|
+
}
|
|
55156
|
+
if (audit.metadata !== undefined) {
|
|
55157
|
+
result.metadata = audit.metadata;
|
|
55158
|
+
}
|
|
55159
|
+
if (this.config.includeCode && audit.code !== undefined) {
|
|
55160
|
+
result.code = audit.code;
|
|
55161
|
+
}
|
|
55162
|
+
if (this.config.includeOutput) {
|
|
55163
|
+
if (audit.stdout !== undefined) {
|
|
55164
|
+
result.stdout = audit.stdout;
|
|
55165
|
+
}
|
|
55166
|
+
if (audit.stderr !== undefined) {
|
|
55167
|
+
result.stderr = audit.stderr;
|
|
55168
|
+
}
|
|
55169
|
+
}
|
|
55170
|
+
return result;
|
|
55171
|
+
}
|
|
55172
|
+
}
|
|
55173
|
+
var init_audit = __esm(() => {
|
|
55174
|
+
init_logger();
|
|
55175
|
+
});
|
|
55176
|
+
|
|
55025
55177
|
// src/engine/concurrency.ts
|
|
55026
55178
|
class Semaphore {
|
|
55027
55179
|
max;
|
|
@@ -55180,6 +55332,51 @@ var init_pool = __esm(() => {
|
|
|
55180
55332
|
init_logger();
|
|
55181
55333
|
});
|
|
55182
55334
|
|
|
55335
|
+
// src/engine/stats.ts
|
|
55336
|
+
function calculateCPUPercent(stats) {
|
|
55337
|
+
const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
|
|
55338
|
+
const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
|
|
55339
|
+
if (systemDelta === 0 || cpuDelta === 0) {
|
|
55340
|
+
return 0;
|
|
55341
|
+
}
|
|
55342
|
+
const numCores = stats.cpu_stats.online_cpus ?? stats.cpu_stats.cpu_usage.percpu_usage?.length ?? 1;
|
|
55343
|
+
return cpuDelta / systemDelta * numCores * 100;
|
|
55344
|
+
}
|
|
55345
|
+
function calculateNetworkStats(stats) {
|
|
55346
|
+
if (!stats.networks) {
|
|
55347
|
+
return { in: 0, out: 0 };
|
|
55348
|
+
}
|
|
55349
|
+
let rxBytes = 0;
|
|
55350
|
+
let txBytes = 0;
|
|
55351
|
+
for (const iface of Object.values(stats.networks)) {
|
|
55352
|
+
rxBytes += iface.rx_bytes;
|
|
55353
|
+
txBytes += iface.tx_bytes;
|
|
55354
|
+
}
|
|
55355
|
+
return { in: rxBytes, out: txBytes };
|
|
55356
|
+
}
|
|
55357
|
+
async function getContainerStats(container) {
|
|
55358
|
+
const stats = await container.stats({
|
|
55359
|
+
stream: false
|
|
55360
|
+
});
|
|
55361
|
+
const cpuPercent = calculateCPUPercent(stats);
|
|
55362
|
+
const memoryBytes = stats.memory_stats.usage;
|
|
55363
|
+
const network = calculateNetworkStats(stats);
|
|
55364
|
+
return {
|
|
55365
|
+
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
|
55366
|
+
memoryMB: Math.round(memoryBytes / (1024 * 1024)),
|
|
55367
|
+
networkBytesIn: network.in,
|
|
55368
|
+
networkBytesOut: network.out
|
|
55369
|
+
};
|
|
55370
|
+
}
|
|
55371
|
+
function calculateResourceDelta(before, after) {
|
|
55372
|
+
return {
|
|
55373
|
+
cpuPercent: after.cpuPercent,
|
|
55374
|
+
memoryMB: after.memoryMB,
|
|
55375
|
+
networkBytesIn: after.networkBytesIn - before.networkBytesIn,
|
|
55376
|
+
networkBytesOut: after.networkBytesOut - before.networkBytesOut
|
|
55377
|
+
};
|
|
55378
|
+
}
|
|
55379
|
+
|
|
55183
55380
|
// src/engine/utils.ts
|
|
55184
55381
|
var exports_utils = {};
|
|
55185
55382
|
__export(exports_utils, {
|
|
@@ -55290,14 +55487,14 @@ var exports_docker = {};
|
|
|
55290
55487
|
__export(exports_docker, {
|
|
55291
55488
|
DockerIsol8: () => DockerIsol8
|
|
55292
55489
|
});
|
|
55293
|
-
import { spawn } from "node:child_process";
|
|
55490
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
55294
55491
|
import { randomUUID } from "node:crypto";
|
|
55295
|
-
import { existsSync as
|
|
55492
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
55296
55493
|
import { PassThrough } from "node:stream";
|
|
55297
55494
|
async function writeFileViaExec(container, filePath, content) {
|
|
55298
55495
|
const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
55299
55496
|
return new Promise((resolve2, reject) => {
|
|
55300
|
-
const child =
|
|
55497
|
+
const child = spawn2("docker", ["exec", "-i", "-u", "sandbox", container.id, "sh", "-c", `cat > ${filePath}`], {
|
|
55301
55498
|
stdio: ["pipe", "ignore", "pipe"]
|
|
55302
55499
|
});
|
|
55303
55500
|
child.on("error", (err) => {
|
|
@@ -55483,6 +55680,7 @@ class DockerIsol8 {
|
|
|
55483
55680
|
tmpSize;
|
|
55484
55681
|
security;
|
|
55485
55682
|
persist;
|
|
55683
|
+
auditLogger;
|
|
55486
55684
|
container = null;
|
|
55487
55685
|
persistentRuntime = null;
|
|
55488
55686
|
pool = null;
|
|
@@ -55504,6 +55702,9 @@ class DockerIsol8 {
|
|
|
55504
55702
|
this.tmpSize = options.tmpSize ?? "256m";
|
|
55505
55703
|
this.persist = options.persist ?? false;
|
|
55506
55704
|
this.security = options.security ?? { seccomp: "strict" };
|
|
55705
|
+
if (options.audit) {
|
|
55706
|
+
this.auditLogger = new AuditLogger(options.audit);
|
|
55707
|
+
}
|
|
55507
55708
|
if (options.debug) {
|
|
55508
55709
|
logger.setDebug(true);
|
|
55509
55710
|
}
|
|
@@ -55527,12 +55728,79 @@ class DockerIsol8 {
|
|
|
55527
55728
|
}
|
|
55528
55729
|
async execute(req) {
|
|
55529
55730
|
await this.semaphore.acquire();
|
|
55731
|
+
const startTime = Date.now();
|
|
55530
55732
|
try {
|
|
55531
|
-
|
|
55733
|
+
const result = this.mode === "persistent" ? await this.executePersistent(req, startTime) : await this.executeEphemeral(req, startTime);
|
|
55734
|
+
return result;
|
|
55532
55735
|
} finally {
|
|
55533
55736
|
this.semaphore.release();
|
|
55534
55737
|
}
|
|
55535
55738
|
}
|
|
55739
|
+
async recordAudit(req, result, startTime, container) {
|
|
55740
|
+
try {
|
|
55741
|
+
const enc = new TextEncoder;
|
|
55742
|
+
const data = enc.encode(req.code);
|
|
55743
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
55744
|
+
const codeHash = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
55745
|
+
let securityEvents;
|
|
55746
|
+
if (container && this.network === "filtered") {
|
|
55747
|
+
securityEvents = await this.collectSecurityEvents(container);
|
|
55748
|
+
if (securityEvents.length === 0) {
|
|
55749
|
+
securityEvents = undefined;
|
|
55750
|
+
}
|
|
55751
|
+
}
|
|
55752
|
+
const audit = {
|
|
55753
|
+
executionId: result.executionId,
|
|
55754
|
+
userId: req.metadata?.userId || "",
|
|
55755
|
+
timestamp: new Date(startTime).toISOString(),
|
|
55756
|
+
runtime: result.runtime,
|
|
55757
|
+
codeHash,
|
|
55758
|
+
containerId: result.containerId || "",
|
|
55759
|
+
exitCode: result.exitCode,
|
|
55760
|
+
durationMs: result.durationMs,
|
|
55761
|
+
resourceUsage: result.resourceUsage,
|
|
55762
|
+
securityEvents,
|
|
55763
|
+
metadata: req.metadata
|
|
55764
|
+
};
|
|
55765
|
+
this.auditLogger.record(audit);
|
|
55766
|
+
} catch (err) {
|
|
55767
|
+
logger.error("Failed to record audit log:", err);
|
|
55768
|
+
}
|
|
55769
|
+
}
|
|
55770
|
+
async collectSecurityEvents(container) {
|
|
55771
|
+
const events = [];
|
|
55772
|
+
try {
|
|
55773
|
+
const exec = await container.exec({
|
|
55774
|
+
Cmd: ["cat", "/tmp/isol8-proxy/security-events.jsonl"],
|
|
55775
|
+
AttachStdout: true,
|
|
55776
|
+
AttachStderr: false,
|
|
55777
|
+
User: "root"
|
|
55778
|
+
});
|
|
55779
|
+
const stream = await exec.start({ Tty: false });
|
|
55780
|
+
const chunks = [];
|
|
55781
|
+
for await (const chunk of stream) {
|
|
55782
|
+
chunks.push(chunk);
|
|
55783
|
+
}
|
|
55784
|
+
const output = Buffer.concat(chunks).toString("utf-8").trim();
|
|
55785
|
+
if (output) {
|
|
55786
|
+
for (const line of output.split(`
|
|
55787
|
+
`)) {
|
|
55788
|
+
if (line.trim()) {
|
|
55789
|
+
try {
|
|
55790
|
+
const event = JSON.parse(line);
|
|
55791
|
+
events.push({
|
|
55792
|
+
type: event.type || "unknown",
|
|
55793
|
+
message: `Security event: ${event.type}`,
|
|
55794
|
+
details: event.details || {},
|
|
55795
|
+
timestamp: event.timestamp || new Date().toISOString()
|
|
55796
|
+
});
|
|
55797
|
+
} catch {}
|
|
55798
|
+
}
|
|
55799
|
+
}
|
|
55800
|
+
}
|
|
55801
|
+
} catch {}
|
|
55802
|
+
return events;
|
|
55803
|
+
}
|
|
55536
55804
|
async putFile(path, content) {
|
|
55537
55805
|
if (!this.container) {
|
|
55538
55806
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
@@ -55640,7 +55908,7 @@ class DockerIsol8 {
|
|
|
55640
55908
|
return adapter.image;
|
|
55641
55909
|
}
|
|
55642
55910
|
}
|
|
55643
|
-
async executeEphemeral(req) {
|
|
55911
|
+
async executeEphemeral(req, startTime) {
|
|
55644
55912
|
const adapter = this.getAdapter(req.runtime);
|
|
55645
55913
|
const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs;
|
|
55646
55914
|
const image = await this.resolveImage(adapter);
|
|
@@ -55659,6 +55927,14 @@ class DockerIsol8 {
|
|
|
55659
55927
|
});
|
|
55660
55928
|
}
|
|
55661
55929
|
const container = await this.pool.acquire(image);
|
|
55930
|
+
let startStats;
|
|
55931
|
+
if (this.auditLogger) {
|
|
55932
|
+
try {
|
|
55933
|
+
startStats = await getContainerStats(container);
|
|
55934
|
+
} catch (err) {
|
|
55935
|
+
logger.debug("Failed to collect baseline stats:", err);
|
|
55936
|
+
}
|
|
55937
|
+
}
|
|
55662
55938
|
try {
|
|
55663
55939
|
if (this.network === "filtered") {
|
|
55664
55940
|
await startProxy(container, this.networkFilter);
|
|
@@ -55699,7 +55975,16 @@ class DockerIsol8 {
|
|
|
55699
55975
|
const { stdout, stderr, truncated } = await this.collectExecOutput(execStream, container, timeoutMs);
|
|
55700
55976
|
const durationMs = Math.round(performance.now() - start);
|
|
55701
55977
|
const inspectResult = await exec.inspect();
|
|
55702
|
-
|
|
55978
|
+
let resourceUsage;
|
|
55979
|
+
if (startStats) {
|
|
55980
|
+
try {
|
|
55981
|
+
const endStats = await getContainerStats(container);
|
|
55982
|
+
resourceUsage = calculateResourceDelta(startStats, endStats);
|
|
55983
|
+
} catch (err) {
|
|
55984
|
+
logger.debug("Failed to collect final stats:", err);
|
|
55985
|
+
}
|
|
55986
|
+
}
|
|
55987
|
+
const result = {
|
|
55703
55988
|
stdout: this.postProcessOutput(stdout, truncated),
|
|
55704
55989
|
stderr: this.postProcessOutput(stderr, false),
|
|
55705
55990
|
exitCode: inspectResult.ExitCode ?? 1,
|
|
@@ -55709,8 +55994,13 @@ class DockerIsol8 {
|
|
|
55709
55994
|
runtime: req.runtime,
|
|
55710
55995
|
timestamp: new Date().toISOString(),
|
|
55711
55996
|
containerId: container.id,
|
|
55997
|
+
...resourceUsage ? { resourceUsage } : {},
|
|
55712
55998
|
...req.outputPaths ? { files: await this.retrieveFiles(container, req.outputPaths) } : {}
|
|
55713
55999
|
};
|
|
56000
|
+
if (this.auditLogger) {
|
|
56001
|
+
await this.recordAudit(req, result, startTime, container);
|
|
56002
|
+
}
|
|
56003
|
+
return result;
|
|
55714
56004
|
} finally {
|
|
55715
56005
|
if (this.persist) {
|
|
55716
56006
|
logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
|
|
@@ -55719,7 +56009,7 @@ class DockerIsol8 {
|
|
|
55719
56009
|
}
|
|
55720
56010
|
}
|
|
55721
56011
|
}
|
|
55722
|
-
async executePersistent(req) {
|
|
56012
|
+
async executePersistent(req, startTime) {
|
|
55723
56013
|
const adapter = this.getAdapter(req.runtime);
|
|
55724
56014
|
const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs;
|
|
55725
56015
|
if (!this.container) {
|
|
@@ -55773,7 +56063,21 @@ class DockerIsol8 {
|
|
|
55773
56063
|
const { stdout, stderr, truncated } = await this.collectExecOutput(execStream, this.container, timeoutMs);
|
|
55774
56064
|
const durationMs = Math.round(performance.now() - start);
|
|
55775
56065
|
const inspectResult = await exec.inspect();
|
|
55776
|
-
|
|
56066
|
+
let resourceUsage;
|
|
56067
|
+
if (this.auditLogger) {
|
|
56068
|
+
try {
|
|
56069
|
+
const endStats = await getContainerStats(this.container);
|
|
56070
|
+
resourceUsage = {
|
|
56071
|
+
cpuPercent: endStats.cpuPercent,
|
|
56072
|
+
memoryMB: endStats.memoryMB,
|
|
56073
|
+
networkBytesIn: endStats.networkBytesIn,
|
|
56074
|
+
networkBytesOut: endStats.networkBytesOut
|
|
56075
|
+
};
|
|
56076
|
+
} catch (err) {
|
|
56077
|
+
logger.debug("Failed to collect resource stats:", err);
|
|
56078
|
+
}
|
|
56079
|
+
}
|
|
56080
|
+
const result = {
|
|
55777
56081
|
stdout: this.postProcessOutput(stdout, truncated),
|
|
55778
56082
|
stderr: this.postProcessOutput(stderr, false),
|
|
55779
56083
|
exitCode: inspectResult.ExitCode ?? 1,
|
|
@@ -55783,8 +56087,13 @@ class DockerIsol8 {
|
|
|
55783
56087
|
runtime: req.runtime,
|
|
55784
56088
|
timestamp: new Date().toISOString(),
|
|
55785
56089
|
containerId: this.container?.id,
|
|
56090
|
+
...resourceUsage ? { resourceUsage } : {},
|
|
55786
56091
|
...req.outputPaths ? { files: await this.retrieveFiles(this.container, req.outputPaths) } : {}
|
|
55787
56092
|
};
|
|
56093
|
+
if (this.auditLogger) {
|
|
56094
|
+
await this.recordAudit(req, result, startTime, this.container);
|
|
56095
|
+
}
|
|
56096
|
+
return result;
|
|
55788
56097
|
}
|
|
55789
56098
|
async retrieveFiles(container, paths) {
|
|
55790
56099
|
const files = {};
|
|
@@ -55876,11 +56185,11 @@ class DockerIsol8 {
|
|
|
55876
56185
|
}
|
|
55877
56186
|
loadDefaultSeccompProfile() {
|
|
55878
56187
|
const devPath = new URL("../../docker/seccomp-profile.json", import.meta.url);
|
|
55879
|
-
if (
|
|
56188
|
+
if (existsSync3(devPath)) {
|
|
55880
56189
|
return readFileSync2(devPath, "utf-8");
|
|
55881
56190
|
}
|
|
55882
56191
|
const prodPath = new URL("./docker/seccomp-profile.json", import.meta.url);
|
|
55883
|
-
if (
|
|
56192
|
+
if (existsSync3(prodPath)) {
|
|
55884
56193
|
return readFileSync2(prodPath, "utf-8");
|
|
55885
56194
|
}
|
|
55886
56195
|
logger.warn("Could not locate default seccomp profile. Running without seccomp filter.");
|
|
@@ -56086,6 +56395,7 @@ var import_dockerode, SANDBOX_WORKDIR = "/sandbox", MAX_OUTPUT_BYTES, PROXY_PORT
|
|
|
56086
56395
|
var init_docker = __esm(() => {
|
|
56087
56396
|
init_runtime();
|
|
56088
56397
|
init_logger();
|
|
56398
|
+
init_audit();
|
|
56089
56399
|
init_pool();
|
|
56090
56400
|
import_dockerode = __toESM(require_docker(), 1);
|
|
56091
56401
|
MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
@@ -56096,7 +56406,7 @@ var package_default;
|
|
|
56096
56406
|
var init_package = __esm(() => {
|
|
56097
56407
|
package_default = {
|
|
56098
56408
|
name: "isol8",
|
|
56099
|
-
version: "0.8.
|
|
56409
|
+
version: "0.8.3",
|
|
56100
56410
|
description: "Secure code execution engine for AI agents",
|
|
56101
56411
|
author: "Illusion47586",
|
|
56102
56412
|
license: "MIT",
|
|
@@ -57854,7 +58164,8 @@ async function createServer(options) {
|
|
|
57854
58164
|
sandboxSize: config.defaults.sandboxSize,
|
|
57855
58165
|
tmpSize: config.defaults.tmpSize,
|
|
57856
58166
|
...body.options,
|
|
57857
|
-
mode: body.sessionId ? "persistent" : "ephemeral"
|
|
58167
|
+
mode: body.sessionId ? "persistent" : "ephemeral",
|
|
58168
|
+
audit: config.audit
|
|
57858
58169
|
};
|
|
57859
58170
|
let engine;
|
|
57860
58171
|
if (body.sessionId) {
|
|
@@ -58025,15 +58336,15 @@ var init_server = __esm(() => {
|
|
|
58025
58336
|
// src/cli.ts
|
|
58026
58337
|
import {
|
|
58027
58338
|
chmodSync,
|
|
58028
|
-
existsSync as
|
|
58029
|
-
mkdirSync,
|
|
58339
|
+
existsSync as existsSync5,
|
|
58340
|
+
mkdirSync as mkdirSync2,
|
|
58030
58341
|
readFileSync as readFileSync3,
|
|
58031
58342
|
renameSync,
|
|
58032
|
-
unlinkSync,
|
|
58343
|
+
unlinkSync as unlinkSync2,
|
|
58033
58344
|
writeFileSync
|
|
58034
58345
|
} from "node:fs";
|
|
58035
58346
|
import { arch, homedir as homedir2, platform } from "node:os";
|
|
58036
|
-
import { join as
|
|
58347
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
58037
58348
|
|
|
58038
58349
|
// node_modules/commander/esm.mjs
|
|
58039
58350
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -61427,10 +61738,10 @@ init_docker();
|
|
|
61427
61738
|
|
|
61428
61739
|
// src/engine/image-builder.ts
|
|
61429
61740
|
init_runtime();
|
|
61430
|
-
import { existsSync as
|
|
61741
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
61431
61742
|
function resolveDockerDir() {
|
|
61432
61743
|
const fromBundled = new URL("../docker", import.meta.url).pathname;
|
|
61433
|
-
if (
|
|
61744
|
+
if (existsSync4(fromBundled)) {
|
|
61434
61745
|
return fromBundled;
|
|
61435
61746
|
}
|
|
61436
61747
|
return new URL("../../docker", import.meta.url).pathname;
|
|
@@ -61783,7 +62094,7 @@ function getServerBinaryName() {
|
|
|
61783
62094
|
return `isol8-server-${resolvedOs}-${resolvedArch}`;
|
|
61784
62095
|
}
|
|
61785
62096
|
async function getServerBinaryVersion(binaryPath) {
|
|
61786
|
-
if (!
|
|
62097
|
+
if (!existsSync5(binaryPath)) {
|
|
61787
62098
|
logger.debug(`[Serve] No binary found at ${binaryPath}`);
|
|
61788
62099
|
return null;
|
|
61789
62100
|
}
|
|
@@ -61816,8 +62127,8 @@ async function downloadServerBinary(binaryPath) {
|
|
|
61816
62127
|
}
|
|
61817
62128
|
process.exit(1);
|
|
61818
62129
|
}
|
|
61819
|
-
const binDir =
|
|
61820
|
-
|
|
62130
|
+
const binDir = join3(homedir2(), ".isol8", "bin");
|
|
62131
|
+
mkdirSync2(binDir, { recursive: true });
|
|
61821
62132
|
const tmpPath = `${binaryPath}.tmp`;
|
|
61822
62133
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
61823
62134
|
writeFileSync(tmpPath, buffer);
|
|
@@ -61828,8 +62139,8 @@ async function downloadServerBinary(binaryPath) {
|
|
|
61828
62139
|
} catch (err) {
|
|
61829
62140
|
spinner.fail("Failed to download server binary");
|
|
61830
62141
|
const tmpPath = `${binaryPath}.tmp`;
|
|
61831
|
-
if (
|
|
61832
|
-
|
|
62142
|
+
if (existsSync5(tmpPath)) {
|
|
62143
|
+
unlinkSync2(tmpPath);
|
|
61833
62144
|
}
|
|
61834
62145
|
throw err;
|
|
61835
62146
|
}
|
|
@@ -61848,8 +62159,8 @@ async function promptYesNo(question) {
|
|
|
61848
62159
|
return normalized === "" || normalized === "y" || normalized === "yes";
|
|
61849
62160
|
}
|
|
61850
62161
|
async function ensureServerBinary(forceUpdate) {
|
|
61851
|
-
const binDir =
|
|
61852
|
-
const binaryPath =
|
|
62162
|
+
const binDir = join3(homedir2(), ".isol8", "bin");
|
|
62163
|
+
const binaryPath = join3(binDir, "isol8-server");
|
|
61853
62164
|
logger.debug(`[Serve] Binary path: ${binaryPath}, forceUpdate: ${forceUpdate}`);
|
|
61854
62165
|
if (forceUpdate) {
|
|
61855
62166
|
logger.debug("[Serve] Force update requested");
|
|
@@ -61879,10 +62190,10 @@ async function ensureServerBinary(forceUpdate) {
|
|
|
61879
62190
|
program2.command("config").description("Show the resolved isol8 configuration").option("--json", "Output as raw JSON").action((opts) => {
|
|
61880
62191
|
const config = loadConfig();
|
|
61881
62192
|
const searchPaths = [
|
|
61882
|
-
|
|
61883
|
-
|
|
62193
|
+
join3(resolve2(process.cwd()), "isol8.config.json"),
|
|
62194
|
+
join3(homedir2(), ".isol8", "config.json")
|
|
61884
62195
|
];
|
|
61885
|
-
const loadedFrom = searchPaths.find((p) =>
|
|
62196
|
+
const loadedFrom = searchPaths.find((p) => existsSync5(p));
|
|
61886
62197
|
logger.debug(`[Config] Config source: ${loadedFrom ?? "defaults"}`);
|
|
61887
62198
|
logger.debug(`[Config] Resolved config: ${JSON.stringify(config)}`);
|
|
61888
62199
|
if (opts.json) {
|
|
@@ -62017,7 +62328,7 @@ async function resolveRunInput(file, opts) {
|
|
|
62017
62328
|
} else if (file) {
|
|
62018
62329
|
const filePath = resolve2(file);
|
|
62019
62330
|
logger.debug(`[Run] Reading file: ${filePath}`);
|
|
62020
|
-
if (!
|
|
62331
|
+
if (!existsSync5(filePath)) {
|
|
62021
62332
|
console.error(`[ERR] File not found: ${file}`);
|
|
62022
62333
|
process.exit(1);
|
|
62023
62334
|
}
|
|
@@ -62104,4 +62415,4 @@ if (!process.argv.slice(2).length) {
|
|
|
62104
62415
|
}
|
|
62105
62416
|
program2.parse();
|
|
62106
62417
|
|
|
62107
|
-
//# debugId=
|
|
62418
|
+
//# debugId=618F453F99998F9064756E2164756E21
|
|
@@ -71,6 +71,10 @@ if [ "$method" = "CONNECT" ]; then
|
|
|
71
71
|
|
|
72
72
|
if ! is_allowed "$host"; then
|
|
73
73
|
msg="isol8: CONNECT to ${host} blocked by network filter"
|
|
74
|
+
# Log security event
|
|
75
|
+
if [ -d "/tmp/isol8-proxy" ]; then
|
|
76
|
+
printf '{"type":"network_blocked","timestamp":"%s","details":{"method":"CONNECT","host":"%s","reason":"filter_mismatch"}}\n' "$(date -Iseconds)" "$host" >> /tmp/isol8-proxy/security-events.jsonl
|
|
77
|
+
fi
|
|
74
78
|
printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
|
|
75
79
|
"${#msg}" "$msg"
|
|
76
80
|
exit 0
|
|
@@ -96,6 +100,10 @@ port="${hostport##*:}"
|
|
|
96
100
|
|
|
97
101
|
if ! is_allowed "$host"; then
|
|
98
102
|
msg="isol8: request to ${host} blocked by network filter"
|
|
103
|
+
# Log security event
|
|
104
|
+
if [ -d "/tmp/isol8-proxy" ]; then
|
|
105
|
+
printf '{"type":"network_blocked","timestamp":"%s","details":{"method":"%s","host":"%s","reason":"filter_mismatch"}}\n' "$(date -Iseconds)" "$method" "$host" >> /tmp/isol8-proxy/security-events.jsonl
|
|
106
|
+
fi
|
|
99
107
|
printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
|
|
100
108
|
"${#msg}" "$msg"
|
|
101
109
|
exit 0
|
package/dist/docker/proxy.sh
CHANGED