Skip to content

Commit 49a0f77

Browse files
fix(operator): add image rollout preflight
1 parent 7749384 commit 49a0f77

1 file changed

Lines changed: 226 additions & 0 deletions

File tree

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import fs from "node:fs"
2+
import os from "node:os"
3+
import path from "node:path"
4+
5+
const argv = process.argv.slice(2)
6+
7+
function takeValue(name) {
8+
const index = argv.indexOf(name)
9+
if (index === -1) return null
10+
if (!argv[index + 1]) {
11+
console.error(`Missing value for ${name}.`)
12+
process.exit(2)
13+
}
14+
const value = argv[index + 1]
15+
argv.splice(index, 2)
16+
return value
17+
}
18+
19+
function takeFlag(name) {
20+
const index = argv.indexOf(name)
21+
if (index === -1) return false
22+
argv.splice(index, 1)
23+
return true
24+
}
25+
26+
const jsonOutput = takeFlag("--json")
27+
const requestedRoot = takeValue("--sessions-root")
28+
if (argv.length > 0) {
29+
console.error(`Unknown arguments: ${argv.join(" ")}`)
30+
process.exit(2)
31+
}
32+
33+
function boundedInteger(value, fallback, minimum, maximum) {
34+
const parsed = Number.parseInt(value ?? "", 10)
35+
if (!Number.isFinite(parsed)) return fallback
36+
return Math.min(Math.max(parsed, minimum), maximum)
37+
}
38+
39+
const MiB = 1024 * 1024
40+
const warningBytes = boundedInteger(process.env.OPERATOR_CODEX_IMAGE_ROLLOUT_WARNING_MIB, 64, 1, 4096) * MiB
41+
const criticalBytes = boundedInteger(process.env.OPERATOR_CODEX_IMAGE_ROLLOUT_CRITICAL_MIB, 256, 2, 16384) * MiB
42+
const maxFiles = boundedInteger(process.env.OPERATOR_CODEX_IMAGE_ROLLOUT_MAX_FILES, 20000, 1, 200000)
43+
const maxCandidateScans = boundedInteger(process.env.OPERATOR_CODEX_IMAGE_ROLLOUT_MAX_CANDIDATE_SCANS, 100, 1, 1000)
44+
const maxTotalScanBytes = boundedInteger(process.env.OPERATOR_CODEX_IMAGE_ROLLOUT_MAX_SCAN_MIB, 1024, 1, 16384) * MiB
45+
46+
if (criticalBytes <= warningBytes) {
47+
console.error("Critical rollout threshold must be greater than the warning threshold.")
48+
process.exit(2)
49+
}
50+
51+
const codexHome = path.resolve(process.env.CODEX_HOME || path.join(os.homedir(), ".codex"))
52+
const sessionsRoot = path.resolve(requestedRoot || path.join(codexHome, "sessions"))
53+
54+
function isInside(root, candidate) {
55+
const relative = path.relative(root, candidate)
56+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative))
57+
}
58+
59+
async function containsInlineImage(filePath, byteBudget) {
60+
const marker = Buffer.from("data:image/")
61+
const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 })
62+
let tail = Buffer.alloc(0)
63+
let scanned = 0
64+
65+
try {
66+
for await (const chunk of stream) {
67+
const remaining = byteBudget - scanned
68+
if (remaining <= 0) return { found: false, scanned, complete: false }
69+
const bounded = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk
70+
const combined = tail.length > 0 ? Buffer.concat([tail, bounded]) : bounded
71+
scanned += bounded.length
72+
if (combined.indexOf(marker) !== -1) return { found: true, scanned, complete: true }
73+
tail = combined.subarray(Math.max(0, combined.length - marker.length + 1))
74+
if (bounded.length < chunk.length) return { found: false, scanned, complete: false }
75+
}
76+
return { found: false, scanned, complete: true }
77+
} finally {
78+
stream.destroy()
79+
}
80+
}
81+
82+
const findings = []
83+
let filesVisited = 0
84+
let candidateScans = 0
85+
let scannedBytes = 0
86+
let traversalTruncated = false
87+
let scanBudgetExhausted = false
88+
89+
async function walk(directory) {
90+
if (filesVisited >= maxFiles) {
91+
traversalTruncated = true
92+
return
93+
}
94+
95+
let entries
96+
try {
97+
entries = await fs.promises.readdir(directory, { withFileTypes: true })
98+
} catch (error) {
99+
findings.push({
100+
severity: "critical",
101+
code: "sessions_directory_unreadable",
102+
path: directory,
103+
message: String(error?.message || error),
104+
})
105+
return
106+
}
107+
108+
for (const entry of entries) {
109+
if (filesVisited >= maxFiles) {
110+
traversalTruncated = true
111+
return
112+
}
113+
114+
const candidate = path.resolve(directory, entry.name)
115+
if (!isInside(sessionsRoot, candidate)) {
116+
findings.push({ severity: "critical", code: "path_escape", path: candidate })
117+
continue
118+
}
119+
120+
let stat
121+
try {
122+
stat = await fs.promises.lstat(candidate)
123+
} catch (error) {
124+
findings.push({ severity: "critical", code: "state_entry_unreadable", path: candidate, message: String(error?.message || error) })
125+
continue
126+
}
127+
128+
if (stat.isSymbolicLink()) {
129+
findings.push({ severity: "critical", code: "symlink_in_sessions", path: candidate })
130+
continue
131+
}
132+
if (stat.isDirectory()) {
133+
await walk(candidate)
134+
continue
135+
}
136+
if (!stat.isFile() || !entry.name.endsWith(".jsonl")) continue
137+
138+
filesVisited += 1
139+
if (stat.size < warningBytes) continue
140+
141+
let imageScan = { found: false, scanned: 0, complete: false }
142+
if (candidateScans < maxCandidateScans && scannedBytes < maxTotalScanBytes) {
143+
const perFileBudget = Math.min(stat.size, maxTotalScanBytes - scannedBytes)
144+
candidateScans += 1
145+
imageScan = await containsInlineImage(candidate, perFileBudget)
146+
scannedBytes += imageScan.scanned
147+
if (!imageScan.complete && !imageScan.found) scanBudgetExhausted = true
148+
} else {
149+
scanBudgetExhausted = true
150+
}
151+
152+
const severity = stat.size >= criticalBytes || imageScan.found || !imageScan.complete ? "critical" : "warning"
153+
const code = stat.size >= criticalBytes
154+
? imageScan.found
155+
? "oversized_inline_image_rollout"
156+
: "oversized_rollout"
157+
: imageScan.found
158+
? "inline_image_rollout"
159+
: imageScan.complete
160+
? "large_rollout"
161+
: "large_rollout_scan_incomplete"
162+
163+
findings.push({
164+
severity,
165+
code,
166+
path: candidate,
167+
bytes: stat.size,
168+
inline_image_marker_found: imageScan.found,
169+
bytes_scanned: imageScan.scanned,
170+
scan_complete: imageScan.complete,
171+
})
172+
}
173+
}
174+
175+
let rootStatus = "present"
176+
try {
177+
const rootStat = await fs.promises.lstat(sessionsRoot)
178+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
179+
findings.push({ severity: "critical", code: "invalid_sessions_root", path: sessionsRoot })
180+
} else {
181+
await walk(sessionsRoot)
182+
}
183+
} catch (error) {
184+
if (error?.code === "ENOENT") rootStatus = "not_present"
185+
else findings.push({ severity: "critical", code: "sessions_root_unreadable", path: sessionsRoot, message: String(error?.message || error) })
186+
}
187+
188+
if (traversalTruncated) findings.push({ severity: "critical", code: "file_traversal_limit_reached", limit: maxFiles })
189+
if (scanBudgetExhausted) findings.push({ severity: "critical", code: "scan_budget_exhausted", max_total_scan_bytes: maxTotalScanBytes })
190+
191+
const criticalCount = findings.filter((item) => item.severity === "critical").length
192+
const warningCount = findings.filter((item) => item.severity === "warning").length
193+
const desktopSafeToLaunch = criticalCount === 0
194+
195+
const result = {
196+
schema_version: 1,
197+
status: desktopSafeToLaunch ? (warningCount > 0 ? "warning" : "clean") : "blocked",
198+
sessions_root: sessionsRoot,
199+
sessions_root_status: rootStatus,
200+
warning_threshold_bytes: warningBytes,
201+
critical_threshold_bytes: criticalBytes,
202+
files_visited: filesVisited,
203+
candidate_files_scanned: candidateScans,
204+
bytes_scanned: scannedBytes,
205+
desktop_safe_to_launch: desktopSafeToLaunch,
206+
findings,
207+
continuity_route: desktopSafeToLaunch
208+
? "guarded_direct_openai"
209+
: "fresh_isolated_codex_home_or_explicitly_authorized_local_route",
210+
mutation_performed: false,
211+
}
212+
213+
if (jsonOutput) console.log(JSON.stringify(result, null, 2))
214+
else {
215+
console.log(`Codex image-rollout preflight: ${result.status}`)
216+
console.log(`Sessions root: ${sessionsRoot}`)
217+
console.log(`Files visited: ${filesVisited}; critical: ${criticalCount}; warnings: ${warningCount}`)
218+
for (const finding of findings) {
219+
console.log(`${finding.severity.toUpperCase()} ${finding.code}${finding.path ? ` ${finding.path}` : ""}`)
220+
}
221+
if (!desktopSafeToLaunch) {
222+
console.error("Refusing Desktop admission for this state profile. Do not delete or rewrite rollout history. Preserve the profile, continue through a fresh isolated CODEX_HOME or explicitly authorized local route, and reconcile uncertain writes before replay.")
223+
}
224+
}
225+
226+
process.exit(desktopSafeToLaunch ? 0 : 75)

0 commit comments

Comments
 (0)