@aspectlylabs/mikka 0.0.172 → 0.0.173
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/cli.js +2 -2
- package/client.js +2 -2
- package/http-link.js +2 -2
- package/login.js +2 -2
- package/main.js +2 -2
- package/mikka-release-manifest.json +18 -13
- package/mikka.js +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/paths.js +2 -2
- package/run-sandbox-worker.mjs +360 -0
- package/sbom.cdx.json +7 -7
package/cli.js
CHANGED
|
@@ -4480,7 +4480,7 @@ import { join as join4 } from "node:path";
|
|
|
4480
4480
|
// package.json
|
|
4481
4481
|
var package_default = {
|
|
4482
4482
|
name: "@mikka/daemon",
|
|
4483
|
-
version: "0.0.
|
|
4483
|
+
version: "0.0.173",
|
|
4484
4484
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
4485
4485
|
type: "module",
|
|
4486
4486
|
private: true,
|
|
@@ -4540,7 +4540,7 @@ var package_default = {
|
|
|
4540
4540
|
engines: {
|
|
4541
4541
|
node: ">=24"
|
|
4542
4542
|
},
|
|
4543
|
-
gitHead: "
|
|
4543
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
4544
4544
|
};
|
|
4545
4545
|
|
|
4546
4546
|
// src/paths.ts
|
package/client.js
CHANGED
|
@@ -3741,7 +3741,7 @@ import { join } from "node:path";
|
|
|
3741
3741
|
// package.json
|
|
3742
3742
|
var package_default = {
|
|
3743
3743
|
name: "@mikka/daemon",
|
|
3744
|
-
version: "0.0.
|
|
3744
|
+
version: "0.0.173",
|
|
3745
3745
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
3746
3746
|
type: "module",
|
|
3747
3747
|
private: true,
|
|
@@ -3801,7 +3801,7 @@ var package_default = {
|
|
|
3801
3801
|
engines: {
|
|
3802
3802
|
node: ">=24"
|
|
3803
3803
|
},
|
|
3804
|
-
gitHead: "
|
|
3804
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
3805
3805
|
};
|
|
3806
3806
|
|
|
3807
3807
|
// src/paths.ts
|
package/http-link.js
CHANGED
|
@@ -4175,7 +4175,7 @@ var InvalidIdError = class extends DaemonError {
|
|
|
4175
4175
|
// package.json
|
|
4176
4176
|
var package_default = {
|
|
4177
4177
|
name: "@mikka/daemon",
|
|
4178
|
-
version: "0.0.
|
|
4178
|
+
version: "0.0.173",
|
|
4179
4179
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
4180
4180
|
type: "module",
|
|
4181
4181
|
private: true,
|
|
@@ -4235,7 +4235,7 @@ var package_default = {
|
|
|
4235
4235
|
engines: {
|
|
4236
4236
|
node: ">=24"
|
|
4237
4237
|
},
|
|
4238
|
-
gitHead: "
|
|
4238
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
4239
4239
|
};
|
|
4240
4240
|
|
|
4241
4241
|
// src/paths.ts
|
package/login.js
CHANGED
|
@@ -3748,7 +3748,7 @@ var ServerUnreachableError = class extends DaemonError {
|
|
|
3748
3748
|
// package.json
|
|
3749
3749
|
var package_default = {
|
|
3750
3750
|
name: "@mikka/daemon",
|
|
3751
|
-
version: "0.0.
|
|
3751
|
+
version: "0.0.173",
|
|
3752
3752
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
3753
3753
|
type: "module",
|
|
3754
3754
|
private: true,
|
|
@@ -3808,7 +3808,7 @@ var package_default = {
|
|
|
3808
3808
|
engines: {
|
|
3809
3809
|
node: ">=24"
|
|
3810
3810
|
},
|
|
3811
|
-
gitHead: "
|
|
3811
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
3812
3812
|
};
|
|
3813
3813
|
|
|
3814
3814
|
// src/paths.ts
|
package/main.js
CHANGED
|
@@ -3785,7 +3785,7 @@ import { join as join5 } from "node:path";
|
|
|
3785
3785
|
// package.json
|
|
3786
3786
|
var package_default = {
|
|
3787
3787
|
name: "@mikka/daemon",
|
|
3788
|
-
version: "0.0.
|
|
3788
|
+
version: "0.0.173",
|
|
3789
3789
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
3790
3790
|
type: "module",
|
|
3791
3791
|
private: true,
|
|
@@ -3845,7 +3845,7 @@ var package_default = {
|
|
|
3845
3845
|
engines: {
|
|
3846
3846
|
node: ">=24"
|
|
3847
3847
|
},
|
|
3848
|
-
gitHead: "
|
|
3848
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
3849
3849
|
};
|
|
3850
3850
|
|
|
3851
3851
|
// src/paths.ts
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"sourceSha": "
|
|
3
|
+
"sourceSha": "f0f4857928d1cfc385e5087b443f6c740b47727d",
|
|
4
4
|
"repository": "alexj11324/Mikka",
|
|
5
|
-
"buildRunId": "
|
|
5
|
+
"buildRunId": "41308067207",
|
|
6
6
|
"buildKind": "github-actions-main",
|
|
7
7
|
"sourceLockSha256": "c99f1e4f641d8e004cfe807653478e10f91e53fcf47102add5f4c154cdc1cb57",
|
|
8
|
-
"createdAt": "2026-11-
|
|
8
|
+
"createdAt": "2026-11-01T13:04:13.631Z",
|
|
9
9
|
"sbom": "sbom.cdx.json",
|
|
10
10
|
"files": [
|
|
11
11
|
{
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
{
|
|
42
42
|
"path": "cli.js",
|
|
43
43
|
"size": 650230,
|
|
44
|
-
"sha256": "
|
|
44
|
+
"sha256": "666643726eeb6514a80ab849b658f35199aaebd0d0967ea9a9aa341d5d57ec6e"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "client.js",
|
|
48
48
|
"size": 141206,
|
|
49
|
-
"sha256": "
|
|
49
|
+
"sha256": "32a6dcff6ecb0b1f2f3378db5ba1cc5b555168d484def492360ef7f9345ad298"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"path": "computer/driver.mjs",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
{
|
|
177
177
|
"path": "http-link.js",
|
|
178
178
|
"size": 184199,
|
|
179
|
-
"sha256": "
|
|
179
|
+
"sha256": "6df0de0bd6a3afd4c5ab74d3f8d63830ce3c7b049675b7769cd2373d7df4adfc"
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
"path": "log.js",
|
|
@@ -186,12 +186,12 @@
|
|
|
186
186
|
{
|
|
187
187
|
"path": "login.js",
|
|
188
188
|
"size": 146084,
|
|
189
|
-
"sha256": "
|
|
189
|
+
"sha256": "d34c26ba6b9f0ad87edeebab91df1655c9d3b1dd32f729cc61382e52731518e2"
|
|
190
190
|
},
|
|
191
191
|
{
|
|
192
192
|
"path": "main.js",
|
|
193
193
|
"size": 595447,
|
|
194
|
-
"sha256": "
|
|
194
|
+
"sha256": "464d5d501c71a1bbe4a38712465244d9aec70c125e75b124ff120d61c1b6bc83"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
"path": "mikka-mcp.mjs",
|
|
@@ -201,22 +201,27 @@
|
|
|
201
201
|
{
|
|
202
202
|
"path": "mikka.js",
|
|
203
203
|
"size": 650246,
|
|
204
|
-
"sha256": "
|
|
204
|
+
"sha256": "a06968aefae3c1ccd33dd43f165d9492119d21db921dee708a7a0896d09a734f"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
"path": "npm-shrinkwrap.json",
|
|
208
208
|
"size": 78117,
|
|
209
|
-
"sha256": "
|
|
209
|
+
"sha256": "65a4dfcacfbb51ec07e851a08615dea08e603bca3ab661e7bb210aaeac8a395a"
|
|
210
210
|
},
|
|
211
211
|
{
|
|
212
212
|
"path": "package.json",
|
|
213
213
|
"size": 1247,
|
|
214
|
-
"sha256": "
|
|
214
|
+
"sha256": "b04797960d02bec545262483034f898bd2ef46bc2a2ca23fb8e8d4f48bc66c7f"
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"path": "paths.js",
|
|
218
218
|
"size": 4138,
|
|
219
|
-
"sha256": "
|
|
219
|
+
"sha256": "16aaad5f1e7e7b699fa646821418d2136f6c40dc4d7dd63e838240c8e3f75e89"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"path": "run-sandbox-worker.mjs",
|
|
223
|
+
"size": 15600,
|
|
224
|
+
"sha256": "e66dd3ff9beaef137695abc18486acee89f22ab0d9ba51e01d0f8fd4c947c4d7"
|
|
220
225
|
},
|
|
221
226
|
{
|
|
222
227
|
"path": "sandbox.js",
|
|
@@ -226,7 +231,7 @@
|
|
|
226
231
|
{
|
|
227
232
|
"path": "sbom.cdx.json",
|
|
228
233
|
"size": 144314,
|
|
229
|
-
"sha256": "
|
|
234
|
+
"sha256": "ae89074dbaea988c336c6016873da9ce849d258390fdc794ea172d1cbf7b562f"
|
|
230
235
|
}
|
|
231
236
|
]
|
|
232
237
|
}
|
package/mikka.js
CHANGED
|
@@ -4481,7 +4481,7 @@ import { join as join4 } from "node:path";
|
|
|
4481
4481
|
// package.json
|
|
4482
4482
|
var package_default = {
|
|
4483
4483
|
name: "@mikka/daemon",
|
|
4484
|
-
version: "0.0.
|
|
4484
|
+
version: "0.0.173",
|
|
4485
4485
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
4486
4486
|
type: "module",
|
|
4487
4487
|
private: true,
|
|
@@ -4541,7 +4541,7 @@ var package_default = {
|
|
|
4541
4541
|
engines: {
|
|
4542
4542
|
node: ">=24"
|
|
4543
4543
|
},
|
|
4544
|
-
gitHead: "
|
|
4544
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
4545
4545
|
};
|
|
4546
4546
|
|
|
4547
4547
|
// src/paths.ts
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aspectlylabs/mikka",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.173",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@aspectlylabs/mikka",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.173",
|
|
10
10
|
"license": "AGPL-3.0-only",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "1.9.0",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aspectlylabs/mikka",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.173",
|
|
4
4
|
"description": "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
5
|
-
"gitHead": "
|
|
5
|
+
"gitHead": "f0f4857928d1cfc385e5087b443f6c740b47727d",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"private": false,
|
|
8
8
|
"license": "AGPL-3.0-only",
|
package/paths.js
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@mikka/daemon",
|
|
9
|
-
version: "0.0.
|
|
9
|
+
version: "0.0.173",
|
|
10
10
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
11
11
|
type: "module",
|
|
12
12
|
private: true,
|
|
@@ -66,7 +66,7 @@ var package_default = {
|
|
|
66
66
|
engines: {
|
|
67
67
|
node: ">=24"
|
|
68
68
|
},
|
|
69
|
-
gitHead: "
|
|
69
|
+
gitHead: "f0f4857928d1cfc385e5087b443f6c740b47727d"
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
// src/paths.ts
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// This trusted host process owns exactly one SandboxManager and one CLI.
|
|
2
|
+
// Only its parent's private IPC channel can configure it or answer a request.
|
|
3
|
+
import { spawn } from "node:child_process"
|
|
4
|
+
import { randomUUID } from "node:crypto"
|
|
5
|
+
import { isIP, Socket } from "node:net"
|
|
6
|
+
import { isAbsolute } from "node:path"
|
|
7
|
+
import { fileURLToPath } from "node:url"
|
|
8
|
+
|
|
9
|
+
const AUTH_LIMIT = 1_048_576
|
|
10
|
+
const MAX_PENDING_NETWORK = 128
|
|
11
|
+
const NETWORK_DECISION_MS = 5 * 60_000
|
|
12
|
+
|
|
13
|
+
export function assertRunSandboxDependencies(dependencies, platform = process.platform) {
|
|
14
|
+
if (dependencies.errors.length) {
|
|
15
|
+
throw new Error(`sandbox dependencies unavailable: ${dependencies.errors.join("; ")}`)
|
|
16
|
+
}
|
|
17
|
+
// Official 0.0.80 reports a missing apply-seccomp helper as only a warning.
|
|
18
|
+
// That would leave host Unix sockets reachable, so this launcher refuses to
|
|
19
|
+
// initialize or wrap. Old/unknown Linux dependency warnings also fail closed.
|
|
20
|
+
if (platform === "linux" && dependencies.warnings.length) {
|
|
21
|
+
throw new Error(`sandbox isolation dependencies unsafe: ${dependencies.warnings.join("; ")}`)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Runs INSIDE the official sandbox. Linux bwrap deliberately closes extra
|
|
26
|
+
// inherited fds, so a framed stdin prefix carries the token across that
|
|
27
|
+
// boundary. It is converted to a token-only fd3 pipe before the real CLI starts.
|
|
28
|
+
async function authBridge() {
|
|
29
|
+
let token
|
|
30
|
+
let child
|
|
31
|
+
let headerBytes = 0
|
|
32
|
+
let tokenBytes = 0
|
|
33
|
+
const header = Buffer.alloc(4)
|
|
34
|
+
let complete = false
|
|
35
|
+
const fail = () => {
|
|
36
|
+
token?.fill(0)
|
|
37
|
+
process.stderr.write("run sandbox auth pipe was incomplete or invalid\n")
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
process.stdin.on("error", fail)
|
|
41
|
+
process.stdin.once("end", () => { if (!complete) fail() })
|
|
42
|
+
const read = (chunk) => {
|
|
43
|
+
let offset = 0
|
|
44
|
+
if (headerBytes < 4) {
|
|
45
|
+
const count = Math.min(4 - headerBytes, chunk.length)
|
|
46
|
+
chunk.copy(header, headerBytes, 0, count)
|
|
47
|
+
headerBytes += count
|
|
48
|
+
offset += count
|
|
49
|
+
if (headerBytes === 4) {
|
|
50
|
+
const length = header.readUInt32BE()
|
|
51
|
+
if (length < 1 || length > AUTH_LIMIT) return fail()
|
|
52
|
+
token = Buffer.alloc(length)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (token) {
|
|
56
|
+
const count = Math.min(token.length - tokenBytes, chunk.length - offset)
|
|
57
|
+
chunk.copy(token, tokenBytes, offset, offset + count)
|
|
58
|
+
chunk.fill(0, offset, offset + count)
|
|
59
|
+
tokenBytes += count
|
|
60
|
+
offset += count
|
|
61
|
+
}
|
|
62
|
+
if (!token || tokenBytes !== token.length) return
|
|
63
|
+
complete = true
|
|
64
|
+
process.stdin.pause()
|
|
65
|
+
process.stdin.off("data", read)
|
|
66
|
+
if (offset < chunk.length) process.stdin.unshift(chunk.subarray(offset))
|
|
67
|
+
child = spawn(process.argv[3], process.argv.slice(4), {
|
|
68
|
+
env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR: "3" },
|
|
69
|
+
stdio: ["pipe", "pipe", "pipe", "pipe"],
|
|
70
|
+
})
|
|
71
|
+
child.once("error", () => fail())
|
|
72
|
+
child.stdin.on("error", () => undefined)
|
|
73
|
+
child.stdio[3].on("error", fail)
|
|
74
|
+
const payload = token
|
|
75
|
+
child.stdio[3].end(payload, () => { payload.fill(0); token = undefined })
|
|
76
|
+
// unshift above preserves protocol bytes delivered in the frame's last
|
|
77
|
+
// chunk. pipe handles subsequent data and backpressure without parsing it.
|
|
78
|
+
process.stdin.pipe(child.stdin)
|
|
79
|
+
child.stdout.pipe(process.stdout, { end: false })
|
|
80
|
+
child.stderr.pipe(process.stderr, { end: false })
|
|
81
|
+
child.once("close", async (code, signal) => {
|
|
82
|
+
token?.fill(0)
|
|
83
|
+
await Promise.all([process.stdout, process.stderr].map((stream) =>
|
|
84
|
+
new Promise((resolve) => stream.write("", resolve))))
|
|
85
|
+
process.exit(code ?? (signal === "SIGINT" ? 130 : 143))
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
process.stdin.on("data", read)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function runWorker() {
|
|
92
|
+
const { NetworkConfigSchema, SandboxManager, SandboxRuntimeConfigSchema } = await import("@anthropic-ai/sandbox-runtime")
|
|
93
|
+
|
|
94
|
+
let configured = false
|
|
95
|
+
let active = false
|
|
96
|
+
let stopping = false
|
|
97
|
+
let cli
|
|
98
|
+
let initialize
|
|
99
|
+
let wrapping
|
|
100
|
+
let nextRequest = 0
|
|
101
|
+
let stderr = ""
|
|
102
|
+
let correlation
|
|
103
|
+
let initTimer
|
|
104
|
+
const pending = new Map()
|
|
105
|
+
const lifetime = new AbortController()
|
|
106
|
+
|
|
107
|
+
// Intentionally kept equivalent to the parent's exported matcher. Both sides
|
|
108
|
+
// independently apply the immutable maximum before a permission callback runs.
|
|
109
|
+
function canonicalHost(host) {
|
|
110
|
+
// eslint-disable-next-line no-control-regex -- Reject control bytes in a destination.
|
|
111
|
+
if (!host || /[\s\x00-\x1f\x7f%\\/@?#*]/u.test(host)) return
|
|
112
|
+
const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host
|
|
113
|
+
if (isIP(bare) === 6) return new URL(`http://[${bare}]`).hostname.slice(1, -1)
|
|
114
|
+
if (bare.includes(":")) return
|
|
115
|
+
try { return new URL(`http://${bare}`).hostname.toLowerCase() } catch { return undefined }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function matchesDomain(request, domains) {
|
|
119
|
+
const host = canonicalHost(request.host)
|
|
120
|
+
if (!host || !Number.isInteger(request.port) || request.port < 1 || request.port > 65535) return false
|
|
121
|
+
return domains.some((entry) => {
|
|
122
|
+
if (entry === "*") return true
|
|
123
|
+
const parts = /^(\[[^\]]+\]|[^:]+?)(?::([1-9][0-9]{0,4}))?$/u.exec(entry)
|
|
124
|
+
if (!parts || (parts[2] !== undefined &&
|
|
125
|
+
(Number(parts[2]) > 65535 || Number(parts[2]) !== request.port))) return false
|
|
126
|
+
const wildcard = parts[1].startsWith("*.")
|
|
127
|
+
const pattern = canonicalHost(wildcard ? parts[1].slice(2) : parts[1])
|
|
128
|
+
if (!pattern) return false
|
|
129
|
+
return wildcard ? !isIP(host) && pattern.includes(".") && host.endsWith(`.${pattern}`) : host === pattern
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function quote(arg) {
|
|
134
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/u.test(arg) ? arg : `'${arg.replaceAll("'", `'"'"'`)}'`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function send(message) {
|
|
138
|
+
if (!process.connected) return
|
|
139
|
+
process.send(message, (error) => { if (error) void stop(0) })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function denyPending() {
|
|
143
|
+
active = false
|
|
144
|
+
lifetime.abort()
|
|
145
|
+
for (const request of pending.values()) {
|
|
146
|
+
clearTimeout(request.timer)
|
|
147
|
+
request.resolve(false)
|
|
148
|
+
}
|
|
149
|
+
pending.clear()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function groupSignal(signal) {
|
|
153
|
+
try { process.kill(-process.pid, signal) } catch (error) {
|
|
154
|
+
if (error.code !== "ESRCH") throw error
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function stop(graceMs = 0, outcome = { code: null, signal: "SIGTERM" }) {
|
|
159
|
+
denyPending()
|
|
160
|
+
if (stopping) return
|
|
161
|
+
stopping = true
|
|
162
|
+
clearTimeout(initTimer)
|
|
163
|
+
// The worker catches TERM; CLI descendants and runtime helpers share its
|
|
164
|
+
// group. A final parent group kill also covers a descendant that ignores it.
|
|
165
|
+
const watchdog = setTimeout(() => {
|
|
166
|
+
process.stderr.write("run sandbox teardown timed out; terminating process group\n")
|
|
167
|
+
groupSignal("SIGKILL")
|
|
168
|
+
}, Math.max(0, Math.min(graceMs, 30_000)) + 4000)
|
|
169
|
+
try {
|
|
170
|
+
groupSignal("SIGTERM")
|
|
171
|
+
if (cli && cli.exitCode === null && cli.signalCode === null) {
|
|
172
|
+
// Linux bwrap creates a new session for its PID namespace. Address the
|
|
173
|
+
// owned wrapper child directly as well as our group; --die-with-parent
|
|
174
|
+
// and --unshare-pid end its nested workload when that wrapper dies.
|
|
175
|
+
cli.kill(graceMs > 0 ? "SIGTERM" : "SIGKILL")
|
|
176
|
+
await new Promise((resolve) => {
|
|
177
|
+
const timer = setTimeout(() => { cli.kill("SIGKILL") }, Math.min(graceMs, 30_000))
|
|
178
|
+
cli.once("exit", () => { clearTimeout(timer); resolve() })
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
await initialize?.catch(() => undefined)
|
|
182
|
+
await wrapping?.catch(() => undefined)
|
|
183
|
+
// Official reset closes this launcher's established proxy connections and
|
|
184
|
+
// removes only its own ephemeral sockets/mountpoints/CA/masked artifacts.
|
|
185
|
+
// No worktree, run directory, logs or caller-owned data are removed here.
|
|
186
|
+
await SandboxManager.reset()
|
|
187
|
+
if (correlation) {
|
|
188
|
+
const annotated = SandboxManager.annotateStderrWithSandboxFailures(correlation, stderr)
|
|
189
|
+
if (annotated.length > stderr.length) process.stderr.write(annotated.slice(stderr.length))
|
|
190
|
+
}
|
|
191
|
+
if (process.connected) await new Promise((resolve) => {
|
|
192
|
+
process.send({ type: "finished", ...outcome }, () => resolve())
|
|
193
|
+
})
|
|
194
|
+
// process.exit does not wait for writes; flush the bridged output first.
|
|
195
|
+
await Promise.all([process.stdout, process.stderr].map((stream) =>
|
|
196
|
+
new Promise((resolve) => stream.write("", resolve))))
|
|
197
|
+
process.exit(outcome.code ?? (outcome.signal === "SIGINT" ? 130 : 143))
|
|
198
|
+
} catch (error) {
|
|
199
|
+
process.stderr.write(`run sandbox teardown failed: ${error.message}\n`)
|
|
200
|
+
// A failed reset cannot be followed by a successful-looking exit or a
|
|
201
|
+
// lingering live proxy. Group kill is fail-closed; parent also enforces it.
|
|
202
|
+
groupSignal("SIGKILL")
|
|
203
|
+
} finally {
|
|
204
|
+
clearTimeout(watchdog)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function start(message) {
|
|
209
|
+
try {
|
|
210
|
+
if (typeof message.command !== "string" || !message.command || message.command.includes("\0") ||
|
|
211
|
+
!Array.isArray(message.args) || message.args.some((arg) => typeof arg !== "string" || arg.includes("\0")) ||
|
|
212
|
+
typeof message.cwd !== "string" || !isAbsolute(message.cwd) ||
|
|
213
|
+
!message.env || typeof message.env !== "object" || Array.isArray(message.env) ||
|
|
214
|
+
Object.entries(message.env).some(([key, value]) => key.includes("=") || key.includes("\0") ||
|
|
215
|
+
(value !== undefined && (typeof value !== "string" || value.includes("\0")))) ||
|
|
216
|
+
!Array.isArray(message.maxAllowDomains) || !Array.isArray(message.modelApiDomains) ||
|
|
217
|
+
typeof message.hasAuthToken !== "boolean") {
|
|
218
|
+
throw new Error("invalid run sandbox launch configuration")
|
|
219
|
+
}
|
|
220
|
+
const maximum = Object.freeze([...message.maxAllowDomains])
|
|
221
|
+
NetworkConfigSchema.parse({ allowedDomains: maximum.filter((entry) => entry !== "*"), deniedDomains: [] })
|
|
222
|
+
NetworkConfigSchema.parse({ allowedDomains: [...message.modelApiDomains], deniedDomains: [] })
|
|
223
|
+
const config = SandboxRuntimeConfigSchema.parse({
|
|
224
|
+
// Every connection, even a model API connection, must consult the live
|
|
225
|
+
// parent closure. No configured allow or command-id allow bypasses it.
|
|
226
|
+
network: {
|
|
227
|
+
allowedDomains: [], deniedDomains: [], strictAllowlist: false,
|
|
228
|
+
allowLocalBinding: false, allowAllUnixSockets: false, allowUnixSockets: [],
|
|
229
|
+
},
|
|
230
|
+
filesystem: message.policy,
|
|
231
|
+
enableWeakerNetworkIsolation: true,
|
|
232
|
+
enableWeakerNestedSandbox: false,
|
|
233
|
+
allowNestedUserNamespaces: false,
|
|
234
|
+
})
|
|
235
|
+
if (!SandboxManager.isSupportedPlatform()) throw new Error("sandbox platform unsupported")
|
|
236
|
+
const dependencies = SandboxManager.checkDependencies()
|
|
237
|
+
assertRunSandboxDependencies(dependencies)
|
|
238
|
+
if (stopping || !process.connected) return
|
|
239
|
+
active = true
|
|
240
|
+
initialize = SandboxManager.initialize(config, async (request) => {
|
|
241
|
+
if (!active || stopping || !process.connected || pending.size >= MAX_PENDING_NETWORK || !matchesDomain(request, maximum)) return false
|
|
242
|
+
const id = ++nextRequest
|
|
243
|
+
return new Promise((resolve) => {
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
if (pending.delete(id)) resolve(false)
|
|
246
|
+
}, NETWORK_DECISION_MS)
|
|
247
|
+
pending.set(id, { resolve, timer })
|
|
248
|
+
send({ type: "network", id, host: request.host, port: request.port })
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
await initialize
|
|
252
|
+
if (!active || stopping || !process.connected) return
|
|
253
|
+
const argv = message.hasAuthToken
|
|
254
|
+
? [process.execPath, fileURLToPath(import.meta.url), "--auth-bridge", message.command, ...message.args]
|
|
255
|
+
: [message.command, ...message.args]
|
|
256
|
+
const command = argv.map(quote).join(" ")
|
|
257
|
+
// Correlation is only for diagnostics, never network/run authority.
|
|
258
|
+
correlation = randomUUID()
|
|
259
|
+
wrapping = SandboxManager.wrapWithSandbox(command, "/bin/sh", undefined, lifetime.signal, { commandId: correlation })
|
|
260
|
+
const wrapped = await wrapping
|
|
261
|
+
if (!active || stopping || !process.connected) return
|
|
262
|
+
const env = { ...message.env }
|
|
263
|
+
// Node's private IPC setup must not be inherited by the untrusted CLI.
|
|
264
|
+
delete env.NODE_CHANNEL_FD
|
|
265
|
+
delete env.NODE_CHANNEL_SERIALIZATION_MODE
|
|
266
|
+
if (message.hasAuthToken === true) {
|
|
267
|
+
env.CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR = "3"
|
|
268
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN
|
|
269
|
+
}
|
|
270
|
+
cli = spawn("/bin/sh", ["-c", `exec ${wrapped}`], {
|
|
271
|
+
cwd: message.cwd,
|
|
272
|
+
env,
|
|
273
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
274
|
+
// Stay in the launcher's group, so even an abrupt worker failure cannot
|
|
275
|
+
// orphan an independently detached CLI before its pid can be reported.
|
|
276
|
+
detached: false,
|
|
277
|
+
})
|
|
278
|
+
cli.stdin.on("error", () => undefined)
|
|
279
|
+
if (message.hasAuthToken) {
|
|
280
|
+
// The token arrives on a separate host pipe. Frame it before forwarding
|
|
281
|
+
// any protocol input, so neither packet boundaries nor early SDK writes
|
|
282
|
+
// can mix credentials with the first CLI message.
|
|
283
|
+
const source = new Socket({ fd: 3, readable: true, writable: false })
|
|
284
|
+
const chunks = []
|
|
285
|
+
let length = 0
|
|
286
|
+
source.on("data", (data) => {
|
|
287
|
+
length += data.length
|
|
288
|
+
if (length > AUTH_LIMIT) {
|
|
289
|
+
data.fill(0)
|
|
290
|
+
source.destroy(new Error("auth pipe payload too large"))
|
|
291
|
+
} else chunks.push(data)
|
|
292
|
+
})
|
|
293
|
+
source.once("error", () => {
|
|
294
|
+
for (const chunk of chunks) chunk.fill(0)
|
|
295
|
+
process.stderr.write("run sandbox auth pipe failed\n")
|
|
296
|
+
void stop(0, { code: 1, signal: null })
|
|
297
|
+
})
|
|
298
|
+
source.once("end", () => {
|
|
299
|
+
if (!length || stopping) {
|
|
300
|
+
for (const chunk of chunks) chunk.fill(0)
|
|
301
|
+
void stop(0, { code: 1, signal: null })
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
const payload = Buffer.concat(chunks)
|
|
305
|
+
for (const chunk of chunks) chunk.fill(0)
|
|
306
|
+
const header = Buffer.alloc(4)
|
|
307
|
+
header.writeUInt32BE(payload.length)
|
|
308
|
+
cli.stdin.write(header)
|
|
309
|
+
cli.stdin.write(payload, () => payload.fill(0))
|
|
310
|
+
process.stdin.pipe(cli.stdin)
|
|
311
|
+
})
|
|
312
|
+
} else process.stdin.pipe(cli.stdin)
|
|
313
|
+
cli.stdout.pipe(process.stdout, { end: false })
|
|
314
|
+
cli.stderr.on("data", (data) => { stderr = (stderr + data.toString()).slice(-16_000) })
|
|
315
|
+
cli.stderr.pipe(process.stderr, { end: false })
|
|
316
|
+
cli.once("error", (error) => {
|
|
317
|
+
process.stderr.write(`sandboxed CLI launch failed: ${error.message}\n`)
|
|
318
|
+
void stop(0, { code: 1, signal: null })
|
|
319
|
+
})
|
|
320
|
+
cli.once("close", (code, signal) => { void stop(0, { code, signal }) })
|
|
321
|
+
} catch (error) {
|
|
322
|
+
process.stderr.write(`run sandbox launch failed: ${error.message}\n`)
|
|
323
|
+
await stop(0, { code: 1, signal: null })
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
process.on("message", (message) => {
|
|
328
|
+
if (!message || typeof message !== "object") return
|
|
329
|
+
if (message.type === "stop") {
|
|
330
|
+
void stop(Number.isFinite(message.graceMs) ? Math.max(0, message.graceMs) : 0)
|
|
331
|
+
} else if (message.type === "init" && !configured && !stopping) {
|
|
332
|
+
configured = true
|
|
333
|
+
clearTimeout(initTimer)
|
|
334
|
+
void start(message)
|
|
335
|
+
} else if (message.type === "decision" && Number.isSafeInteger(message.id)) {
|
|
336
|
+
const request = pending.get(message.id)
|
|
337
|
+
if (!request) return
|
|
338
|
+
pending.delete(message.id)
|
|
339
|
+
clearTimeout(request.timer)
|
|
340
|
+
request.resolve(message.allow === true && active && !stopping && process.connected)
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
process.on("disconnect", () => { void stop(0) })
|
|
344
|
+
process.on("SIGTERM", () => { void stop(0) })
|
|
345
|
+
process.on("SIGINT", () => { void stop(0, { code: null, signal: "SIGINT" }) })
|
|
346
|
+
process.stdout.on("error", () => { void stop(0) })
|
|
347
|
+
process.stderr.on("error", () => { void stop(0) })
|
|
348
|
+
initTimer = setTimeout(() => {
|
|
349
|
+
process.stderr.write("run sandbox launch configuration was not received\n")
|
|
350
|
+
void stop(0, { code: 1, signal: null })
|
|
351
|
+
}, 10_000)
|
|
352
|
+
if (!process.connected) void stop(0, { code: 1, signal: null })
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Importing the pure dependency check for source-only tests does not start a
|
|
356
|
+
// launcher, touch resources or initialize the official manager.
|
|
357
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
358
|
+
if (process.argv[2] === "--auth-bridge") await authBridge()
|
|
359
|
+
else await runWorker()
|
|
360
|
+
}
|
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:57e9fca5-ac79-4f36-8d0a-71d72bbad1a7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-11-
|
|
8
|
+
"timestamp": "2026-11-01T13:04:12.654Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "pre-build"
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@aspectlylabs/mikka@0.0.
|
|
22
|
+
"bom-ref": "@aspectlylabs/mikka@0.0.173",
|
|
23
23
|
"type": "library",
|
|
24
|
-
"name": ".retained-build-
|
|
25
|
-
"version": "0.0.
|
|
24
|
+
"name": ".retained-build-lJvCJN",
|
|
25
|
+
"version": "0.0.173",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"description": "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
28
|
-
"purl": "pkg:npm/%40aspectlylabs/mikka@0.0.
|
|
28
|
+
"purl": "pkg:npm/%40aspectlylabs/mikka@0.0.173",
|
|
29
29
|
"properties": [],
|
|
30
30
|
"externalReferences": [
|
|
31
31
|
{
|
|
@@ -4627,7 +4627,7 @@
|
|
|
4627
4627
|
],
|
|
4628
4628
|
"dependencies": [
|
|
4629
4629
|
{
|
|
4630
|
-
"ref": "@aspectlylabs/mikka@0.0.
|
|
4630
|
+
"ref": "@aspectlylabs/mikka@0.0.173",
|
|
4631
4631
|
"dependsOn": [
|
|
4632
4632
|
"@agentclientprotocol/sdk@1.9.0",
|
|
4633
4633
|
"@anthropic-ai/claude-agent-sdk@0.3.305",
|