threadwire 0.1.6 → 0.1.8

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.
@@ -0,0 +1,409 @@
1
+ // @ts-nocheck
2
+ /* eslint-disable jsdoc/require-jsdoc */
3
+
4
+ import {createHmac, randomBytes, timingSafeEqual} from "node:crypto"
5
+ import {constants} from "node:fs"
6
+ import {chmod, lstat, mkdir, open, readFile, readdir, rename, unlink} from "node:fs/promises"
7
+ import {dirname, join} from "node:path"
8
+
9
+ const OWNER = "threadwire-isolated-runtime"
10
+
11
+ /**
12
+ * Authenticated registry of both public session aliases and private volume
13
+ * ownership. Lineage ownership deliberately outlives an expired alias until
14
+ * Docker confirms that the volume was deleted.
15
+ * @param {{file: string, key: string, namespace: string, now?: () => number, random?: (size: number) => Buffer}} options
16
+ */
17
+ export async function openStateRegistry(options) {
18
+ if (!/^\/.+/u.test(options.file) || options.key.length < 32 || !/^[A-Za-z0-9_.-]{1,64}$/u.test(options.namespace)) {
19
+ throw new Error("Invalid isolated state configuration")
20
+ }
21
+ const now = options.now ?? Date.now
22
+ const random = options.random ?? randomBytes
23
+ const durabilityObserver = options.durabilityObserver
24
+ await mkdir(dirname(options.file), {recursive: true, mode: 0o700})
25
+ await chmod(dirname(options.file), 0o700)
26
+ await requirePrivateDirectory(dirname(options.file))
27
+ const sessions = new Map()
28
+ const lineages = new Map()
29
+ const runs = new Map()
30
+ let serial = Promise.resolve()
31
+ try {
32
+ const document = JSON.parse(await readFile(options.file, "utf8"))
33
+ const payload = JSON.stringify(document.payload)
34
+ if (![1, 2, 3].includes(document.version) || typeof document.mac !== "string" || !equalMac(document.mac, mac(options.key, payload))) throw new Error()
35
+ if (!Array.isArray(document.payload?.sessions)) throw new Error()
36
+ for (const entry of document.payload.sessions) {
37
+ if (!validSession(entry)) throw new Error()
38
+ sessions.set(entry.sessionId, without(entry, "sessionId"))
39
+ lineages.set(entry.lineage, lineageFromSession(options.namespace, entry))
40
+ }
41
+ if (document.version === 2) {
42
+ if (!Array.isArray(document.payload.lineages)) throw new Error()
43
+ for (const entry of document.payload.lineages) {
44
+ if (!validLineage(entry, options.namespace)) throw new Error()
45
+ lineages.set(entry.lineage, entry)
46
+ }
47
+ }
48
+ if (document.version === 3) {
49
+ if (!Array.isArray(document.payload.lineages) || !Array.isArray(document.payload.runs)) throw new Error()
50
+ for (const entry of document.payload.lineages) {
51
+ if (!validLineage(entry, options.namespace)) throw new Error()
52
+ lineages.set(entry.lineage, entry)
53
+ }
54
+ for (const entry of document.payload.runs) {
55
+ canonicalRunIdentity(entry)
56
+ runs.set(entry.run, entry)
57
+ }
58
+ }
59
+ } catch (error) {
60
+ if (/** @type {NodeJS.ErrnoException} */ (error).code !== "ENOENT") throw new Error("Isolated state registry authentication failed", {cause: error})
61
+ }
62
+ await removeAuthenticatedTemps(dirname(options.file), options.key, options.namespace)
63
+
64
+ const persist = async (sessionState = sessions, lineageState = lineages, runState = runs, signal) => {
65
+ throwIfAborted(signal)
66
+ const payload = {
67
+ sessions: [...sessionState.entries()].map(([sessionId, entry]) => ({sessionId, ...entry})),
68
+ lineages: [...lineageState.values()],
69
+ runs: [...runState.values()]
70
+ }
71
+ const serializedPayload = JSON.stringify(payload)
72
+ const document = JSON.stringify({version: 3, payload, mac: mac(options.key, serializedPayload)})
73
+ const temporary = join(dirname(options.file), `.sessions-${random(12).toString("hex")}.tmp`)
74
+ let handle
75
+ try {
76
+ handle = await open(temporary, "wx", 0o600)
77
+ throwIfAborted(signal)
78
+ await handle.writeFile(document)
79
+ await durabilityObserver?.("before-file-sync")
80
+ await handle.sync()
81
+ await durabilityObserver?.("after-file-sync")
82
+ await handle.close()
83
+ handle = undefined
84
+ throwIfAborted(signal)
85
+ await durabilityObserver?.("before-rename")
86
+ await rename(temporary, options.file)
87
+ await durabilityObserver?.("after-rename")
88
+ const directory = await open(dirname(options.file), "r")
89
+ try {
90
+ await durabilityObserver?.("before-directory-sync")
91
+ await directory.sync()
92
+ await durabilityObserver?.("after-directory-sync")
93
+ } finally {
94
+ await directory.close()
95
+ }
96
+ } catch (error) {
97
+ await handle?.close().catch(() => {})
98
+ throw error
99
+ }
100
+ }
101
+ const transact = (operation) => {
102
+ const result = serial.then(operation)
103
+ serial = result.catch(() => {})
104
+ return result
105
+ }
106
+ const allocationRecord = (allocation) => ({
107
+ task: allocation.task,
108
+ lineage: allocation.lineage,
109
+ volume: allocation.volume,
110
+ labels: allocation.labels
111
+ })
112
+
113
+ return {
114
+ namespace: options.namespace,
115
+ allocate(task) {
116
+ const lineage = random(24).toString("hex")
117
+ return {
118
+ task,
119
+ lineage,
120
+ volume: `threadwire-state-${options.namespace}-${lineage}`,
121
+ labels: stateLabels(options.namespace, task, lineage)
122
+ }
123
+ },
124
+ sealRun(identity) {
125
+ return mac(options.key, canonicalRunIdentity(identity))
126
+ },
127
+ ownsRun(identity, seal) {
128
+ const lineage = lineages.get(identity.lineage)
129
+ if (!lineage || lineage.volume !== identity.volume || lineage.labels["org.threadwire.task"] !== identity.task) return false
130
+ return typeof seal === "string" && equalMac(seal, mac(options.key, canonicalRunIdentity(identity)))
131
+ },
132
+ async trackRun(identity, signal) {
133
+ await transact(async () => {
134
+ throwIfAborted(signal)
135
+ canonicalRunIdentity(identity)
136
+ if (!lineages.has(identity.lineage)) throw new Error("Codex run lineage is unavailable")
137
+ const existing = runs.get(identity.run)
138
+ if (existing && canonicalRunIdentity(existing) !== canonicalRunIdentity(identity)) throw new Error("Codex run ownership collision")
139
+ const nextRuns = new Map(runs)
140
+ nextRuns.set(identity.run, identity)
141
+ await persist(sessions, lineages, nextRuns, signal)
142
+ replace(runs, nextRuns)
143
+ throwIfAborted(signal)
144
+ })
145
+ },
146
+ async completeRun(run, signal) {
147
+ await transact(async () => {
148
+ throwIfAborted(signal)
149
+ if (!runs.has(run)) return
150
+ const nextRuns = new Map(runs)
151
+ nextRuns.delete(run)
152
+ await persist(sessions, lineages, nextRuns, signal)
153
+ replace(runs, nextRuns)
154
+ throwIfAborted(signal)
155
+ })
156
+ },
157
+ runRecords() {
158
+ return [...runs.values()].map((entry) => ({...entry}))
159
+ },
160
+ lookup(sessionId) {
161
+ const entry = sessions.get(sessionId)
162
+ if (!entry || entry.expiresAt <= now()) return undefined
163
+ return {...entry, labels: stateLabels(options.namespace, entry.task, entry.lineage)}
164
+ },
165
+ async track(allocation, signal) {
166
+ await transact(async () => {
167
+ throwIfAborted(signal)
168
+ const existing = lineages.get(allocation.lineage)
169
+ const candidate = allocationRecord(allocation)
170
+ if (existing && JSON.stringify(existing) !== JSON.stringify(candidate)) throw new Error("Codex lineage ownership collision")
171
+ const nextLineages = new Map(lineages)
172
+ nextLineages.set(allocation.lineage, candidate)
173
+ await persist(sessions, nextLineages, runs, signal)
174
+ replace(lineages, nextLineages)
175
+ throwIfAborted(signal)
176
+ })
177
+ },
178
+ async register(sessionIds, allocation, ttlMs, signal) {
179
+ await transact(async () => {
180
+ throwIfAborted(signal)
181
+ const candidate = allocationRecord(allocation)
182
+ const owned = lineages.get(allocation.lineage)
183
+ if (owned && JSON.stringify(owned) !== JSON.stringify(candidate)) throw new Error("Codex lineage ownership collision")
184
+ const nextSessions = new Map(sessions)
185
+ const nextLineages = new Map(lineages)
186
+ for (const sessionId of sessionIds) {
187
+ const existing = nextSessions.get(sessionId)
188
+ if (existing && (existing.task !== allocation.task || existing.lineage !== allocation.lineage || existing.volume !== allocation.volume)) {
189
+ throw new Error("Codex session lineage collision")
190
+ }
191
+ nextSessions.set(sessionId, {
192
+ task: allocation.task, lineage: allocation.lineage, volume: allocation.volume, expiresAt: now() + ttlMs
193
+ })
194
+ }
195
+ nextLineages.set(allocation.lineage, candidate)
196
+ await persist(nextSessions, nextLineages, runs, signal)
197
+ replace(sessions, nextSessions)
198
+ replace(lineages, nextLineages)
199
+ throwIfAborted(signal)
200
+ })
201
+ },
202
+ /**
203
+ * Sweeps aliases, adopts only exactly labelled namespace-owned orphan
204
+ * volumes, and retries failed deletions without dropping ownership.
205
+ */
206
+ async collect(docker, activeLineages = new Set(), requestOptions = {}) {
207
+ throwIfAborted(requestOptions.signal)
208
+ const listed = await docker.listVolumes({
209
+ "org.threadwire.owner": OWNER,
210
+ "org.threadwire.namespace": options.namespace
211
+ }, requestOptions)
212
+ const plan = await transact(async () => {
213
+ throwIfAborted(requestOptions.signal)
214
+ const nextSessions = new Map(sessions)
215
+ const nextLineages = new Map(lineages)
216
+ for (const [sessionId, entry] of nextSessions) if (entry.expiresAt <= now()) nextSessions.delete(sessionId)
217
+ for (const volume of Array.isArray(listed?.Volumes) ? listed.Volumes : []) {
218
+ const orphan = ownedVolumeRecord(volume, options.namespace)
219
+ if (orphan && !nextLineages.has(orphan.lineage)) nextLineages.set(orphan.lineage, orphan)
220
+ }
221
+ // Persist removals and adopted cleanup evidence before touching Docker.
222
+ await persist(nextSessions, nextLineages, runs, requestOptions.signal)
223
+ replace(sessions, nextSessions)
224
+ replace(lineages, nextLineages)
225
+ throwIfAborted(requestOptions.signal)
226
+ const live = new Set([...nextSessions.values()].map((entry) => entry.lineage))
227
+ return [...nextLineages].filter(([lineage]) => !live.has(lineage) && !activeLineages.has(lineage))
228
+ })
229
+ const removedCandidates = []
230
+ for (const [lineage, entry] of plan) {
231
+ throwIfAborted(requestOptions.signal)
232
+ try {
233
+ const volume = await docker.inspectVolume(entry.volume, requestOptions)
234
+ validateStateVolume(volume, entry.volume, entry.labels)
235
+ if (activeLineages.has(lineage)) continue
236
+ await docker.removeVolume(entry.volume, requestOptions)
237
+ removedCandidates.push([lineage, entry])
238
+ } catch (error) {
239
+ if (isDockerMissing(error)) removedCandidates.push([lineage, entry])
240
+ }
241
+ }
242
+ return transact(async () => {
243
+ throwIfAborted(requestOptions.signal)
244
+ const nextLineages = new Map(lineages)
245
+ let removed = 0
246
+ const live = new Set([...sessions.values()].filter((entry) => entry.expiresAt > now()).map((entry) => entry.lineage))
247
+ for (const [lineage, expected] of removedCandidates) {
248
+ if (live.has(lineage) || activeLineages.has(lineage)) continue
249
+ if (JSON.stringify(nextLineages.get(lineage)) !== JSON.stringify(expected)) continue
250
+ nextLineages.delete(lineage)
251
+ removed++
252
+ }
253
+ await persist(sessions, nextLineages, runs, requestOptions.signal)
254
+ replace(lineages, nextLineages)
255
+ throwIfAborted(requestOptions.signal)
256
+ return {sessions: sessions.size, lineages: lineages.size, removed}
257
+ })
258
+ },
259
+ counts() {
260
+ return {sessions: sessions.size, lineages: lineages.size, runs: runs.size}
261
+ }
262
+ }
263
+ }
264
+
265
+ function throwIfAborted(signal) {
266
+ if (signal?.aborted) throw signal.reason instanceof Error ? signal.reason : new Error("Isolated state operation aborted")
267
+ }
268
+
269
+ export function stateLabels(namespace, task, lineage) {
270
+ return {
271
+ "org.threadwire.owner": OWNER,
272
+ "org.threadwire.namespace": namespace,
273
+ "org.threadwire.task": createHmac("sha256", namespace).update(task).digest("hex"),
274
+ "org.threadwire.lineage": lineage
275
+ }
276
+ }
277
+
278
+ export function validateStateVolume(volume, name, labels) {
279
+ if (!record(volume) || volume.Name !== name || !record(volume.Labels)) throw new Error("Isolated state volume collision")
280
+ const actual = volume.Labels
281
+ if (Object.keys(actual).length !== Object.keys(labels).length) throw new Error("Isolated state volume collision")
282
+ for (const [key, value] of Object.entries(labels)) if (actual[key] !== value) throw new Error("Isolated state volume collision")
283
+ }
284
+
285
+ function ownedVolumeRecord(volume, namespace) {
286
+ if (!record(volume) || !record(volume.Labels)) return undefined
287
+ const labels = volume.Labels
288
+ const lineage = labels["org.threadwire.lineage"]
289
+ const name = volume.Name
290
+ if (Object.keys(labels).length !== 4
291
+ || labels["org.threadwire.owner"] !== OWNER
292
+ || labels["org.threadwire.namespace"] !== namespace
293
+ || !/^[0-9a-f]{64}$/u.test(labels["org.threadwire.task"])
294
+ || !/^[0-9a-f]{48}$/u.test(lineage)
295
+ || name !== `threadwire-state-${namespace}-${lineage}`) return undefined
296
+ return {task: "", lineage, volume: name, labels}
297
+ }
298
+
299
+ function lineageFromSession(namespace, entry) {
300
+ return {
301
+ task: entry.task, lineage: entry.lineage, volume: entry.volume,
302
+ labels: stateLabels(namespace, entry.task, entry.lineage)
303
+ }
304
+ }
305
+
306
+ function validSession(value) {
307
+ return record(value) && typeof value.sessionId === "string" && typeof value.task === "string"
308
+ && /^[0-9a-f]{48}$/u.test(value.lineage)
309
+ && /^threadwire-state-[A-Za-z0-9_.-]+-[0-9a-f]{48}$/u.test(value.volume)
310
+ && Number.isSafeInteger(value.expiresAt)
311
+ }
312
+
313
+ function validLineage(value, namespace) {
314
+ return record(value) && typeof value.task === "string" && /^[0-9a-f]{48}$/u.test(value.lineage)
315
+ && value.volume === `threadwire-state-${namespace}-${value.lineage}` && record(value.labels)
316
+ && Object.keys(value.labels).length === 4
317
+ && value.labels["org.threadwire.owner"] === OWNER
318
+ && value.labels["org.threadwire.namespace"] === namespace
319
+ && value.labels["org.threadwire.lineage"] === value.lineage
320
+ && /^[0-9a-f]{64}$/u.test(value.labels["org.threadwire.task"])
321
+ }
322
+
323
+ function isDockerMissing(error) {
324
+ return error instanceof Error && /\(404\)/u.test(error.message)
325
+ }
326
+
327
+ function without(value, key) {
328
+ const copy = {...value}
329
+ delete copy[key]
330
+ return copy
331
+ }
332
+
333
+ function replace(target, source) {
334
+ target.clear()
335
+ for (const [key, value] of source) target.set(key, value)
336
+ }
337
+
338
+ async function removeAuthenticatedTemps(directory, key, namespace) {
339
+ const names = await readdir(directory)
340
+ let removed = false
341
+ for (const name of names) {
342
+ if (!/^\.sessions-[0-9a-f]{24}\.tmp$/u.test(name)) continue
343
+ let handle
344
+ try {
345
+ const path = join(directory, name)
346
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW)
347
+ const before = await handle.stat({bigint: true})
348
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n
349
+ || before.uid !== BigInt(process.getuid?.() ?? -1)
350
+ || before.size < 1n || before.size > 4_194_304n || (before.mode & 0o077n) !== 0n) continue
351
+ const document = JSON.parse(await handle.readFile("utf8"))
352
+ if (!authenticatedDocument(document, key, namespace)) continue
353
+ const after = await lstat(path, {bigint: true})
354
+ if (after.dev !== before.dev || after.ino !== before.ino || after.isSymbolicLink()) continue
355
+ await unlink(path)
356
+ removed = true
357
+ } catch {
358
+ // Malformed, unreadable, and foreign temp files are deliberately left untouched.
359
+ } finally {
360
+ await handle?.close().catch(() => {})
361
+ }
362
+ }
363
+ if (!removed) return
364
+ const handle = await open(directory, "r")
365
+ try { await handle.sync() } finally { await handle.close() }
366
+ }
367
+
368
+ function authenticatedDocument(document, key, namespace) {
369
+ if (!record(document) || ![1, 2, 3].includes(document.version) || typeof document.mac !== "string"
370
+ || !record(document.payload) || !Array.isArray(document.payload.sessions)) return false
371
+ const payload = JSON.stringify(document.payload)
372
+ if (!equalMac(document.mac, mac(key, payload))) return false
373
+ if (!document.payload.sessions.every(validSession)) return false
374
+ if (document.version === 1) return true
375
+ if (!Array.isArray(document.payload.lineages)
376
+ || !document.payload.lineages.every((entry) => validLineage(entry, namespace))) return false
377
+ return document.version === 2 || (Array.isArray(document.payload.runs)
378
+ && document.payload.runs.every((entry) => {
379
+ try { canonicalRunIdentity(entry); return true } catch { return false }
380
+ }))
381
+ }
382
+
383
+ function canonicalRunIdentity(identity) {
384
+ const keys = [
385
+ "namespace", "run", "lineage", "task", "volume", "network", "container",
386
+ "image", "worktreeType", "worktreeSource", "worktreeSubpath"
387
+ ]
388
+ if (!record(identity) || Object.keys(identity).length !== keys.length
389
+ || keys.some((key) => typeof identity[key] !== "string")) throw new Error("Invalid isolated run identity")
390
+ return JSON.stringify(Object.fromEntries(keys.map((key) => [key, identity[key]])))
391
+ }
392
+
393
+ async function requirePrivateDirectory(path) {
394
+ const stats = await lstat(path)
395
+ if (!stats.isDirectory() || stats.isSymbolicLink() || (stats.mode & 0o077) !== 0) throw new Error("Invalid isolated state directory")
396
+ }
397
+
398
+ function mac(key, payload) {
399
+ return createHmac("sha256", key).update(payload).digest("hex")
400
+ }
401
+
402
+ function equalMac(left, right) {
403
+ if (!/^[0-9a-f]{64}$/u.test(left)) return false
404
+ return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"))
405
+ }
406
+
407
+ function record(value) {
408
+ return typeof value === "object" && value !== null && !Array.isArray(value)
409
+ }
@@ -0,0 +1,123 @@
1
+ // @ts-check
2
+
3
+ import {isAbsolute, normalize} from "node:path"
4
+
5
+ const DIGEST_IMAGE = /^(?:[^@\s]+@)?sha256:[0-9a-f]{64}$/u
6
+ const SAFE_VALUE = /^[^\0\r\n]+$/u
7
+
8
+ /**
9
+ * @typedef {{
10
+ * image: string,
11
+ * worktree: string,
12
+ * networkName: string,
13
+ * brokerToken: string,
14
+ * prompt: string,
15
+ * resumeSession?: string,
16
+ * brokerUrl?: string
17
+ * worktreeVolume?: {name: string, subpath: string}
18
+ * workingDirectory: string,
19
+ * providerArguments: string[],
20
+ * stateVolume: string,
21
+ * worktreeInode: string,
22
+ * cwdInode: string
23
+ * lineage?: string
24
+ * namespace?: string
25
+ * runId?: string
26
+ * taskHash?: string
27
+ * runSeal?: string
28
+ * }} WorkerSpecOptions
29
+ */
30
+
31
+ /** @param {WorkerSpecOptions} options */
32
+ export function buildWorkerContainerSpec(options) {
33
+ if (!DIGEST_IMAGE.test(options.image)) throw new Error("Worker image must use an immutable digest")
34
+ if (!isAbsolute(options.worktree) || normalize(options.worktree) !== options.worktree || options.worktree === "/") {
35
+ throw new Error("Worker worktree must be a normalized non-root absolute path")
36
+ }
37
+ for (const [name, value] of /** @type {[string, string][]} */ ([["network", options.networkName], ["capability", options.brokerToken], ["prompt", options.prompt]])) {
38
+ if (!SAFE_VALUE.test(value)) throw new Error(`Worker ${name} is invalid`)
39
+ }
40
+ if (options.resumeSession !== undefined && !SAFE_VALUE.test(options.resumeSession)) throw new Error("Worker resume session is invalid")
41
+ if (!isAbsolute(options.workingDirectory) || !withinWorktree(options.workingDirectory)) throw new Error("Worker cwd is invalid")
42
+ const environment = [
43
+ "HOME=/home/worker",
44
+ "CODEX_HOME=/home/worker/.codex",
45
+ "TMPDIR=/tmp",
46
+ `OPENAI_BASE_URL=${options.brokerUrl ?? "http://model-broker:8789/v1"}`,
47
+ `OPENAI_API_KEY=${options.brokerToken}`,
48
+ `CODEX_API_KEY=${options.brokerToken}`,
49
+ `THREADWIRE_PROMPT=${options.prompt}`,
50
+ `THREADWIRE_CODEX_ARGUMENTS=${JSON.stringify(options.providerArguments)}`,
51
+ `THREADWIRE_WORKTREE_INODE=${options.worktreeInode}`,
52
+ `THREADWIRE_CWD_INODE=${options.cwdInode}`,
53
+ ...(options.resumeSession === undefined ? [] : [`THREADWIRE_RESUME_SESSION=${options.resumeSession}`])
54
+ ]
55
+ const worktreeMount = options.worktreeVolume === undefined
56
+ ? {
57
+ Type: "bind",
58
+ Source: options.worktree,
59
+ Target: "/worktree",
60
+ ReadOnly: false,
61
+ BindOptions: {Propagation: "rprivate"}
62
+ }
63
+ : {
64
+ Type: "volume",
65
+ Source: options.worktreeVolume.name,
66
+ Target: "/worktree",
67
+ ReadOnly: false,
68
+ VolumeOptions: {Subpath: options.worktreeVolume.subpath}
69
+ }
70
+ return {
71
+ Image: options.image,
72
+ Labels: {
73
+ "org.threadwire.owner": "isolated-runtime",
74
+ ...(options.lineage === undefined ? {} : {
75
+ "org.threadwire.namespace": options.namespace,
76
+ "org.threadwire.run": options.runId,
77
+ "org.threadwire.lineage": options.lineage,
78
+ "org.threadwire.task": options.taskHash,
79
+ "org.threadwire.network": options.networkName,
80
+ "org.threadwire.state-volume": options.stateVolume,
81
+ "org.threadwire.run-seal": options.runSeal
82
+ })
83
+ },
84
+ Entrypoint: ["/usr/local/libexec/threadwire/worker-entrypoint"],
85
+ Env: environment,
86
+ WorkingDir: options.workingDirectory,
87
+ User: "10002:10002",
88
+ NetworkDisabled: false,
89
+ HostConfig: {
90
+ AutoRemove: false,
91
+ Mounts: [
92
+ worktreeMount,
93
+ {Type: "volume", Source: options.stateVolume, Target: "/home/worker", ReadOnly: false}
94
+ ],
95
+ CapDrop: ["ALL"],
96
+ NetworkMode: options.networkName,
97
+ ReadonlyRootfs: true,
98
+ SecurityOpt: ["no-new-privileges"],
99
+ PidsLimit: 128,
100
+ Memory: 1_073_741_824,
101
+ NanoCpus: 2_000_000_000,
102
+ Tmpfs: {
103
+ "/tmp": "rw,noexec,nosuid,nodev,size=67108864,uid=10002,gid=10002",
104
+ "/run": "rw,noexec,nosuid,nodev,size=1048576,uid=10002,gid=10002"
105
+ },
106
+ Ulimits: [
107
+ {Name: "nofile", Soft: 1024, Hard: 1024},
108
+ {Name: "core", Soft: 0, Hard: 0},
109
+ {Name: "fsize", Soft: 1_073_741_824, Hard: 1_073_741_824}
110
+ ]
111
+ }
112
+ }
113
+ }
114
+
115
+ /** @param {string} path */
116
+ function withinWorktree(path) {
117
+ return path === "/worktree" || path.startsWith("/worktree/")
118
+ }
119
+
120
+ /** @param {string} value */
121
+ export function isDigestImage(value) {
122
+ return DIGEST_IMAGE.test(value)
123
+ }
@@ -0,0 +1,139 @@
1
+ // @ts-check
2
+
3
+ import {randomBytes} from "node:crypto"
4
+
5
+ const TOKEN_PATTERN = /^[A-Za-z0-9_-]{32,128}$/u
6
+ const NETWORK_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u
7
+ const RUN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u
8
+ const ALLOWED_HEADERS = new Set([
9
+ "authorization", "content-length", "content-type", "user-agent", "accept",
10
+ "accept-encoding", "accept-language", "sec-fetch-mode",
11
+ "x-stainless-arch", "x-stainless-lang", "x-stainless-os",
12
+ "x-stainless-package-version", "x-stainless-runtime",
13
+ "x-stainless-runtime-version", "x-stainless-retry-count", "x-stainless-timeout",
14
+ "x-stainless-async", "originator", "conversation_id", "session_id",
15
+ "version", "x-codex-turn-metadata"
16
+ ])
17
+
18
+ /** @param {{now?: () => number, random?: (bytes: number) => Buffer}} [options] */
19
+ export function createGrantStore(options = {}) {
20
+ const now = options.now ?? Date.now
21
+ const random = options.random ?? randomBytes
22
+ /** @type {Map<string, {runId: string, provider: "codex", networkId: string, model: string, expiresAt: number, active: boolean}>} */
23
+ const grants = new Map()
24
+ return {
25
+ /** @param {{runId: string, provider: "codex", networkId: string, model: string, ttlMs: number}} grant */
26
+ issue(grant) {
27
+ if (!RUN_PATTERN.test(grant.runId) || !NETWORK_PATTERN.test(grant.networkId)) throw new Error("Invalid broker grant")
28
+ if (grant.provider !== "codex") throw new Error("Invalid broker grant")
29
+ if (typeof grant.model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(grant.model)) throw new Error("Invalid broker grant")
30
+ if (!Number.isSafeInteger(grant.ttlMs) || grant.ttlMs < 1 || grant.ttlMs > 3_600_000) throw new Error("Invalid broker grant")
31
+ const token = random(32).toString("base64url")
32
+ grants.set(token, {...grant, expiresAt: now() + grant.ttlMs, active: false})
33
+ return token
34
+ },
35
+ /** @param {string} token */
36
+ activate(token) {
37
+ const grant = grants.get(token)
38
+ if (!grant) throw new Error("Broker grant unknown token")
39
+ if (grant.expiresAt < now()) {
40
+ grants.delete(token)
41
+ throw new Error("Broker grant expired")
42
+ }
43
+ if (grant.active) throw new Error("Broker grant already active")
44
+ grant.active = true
45
+ return {...grant}
46
+ },
47
+ /** @param {string} token @param {{provider: string, networkId: string, runId?: string}} context */
48
+ authorize(token, context) {
49
+ const grant = grants.get(token)
50
+ if (!grant) throw new Error("Broker grant unknown token")
51
+ if (grant.provider !== context.provider || grant.networkId !== context.networkId || (context.runId !== undefined && grant.runId !== context.runId)) throw new Error("Broker grant lineage denied")
52
+ if (!grant.active) throw new Error("Broker grant pending")
53
+ if (grant.expiresAt < now()) {
54
+ grants.delete(token)
55
+ throw new Error("Broker grant expired")
56
+ }
57
+ return {...grant}
58
+ },
59
+ /** @param {string} token */
60
+ revoke(token) {
61
+ grants.delete(token)
62
+ },
63
+ clear() {
64
+ grants.clear()
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * @param {{method?: string, path?: string, headers?: Record<string, string | string[] | undefined>}} request
71
+ */
72
+ export function validateBrokerRequest(request) {
73
+ if (request.method !== "POST" || request.path !== "/v1/responses") throw new Error("Broker request denied")
74
+ for (const header of Object.keys(request.headers ?? {})) {
75
+ if (!ALLOWED_HEADERS.has(header.toLowerCase())) throw new Error(`Broker request denied header: ${header.toLowerCase()}`)
76
+ }
77
+ return {method: "POST", path: "/v1/responses"}
78
+ }
79
+
80
+ const RESPONSE_KEYS = new Set([
81
+ "model", "input", "instructions", "stream", "tools", "tool_choice",
82
+ "parallel_tool_calls", "previous_response_id", "reasoning", "store",
83
+ "include", "metadata", "max_output_tokens", "temperature", "top_p", "truncation",
84
+ "prompt_cache_key"
85
+ ])
86
+
87
+ /** @param {Buffer} body @param {string} allowedModel */
88
+ export function validateResponsesBody(body, allowedModel) {
89
+ const text = body.toString("utf8")
90
+ if (topLevelKeyCount(text, "model") !== 1) throw new Error("Broker request denied")
91
+ let value
92
+ try {
93
+ value = JSON.parse(text)
94
+ } catch {
95
+ throw new Error("Broker request denied")
96
+ }
97
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Broker request denied")
98
+ const unexpected = Object.keys(value).find((key) => !RESPONSE_KEYS.has(key))
99
+ if (unexpected !== undefined) throw new Error(`Broker request denied field: ${unexpected}`)
100
+ if (value.model !== allowedModel || !("input" in value)) throw new Error("Broker request denied")
101
+ return value
102
+ }
103
+
104
+ /** @param {string} text @param {string} wanted */
105
+ function topLevelKeyCount(text, wanted) {
106
+ let depth = 0
107
+ let count = 0
108
+ let index = 0
109
+ while (index < text.length) {
110
+ const character = text[index]
111
+ if (character === "\"") {
112
+ let string = ""
113
+ index += 1
114
+ while (index < text.length && text[index] !== "\"") {
115
+ if (text[index] === "\\") {
116
+ index += 2
117
+ continue
118
+ }
119
+ string += text[index]
120
+ index += 1
121
+ }
122
+ if (depth === 1) {
123
+ let next = index + 1
124
+ while (/\s/u.test(text[next] ?? "")) next += 1
125
+ if (text[next] === ":" && string === wanted) count += 1
126
+ }
127
+ } else if (character === "{") depth += 1
128
+ else if (character === "}") depth -= 1
129
+ index += 1
130
+ }
131
+ return count
132
+ }
133
+
134
+ /** @param {string | undefined} authorization */
135
+ export function bearerToken(authorization) {
136
+ const match = /^Bearer ([A-Za-z0-9_-]{32,128})$/u.exec(authorization ?? "")
137
+ if (!match || !TOKEN_PATTERN.test(match[1] ?? "")) throw new Error("Broker grant format denied")
138
+ return /** @type {string} */ (match[1])
139
+ }