@sellable/install 0.1.358 → 0.1.359-phase111.20260720052618

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.
Files changed (31) hide show
  1. package/bin/sellable-install.mjs +0 -2
  2. package/package.json +2 -11
  3. package/skill-templates/refill-sends-evergreen.md +5 -0
  4. package/skill-templates/refill-sends-v2.md +23 -1
  5. package/skill-templates/refill-sends.md +14 -1
  6. package/bin/sellable-agent-egress-proxy.mjs +0 -31
  7. package/bin/sellable-agent-host-bootstrap.mjs +0 -26
  8. package/bin/sellable-agent-host-worker.mjs +0 -9
  9. package/bin/sellable-agent-runtime-helper.mjs +0 -37
  10. package/bin/sellable-agent-runtime-launcher.mjs +0 -151
  11. package/bin/sellable-agent-runtime-probe.mjs +0 -163
  12. package/bin/sellable-agent-sandbox-init.mjs +0 -93
  13. package/container/Dockerfile +0 -37
  14. package/container/README.md +0 -15
  15. package/container/compose.yaml +0 -22
  16. package/container/entrypoint.sh +0 -13
  17. package/lib/sellable-agent/containment-contract.mjs +0 -472
  18. package/lib/sellable-agent/external-runtime-builder.mjs +0 -272
  19. package/lib/sellable-agent/hermes-bridge.mjs +0 -497
  20. package/lib/sellable-agent/host-bootstrap.mjs +0 -458
  21. package/lib/sellable-agent/host-worker.mjs +0 -2067
  22. package/lib/sellable-agent/profile-materializer.mjs +0 -1410
  23. package/lib/sellable-agent/provisioning-adapter.mjs +0 -992
  24. package/lib/sellable-agent/runtime-boundary.mjs +0 -635
  25. package/lib/sellable-agent/runtime-egress-proxy.mjs +0 -241
  26. package/lib/sellable-agent/runtime-helper.mjs +0 -1428
  27. package/lib/sellable-agent/runtime-reconciler.mjs +0 -683
  28. package/lib/sellable-agent/service-installer.mjs +0 -441
  29. package/services/s6/sellable-agent-egress-proxy/run +0 -15
  30. package/services/s6/sellable-agent-host-worker/run +0 -4
  31. package/services/s6/sellable-agent-runtime/run +0 -19
@@ -1,37 +0,0 @@
1
- ARG HERMES_IMAGE=ghcr.io/hostinger/hvps-hermes-agent@sha256:f048663be9ba564de795df5f2a8184f32b92561a1f053c179dca3b45fa5bc8b9
2
- FROM ${HERMES_IMAGE}
3
-
4
- USER root
5
-
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.358
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.619
8
- ARG INSTALLER_INTEGRITY
9
- ARG MCP_INTEGRITY=sha512-ZepZ/YDN09SEyBNKfNEHz751z8s4VQZhvWFUnJmw0mc46YUN2OcJ7s3uN90v4ykNTSnlBrUzUCjhlY3B869aEQ==
10
-
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.358" \
12
- && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.619" \
13
- && test -n "${INSTALLER_INTEGRITY}" \
14
- && test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
15
- && test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
16
- && apt-get update \
17
- && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
18
- bubblewrap \
19
- ca-certificates \
20
- nftables \
21
- sudo \
22
- && rm -rf /var/lib/apt/lists/* \
23
- && mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
24
- && npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
25
- && npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
26
- && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.358' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.619') throw new Error('package identity rejected');" \
27
- && node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
28
- && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.358', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.619', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
29
- && npm cache clean --force
30
-
31
- COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
32
- RUN chmod 0555 /usr/local/bin/sellable-agent-container-entrypoint
33
-
34
- HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
35
- CMD /command/s6-svstat /etc/services.d/sellable-agent-host-worker | grep -q '^up' || exit 1
36
-
37
- ENTRYPOINT ["/usr/local/bin/sellable-agent-container-entrypoint"]
@@ -1,15 +0,0 @@
1
- # Sellable Agent host container
2
-
3
- This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
4
-
5
- Build from this directory after `@sellable/install@0.1.358` is published:
6
-
7
- ```sh
8
- docker build --platform linux/amd64 \
9
- --build-arg INSTALLER_INTEGRITY='sha512-…' \
10
- --tag sellable-agent-host:0.1.358 .
11
- ```
12
-
13
- Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
14
-
15
- Set `SELLABLE_AGENT_IMAGE` to the exact built image digest and start `compose.yaml`. The container bootstraps idempotently, writes `bootstrap/registration-receipt.json`, and starts the worker and isolated runtime under s6. Do not mount a Docker socket.
@@ -1,22 +0,0 @@
1
- services:
2
- sellable-agent-host:
3
- image: ${SELLABLE_AGENT_IMAGE:?set exact Sellable Agent image}
4
- container_name: ${SELLABLE_AGENT_CONTAINER_NAME:-sellable-agent-host-11201}
5
- hostname: ${SELLABLE_AGENT_HOSTNAME:-hermes-hostinger-1807396}
6
- restart: unless-stopped
7
- cap_add:
8
- - NET_ADMIN
9
- - SYS_ADMIN
10
- cgroup: host
11
- pid: host
12
- volumes:
13
- - ./bootstrap:/var/lib/sellable-agent-bootstrap:rw
14
- - sellable-agent-worker:/var/lib/sellable-agent-worker
15
- - sellable-agent-runtime:/var/lib/sellable-agent-runtime
16
- - sellable-agent-profiles:/var/lib/sellable-agent-profiles
17
- - /sys/fs/cgroup:/sys/fs/cgroup:rw
18
-
19
- volumes:
20
- sellable-agent-worker:
21
- sellable-agent-runtime:
22
- sellable-agent-profiles:
@@ -1,13 +0,0 @@
1
- #!/bin/sh
2
- set -eu
3
-
4
- BOOTSTRAP_CONFIG=/var/lib/sellable-agent-bootstrap/bootstrap.json
5
- BOOTSTRAP_BIN=/opt/sellable-agent/install-root/node_modules/.bin/sellable-agent-host-bootstrap
6
-
7
- test "$(id -u)" = "0"
8
- test "$(id -g)" = "0"
9
- test -x "${BOOTSTRAP_BIN}"
10
- test -f "${BOOTSTRAP_CONFIG}"
11
-
12
- "${BOOTSTRAP_BIN}" --config "${BOOTSTRAP_CONFIG}"
13
- exec /command/s6-svscan /etc/services.d
@@ -1,472 +0,0 @@
1
- import {
2
- chmodSync,
3
- existsSync,
4
- lstatSync,
5
- mkdirSync,
6
- readFileSync,
7
- realpathSync,
8
- writeFileSync,
9
- } from "node:fs";
10
- import { isAbsolute, join, relative, resolve } from "node:path";
11
- import { runBoundedChildProcess } from "./provisioning-adapter.mjs";
12
-
13
- const INPUT_KEYS = new Set([
14
- "profileId",
15
- "workerUid",
16
- "runtimeUid",
17
- "credentialReferences",
18
- "oauthPrivateReference",
19
- ]);
20
- const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
21
- const FINGERPRINT = /^[a-f0-9]{64}$/;
22
- const EXACT_EGRESS = [
23
- "https://app.sellable.dev",
24
- "https://slack.com",
25
- "https://api.openai.com",
26
- ];
27
-
28
- export const PINNED_CONTAINMENT_IMAGE =
29
- "public.ecr.aws/supabase/postgres@sha256:af083ef64d0408c8f098ee6f5c364a59b26f36fbc0f3a334a62c5c1d57362e9b";
30
-
31
- const CONTAINER_CHECK_KEYS = [
32
- "runtimeUid",
33
- "profileRead",
34
- "controlDenied",
35
- "credentialDenied",
36
- "oauthDenied",
37
- "siblingDenied",
38
- "hostDenied",
39
- "dockerDenied",
40
- "rootReadOnly",
41
- "noNewPrivileges",
42
- "capabilitiesDropped",
43
- "networkDisabled",
44
- ];
45
-
46
- function validPrivateReference(value, scheme) {
47
- return (
48
- value &&
49
- typeof value === "object" &&
50
- !Array.isArray(value) &&
51
- Object.keys(value).every((key) =>
52
- ["reference", "fingerprint", "purpose", "target", "owner"].includes(key)
53
- ) &&
54
- typeof value.reference === "string" &&
55
- value.reference.startsWith(`${scheme}://`) &&
56
- FINGERPRINT.test(value.fingerprint)
57
- );
58
- }
59
-
60
- export function buildContainmentContract(input) {
61
- if (!input || typeof input !== "object" || Array.isArray(input)) {
62
- return { ok: false, code: "invalid_input" };
63
- }
64
- if (Object.keys(input).some((key) => !INPUT_KEYS.has(key))) {
65
- return { ok: false, code: "unknown_field" };
66
- }
67
- if (
68
- !SAFE_ID.test(input.profileId) ||
69
- !Number.isSafeInteger(input.workerUid) ||
70
- input.workerUid < 1 ||
71
- !Number.isSafeInteger(input.runtimeUid) ||
72
- input.runtimeUid < 1 ||
73
- input.workerUid === input.runtimeUid ||
74
- !Array.isArray(input.credentialReferences) ||
75
- !input.credentialReferences.every(
76
- (reference) =>
77
- validPrivateReference(reference, "secret") &&
78
- ["service-credential", "worker-credential"].includes(reference.purpose)
79
- ) ||
80
- !validPrivateReference(input.oauthPrivateReference, "oauth-private")
81
- ) {
82
- return { ok: false, code: "invalid_input" };
83
- }
84
- const profileBase = `/var/lib/sellable-agents/${input.profileId}`;
85
- const manifest = {
86
- profileId: input.profileId,
87
- readOnlyRootFilesystem: true,
88
- noNewPrivileges: true,
89
- dockerSocket: false,
90
- capabilities: [],
91
- worker: {
92
- name: `sellable-worker-${input.profileId}`,
93
- uid: input.workerUid,
94
- },
95
- runtime: {
96
- name: `sellable-runtime-${input.profileId}`,
97
- uid: input.runtimeUid,
98
- },
99
- mounts: [
100
- {
101
- profileId: input.profileId,
102
- source: `${profileBase}/control`,
103
- target: "/control",
104
- readOnly: false,
105
- owner: "worker",
106
- },
107
- {
108
- profileId: input.profileId,
109
- source: `${profileBase}/profile`,
110
- target: "/profile",
111
- readOnly: false,
112
- owner: "runtime",
113
- },
114
- ],
115
- credentials: input.credentialReferences.map((reference) => ({
116
- purpose: reference.purpose,
117
- reference: reference.reference,
118
- fingerprint: reference.fingerprint,
119
- target: `/run/credentials/${reference.purpose}`,
120
- owner: "worker",
121
- })),
122
- oauthPrivateReference: {
123
- reference: input.oauthPrivateReference.reference,
124
- fingerprint: input.oauthPrivateReference.fingerprint,
125
- owner: "worker",
126
- },
127
- oauth: {
128
- privateExchangeOwner: "worker",
129
- healthStatus: "NOT_PROBED",
130
- auditStatus: "REQUIRED",
131
- broaderRolloutBlocked: true,
132
- },
133
- authority: {
134
- database: false,
135
- migrations: false,
136
- hostControl: false,
137
- },
138
- resources: { pids: 128, memoryMb: 1024, cpuQuota: 1 },
139
- egress: [...EXACT_EGRESS],
140
- };
141
- const validated = validateContainmentContract(manifest);
142
- return validated.ok ? { ok: true, manifest } : validated;
143
- }
144
-
145
- export function validateContainmentContract(manifest) {
146
- if (
147
- !manifest ||
148
- typeof manifest !== "object" ||
149
- !SAFE_ID.test(manifest.profileId)
150
- ) {
151
- return { ok: false, code: "invalid_manifest" };
152
- }
153
- if (
154
- manifest.readOnlyRootFilesystem !== true ||
155
- manifest.noNewPrivileges !== true ||
156
- manifest.dockerSocket !== false ||
157
- !Array.isArray(manifest.capabilities) ||
158
- manifest.capabilities.length !== 0
159
- ) {
160
- return { ok: false, code: "privilege_boundary_failed" };
161
- }
162
- if (
163
- !Number.isSafeInteger(manifest.worker?.uid) ||
164
- manifest.worker.uid < 1 ||
165
- !Number.isSafeInteger(manifest.runtime?.uid) ||
166
- manifest.runtime.uid < 1 ||
167
- manifest.worker.uid === manifest.runtime.uid
168
- ) {
169
- return { ok: false, code: "identity_boundary_failed" };
170
- }
171
- const profileBase = `/var/lib/sellable-agents/${manifest.profileId}/`;
172
- if (
173
- !Array.isArray(manifest.mounts) ||
174
- manifest.mounts.length !== 2 ||
175
- manifest.mounts.some(
176
- (mount) =>
177
- mount.profileId !== manifest.profileId ||
178
- typeof mount.source !== "string" ||
179
- !mount.source.startsWith(profileBase) ||
180
- !["/control", "/profile"].includes(mount.target) ||
181
- !["worker", "runtime"].includes(mount.owner) ||
182
- mount.source.includes("docker.sock")
183
- ) ||
184
- manifest.mounts.find((mount) => mount.target === "/control")?.owner !==
185
- "worker" ||
186
- manifest.mounts.find((mount) => mount.target === "/profile")?.owner !==
187
- "runtime"
188
- ) {
189
- return { ok: false, code: "mount_boundary_failed" };
190
- }
191
- if (
192
- !Array.isArray(manifest.credentials) ||
193
- !manifest.credentials.every(
194
- (reference) =>
195
- validPrivateReference(reference, "secret") &&
196
- typeof reference.target === "string" &&
197
- reference.target.startsWith("/run/credentials/") &&
198
- reference.owner === "worker"
199
- ) ||
200
- !validPrivateReference(manifest.oauthPrivateReference, "oauth-private") ||
201
- manifest.oauthPrivateReference.owner !== "worker"
202
- ) {
203
- return { ok: false, code: "secret_boundary_failed" };
204
- }
205
- if (
206
- manifest.oauth?.privateExchangeOwner !== "worker" ||
207
- manifest.oauth?.healthStatus !== "NOT_PROBED" ||
208
- manifest.oauth?.auditStatus !== "REQUIRED" ||
209
- manifest.oauth?.broaderRolloutBlocked !== true
210
- ) {
211
- return { ok: false, code: "oauth_readiness_boundary_failed" };
212
- }
213
- if (
214
- manifest.authority?.database !== false ||
215
- manifest.authority?.migrations !== false ||
216
- manifest.authority?.hostControl !== false
217
- ) {
218
- return { ok: false, code: "authority_boundary_failed" };
219
- }
220
- if (
221
- manifest.resources?.pids !== 128 ||
222
- manifest.resources?.memoryMb !== 1024 ||
223
- manifest.resources?.cpuQuota !== 1 ||
224
- JSON.stringify(manifest.egress) !== JSON.stringify(EXACT_EGRESS)
225
- ) {
226
- return { ok: false, code: "resource_or_egress_boundary_failed" };
227
- }
228
- if (
229
- /xox[bap]-|authorizationCode|authorization_code|accessToken|refreshToken/.test(
230
- JSON.stringify(manifest)
231
- )
232
- ) {
233
- return { ok: false, code: "raw_secret_material" };
234
- }
235
- return { ok: true, code: "contained" };
236
- }
237
-
238
- export function isContainmentEgressAllowed(manifest, value) {
239
- if (!validateContainmentContract(manifest).ok) return false;
240
- try {
241
- const url = new URL(value);
242
- if (url.protocol !== "https:" || url.username || url.password) return false;
243
- return manifest.egress.includes(url.origin);
244
- } catch {
245
- return false;
246
- }
247
- }
248
-
249
- function containedRelative(root, target) {
250
- const rel = relative(root, target);
251
- return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
252
- }
253
-
254
- export function materializeContainmentProfile(manifest, { root }) {
255
- if (!validateContainmentContract(manifest).ok) {
256
- return { ok: false, code: "invalid_manifest" };
257
- }
258
- try {
259
- if (!isAbsolute(root) || !existsSync(root) || lstatSync(root).isSymbolicLink()) {
260
- return { ok: false, code: "invalid_root" };
261
- }
262
- const realRoot = realpathSync(root);
263
- const profileRoot = join(realRoot, manifest.profileId);
264
- if (!containedRelative(realRoot, profileRoot)) {
265
- return { ok: false, code: "path_confinement_failed" };
266
- }
267
- mkdirSync(join(profileRoot, "control"), { recursive: true, mode: 0o700 });
268
- mkdirSync(join(profileRoot, "profile"), { recursive: true, mode: 0o750 });
269
- mkdirSync(join(profileRoot, "credentials"), { recursive: true, mode: 0o700 });
270
- writeFileSync(join(profileRoot, "profile", "probe.txt"), "sellable-agent-contained\n", {
271
- mode: 0o444,
272
- });
273
- chmodSync(join(profileRoot, "profile", "probe.txt"), 0o444);
274
- writeFileSync(
275
- join(profileRoot, "containment.json"),
276
- `${JSON.stringify(manifest, null, 2)}\n`,
277
- { mode: 0o600 }
278
- );
279
- return { ok: true, profileRoot };
280
- } catch {
281
- return { ok: false, code: "materialization_failed" };
282
- }
283
- }
284
-
285
- export function buildContainerContainmentProbeSpec(
286
- manifest,
287
- { root, runtimeUid, runtimeGid }
288
- ) {
289
- const validation = validateContainmentContract(manifest);
290
- if (!validation.ok) return { ok: false, code: validation.code };
291
- try {
292
- if (
293
- !Number.isSafeInteger(runtimeUid) ||
294
- runtimeUid < 1 ||
295
- !Number.isSafeInteger(runtimeGid) ||
296
- runtimeGid < 1 ||
297
- !isAbsolute(root) ||
298
- String(root).includes(",") ||
299
- String(root).includes("\n")
300
- ) {
301
- return { ok: false, code: "invalid_probe_runtime" };
302
- }
303
- const realRoot = realpathSync(root);
304
- const materializedRoot = realpathSync(join(realRoot, manifest.profileId));
305
- const profileData = realpathSync(join(materializedRoot, "profile"));
306
- if (
307
- !containedRelative(realRoot, materializedRoot) ||
308
- !containedRelative(materializedRoot, profileData) ||
309
- lstatSync(profileData).isSymbolicLink() ||
310
- readFileSync(join(profileData, "probe.txt"), "utf8") !==
311
- "sellable-agent-contained\n"
312
- ) {
313
- return { ok: false, code: "invalid_materialized_profile" };
314
- }
315
- const script = [
316
- `test "$(id -u)" = "${runtimeUid}"`,
317
- 'test "$(cat /profile/probe.txt)" = "sellable-agent-contained"',
318
- 'test ! -r /control/probe.txt',
319
- 'test ! -r /run/credentials/service-credential',
320
- 'test ! -r /run/credentials/oauth-private',
321
- 'test ! -r /siblings/profile-2/probe.txt',
322
- 'test ! -r /host/probe.txt',
323
- 'test ! -S /var/run/docker.sock',
324
- 'if touch /sellable-root-write-probe 2>/dev/null; then exit 31; fi',
325
- "grep -Eq '^NoNewPrivs:[[:space:]]+1$' /proc/self/status",
326
- "grep -Eq '^CapEff:[[:space:]]+0+$' /proc/self/status",
327
- "test ! -e /sys/class/net/eth0",
328
- `printf '%s\\n' '${JSON.stringify({
329
- ok: true,
330
- checks: Object.fromEntries(CONTAINER_CHECK_KEYS.map((key) => [key, true])),
331
- })}'`,
332
- ].join("\n");
333
- const args = [
334
- "run",
335
- "--rm",
336
- "--label",
337
- "sellable.containment.probe=1",
338
- "--pull",
339
- "never",
340
- "--network",
341
- "none",
342
- "--read-only",
343
- "--cap-drop",
344
- "ALL",
345
- "--security-opt",
346
- "no-new-privileges",
347
- "--pids-limit",
348
- "128",
349
- "--memory",
350
- "1024m",
351
- "--cpus",
352
- "1",
353
- "--user",
354
- `${runtimeUid}:${runtimeGid}`,
355
- "--tmpfs",
356
- "/tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777",
357
- "--mount",
358
- `type=bind,src=${profileData},dst=/profile,readonly`,
359
- PINNED_CONTAINMENT_IMAGE,
360
- "sh",
361
- "-ceu",
362
- script,
363
- ];
364
- return {
365
- ok: true,
366
- command: "docker",
367
- image: PINNED_CONTAINMENT_IMAGE,
368
- args,
369
- shell: false,
370
- timeoutMs: 30_000,
371
- maxOutputBytes: 8 * 1024,
372
- };
373
- } catch {
374
- return { ok: false, code: "invalid_materialized_profile" };
375
- }
376
- }
377
-
378
- export async function runContainerContainmentProbe(manifest, runtime) {
379
- const spec = buildContainerContainmentProbeSpec(manifest, runtime);
380
- if (!spec.ok) return { ...spec, checks: {} };
381
- const output = await runBoundedChildProcess(spec);
382
- if (
383
- output.exitCode !== 0 ||
384
- output.timedOut ||
385
- output.outputLimitExceeded ||
386
- output.terminated
387
- ) {
388
- return { ok: false, code: "container_probe_failed", checks: {} };
389
- }
390
- try {
391
- const parsed = JSON.parse(output.stdout);
392
- if (
393
- !parsed ||
394
- parsed.ok !== true ||
395
- Object.keys(parsed).sort().join("\0") !== ["checks", "ok"].sort().join("\0") ||
396
- Object.keys(parsed.checks).sort().join("\0") !==
397
- [...CONTAINER_CHECK_KEYS].sort().join("\0") ||
398
- CONTAINER_CHECK_KEYS.some((key) => parsed.checks[key] !== true)
399
- ) {
400
- throw new Error("invalid probe result");
401
- }
402
- return { ok: true, code: "contained", checks: parsed.checks };
403
- } catch {
404
- return { ok: false, code: "container_probe_failed", checks: {} };
405
- }
406
- }
407
-
408
- function identityCanAccess(manifest, identity, target) {
409
- if (identity !== "worker" && identity !== "runtime") return false;
410
- if (target === "/") return false;
411
- if (target === "/var/run/docker.sock") return false;
412
- if (target.startsWith("/run/credentials/")) return identity === "worker";
413
- const mount = manifest.mounts.find(
414
- (candidate) => target === candidate.target || target.startsWith(`${candidate.target}/`)
415
- );
416
- return Boolean(mount && mount.owner === identity);
417
- }
418
-
419
- export function probeContainmentProfile(manifest, { root }) {
420
- const validation = validateContainmentContract(manifest);
421
- if (!validation.ok) return { ok: false, code: validation.code, checks: {} };
422
- try {
423
- const realRoot = realpathSync(root);
424
- const profileRoot = realpathSync(join(realRoot, manifest.profileId));
425
- const materializedManifest = JSON.parse(
426
- readFileSync(join(profileRoot, "containment.json"), "utf8")
427
- );
428
- if (JSON.stringify(materializedManifest) !== JSON.stringify(manifest)) {
429
- return { ok: false, code: "materialized_manifest_mismatch", checks: {} };
430
- }
431
- const checks = {
432
- runtimeProfile: identityCanAccess(manifest, "runtime", "/profile"),
433
- runtimeControlDenied: !identityCanAccess(manifest, "runtime", "/control"),
434
- runtimeCredentialDenied: !identityCanAccess(
435
- manifest,
436
- "runtime",
437
- "/run/credentials/service-credential"
438
- ),
439
- runtimeOauthDenied: !identityCanAccess(
440
- manifest,
441
- "runtime",
442
- "/run/credentials/oauth-private"
443
- ),
444
- siblingDenied: !identityCanAccess(manifest, "runtime", "/siblings/profile-2"),
445
- rootDenied: !identityCanAccess(manifest, "runtime", "/"),
446
- dockerDenied: !identityCanAccess(manifest, "runtime", "/var/run/docker.sock"),
447
- databaseDenied: manifest.authority.database === false,
448
- migrationDenied: manifest.authority.migrations === false,
449
- sellableAllowed: isContainmentEgressAllowed(
450
- manifest,
451
- "https://app.sellable.dev/api/v3/workspaces"
452
- ),
453
- slackAllowed: isContainmentEgressAllowed(
454
- manifest,
455
- "https://slack.com/api/auth.test"
456
- ),
457
- modelAllowed: isContainmentEgressAllowed(
458
- manifest,
459
- "https://api.openai.com/v1/responses"
460
- ),
461
- extraEndpointDenied: !isContainmentEgressAllowed(
462
- manifest,
463
- "https://evil.example/collect"
464
- ),
465
- };
466
- return Object.values(checks).every(Boolean)
467
- ? { ok: true, code: "contained", checks }
468
- : { ok: false, code: "probe_failed", checks };
469
- } catch {
470
- return { ok: false, code: "probe_failed", checks: {} };
471
- }
472
- }