Repository navigation
fix(cli): accept NTFS file ids above 2^53 in the update restart marker #6644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,8 +18,11 @@ function parseMarker(raw: string): UpdateRestartChildMarker { | |||||||||||||||||
| || value.version === "0.0.0" || !parseStrictSemver(value.version) || !value.home | ||||||||||||||||||
| || !Number.isSafeInteger(value.home.revision)) throw new Error("update_restart_child_marker_invalid"); | ||||||||||||||||||
| for (const directory of [value.home.config, value.home.codex]) { | ||||||||||||||||||
| // Windows reports 64-bit NTFS file ids (MFT sequence in the high 16 bits) as doubles above | ||||||||||||||||||
| // 2^53. Parent and child read them through the same stat API, so any finite integer is a | ||||||||||||||||||
| // faithful identity token; assertUpdateRestartHome still compares path, dev and ino exactly. | ||||||||||||||||||
| if (!directory || typeof directory.path !== "string" || !directory.path | ||||||||||||||||||
| || !Number.isSafeInteger(directory.dev) || !Number.isSafeInteger(directory.ino)) { | ||||||||||||||||||
| || !Number.isInteger(directory.dev) || !Number.isInteger(directory.ino)) { | ||||||||||||||||||
|
Comment on lines
+21
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n 'standalone|runUpdateRestart|statSync|assertUpdateRestartHome|readUpdateRestartHome' src/cli/update-restart.ts src/cli/update-restart-home.ts src/cli/update-restart-child.ts tests/cliRepository: lidge-jun/opencodex Length of output: 11180 🏁 Script executed: printf '%s\n' '--- changed hunk ---'; git diff --unified=12 6774f0f6f26c103da144a630971091a52ada80a5 059d3502c4c6d8c86823862764b8ecedc38d8334 -- src/cli/update-restart-child.ts; printf '%s\n' '--- home helper ---'; nl -ba src/cli/update-restart-home.ts; printf '%s\n' '--- parent flow ---'; nl -ba src/cli/update-restart.ts | sed -n '55,125p;175,245p'; printf '%s\n' '--- child flow ---'; nl -ba src/cli/update-restart-child.ts | sed -n '1,100p'; printf '%s\n' '--- focused tests ---'; nl -ba tests/cli/cli-update-restart-home.test.ts | sed -n '1,105p'; printf '%s\n' '--- runtime/package declarations ---'; rg -n '\"engines\"|bun|node' package.json src/AGENTS.md AGENTS.md 2>/dev/null | head -100Repository: lidge-jun/opencodex Length of output: 31008 🌐 Web query:
💡 Result: 🌐 Web query:
💡 Result: 🌐 Web query:
💡 Result: 🌐 Web query:
💡 Result: 🏁 Script executed: rg -n -F -- 'restartFromCurrentInstallation' src; rg -n -F -- 'readUpdateRestartHome()' src/cli src | head -80; rg -n -F -- 'runUpdateRestart(' src tests/cli/cli-update-restart.test.tsRepository: lidge-jun/opencodex Length of output: 3444 🏁 Script executed: printf '%s\n' '--- update candidate references ---'; rg -n 'candidate|restartFromCurrentInstallation|readUpdateRestartHome|update.restart' src/cli/index.ts | tail -70; printf '%s\n' '--- production entrypoint block ---'; nl -ba src/cli/index.ts | sed -n '885,960p'; printf '%s\n' '--- candidate declarations ---'; rg -n -C 5 'UpdateRestartCandidate|home:' src/cli/update-restart.ts src/cli/index.tsRepository: lidge-jun/opencodex Length of output: 13260 🏁 Script executed: printf '%s\n' '--- update candidate references ---'; rg -n -C 4 'candidate|restartFromCurrentInstallation|readUpdateRestartHome|UpdateRestart' src/cli/index.ts | tail -100; printf '%s\n' '--- complete relevant caller context ---'; nl -ba src/cli/index.ts | sed -n '900,955p'; printf '%s\n' '--- updater handoff construction ---'; rg -n -F -- 'UpdateRestartCandidate' src/cli/update-restart.ts src/cli/index.tsRepository: lidge-jun/opencodex Length of output: 8643 🏁 Script executed: printf '%s\n' '--- candidate construction ---'; nl -ba src/cli/update-restart-candidate.ts; printf '%s\n' '--- restart handler callers ---'; rg -n -F -- 'handleProxyRestart(' src/cli/index.ts; printf '%s\n' '--- surrounding callers ---'; rg -n -C 5 -F -- 'handleProxyRestart(' src/cli/index.tsRepository: lidge-jun/opencodex Length of output: 2414 🏁 Script executed: rg -n -F -- 'new UpdateRestartRequired' src tests; rg -n -F -- 'UpdateRestartRequired' src | head -100; rg -n -F -- 'readUpdateRestartHome' srcRepository: lidge-jun/opencodex Length of output: 1742 🏁 Script executed: nl -ba src/cli/system-restart-client.ts | sed -n '80,180p'; printf '%s\n' '--- binding call ---'; rg -n -C 3 -F -- 'requestBoundSystemRestart' src/cliRepository: lidge-jun/opencodex Length of output: 7675 🏁 Script executed: rg -n 'resolveServiceOwnership|resolveServiceState|revision' src/service/state.ts; nl -ba src/service/state.ts | sed -n '1,220p']]Repository: lidge-jun/opencodex Length of output: 3105 🏁 Script executed: printf '%s\n' '--- service state selection ---'; nl -ba src/service/state.ts | sed -n '450,477p;570,620p'; printf '%s\n' '--- service state paths ---'; rg -n -F -- 'serviceStatePaths' src/service/state.tsRepository: lidge-jun/opencodex Length of output: 4651 🏁 Script executed: nl -ba src/service/state.ts | sed -n '1,30p;160,185p'; rg -n 'function currentOpenCodexHome|currentOpenCodexHome' src/service/state.tsRepository: lidge-jun/opencodex Length of output: 3645 🏁 Script executed: printf '%s\n' '--- OpenCodex home binding ---'; nl -ba src/service/state.ts | sed -n '240,260p'; printf '%s\n' '--- Codex home binding ---'; rg -n 'currentCodexHome|resolveCodexHomeDir' src/codex/home.ts; nl -ba src/codex/home.ts | sed -n '1,120p']]Repository: lidge-jun/opencodex Length of output: 1339 🏁 Script executed: rg -n 'currentCodexHome|resolveCodexHomeDir' src/service/state.ts; nl -ba src/service/state.ts | sed -n '850,880p'Repository: lidge-jun/opencodex Length of output: 2131 🏁 Script executed: nl -ba src/service/state.ts | sed -n '212,225p'; nl -ba src/codex/home.ts | sed -n '185,240p'Repository: lidge-jun/opencodex Length of output: 3660 Reject unsafe filesystem identities before starting the update child. On 64-bit Linux FUSE, distinct inode values such as Reject unsafe Suggested fixdiff --git a/src/cli/update-restart-home.ts b/src/cli/update-restart-home.ts
@@
const canonical = realpathSync.native(path);
const stat = statSync(canonical);
if (!stat.isDirectory()) throw new Error("update_restart_home_unverified");
+ if (!Number.isSafeInteger(stat.dev) || !Number.isSafeInteger(stat.ino)) {
+ throw new Error("update_restart_home_unverified");
+ }
return { path: canonical, dev: stat.dev, ino: stat.ino };
diff --git a/src/cli/update-restart-child.ts b/src/cli/update-restart-child.ts
@@
- // Windows reports 64-bit NTFS file ids (MFT sequence in the high 16 bits) as doubles above
- // 2^53. Parent and child read them through the same stat API, so any finite integer is a
- // faithful identity token; assertUpdateRestartHome still compares path, dev and ino exactly.
if (!directory || typeof directory.path !== "string" || !directory.path
- || !Number.isInteger(directory.dev) || !Number.isInteger(directory.ino)) {
+ || !Number.isSafeInteger(directory.dev) || !Number.isSafeInteger(directory.ino)) {
throw new Error("update_restart_child_marker_invalid");
}
diff --git a/tests/cli/cli-update-restart-home.test.ts b/tests/cli/cli-update-restart-home.test.ts
@@
-test("NTFS file ids above 2^53 are admitted as home identity", () => {
- // Windows runners hand out directory ids with the MFT sequence in the high bits; the marker must
- // not depend on which id a temp directory happens to receive.
- const h = home();
+test("unsafe file ids are rejected as home identity", () => {
+ home();
const captured = readUpdateRestartHome();
const large = { ...captured, config: { ...captured.config, ino: 2 ** 60 + 4096 }, codex: { ...captured.codex, dev: 2 ** 56 } };
const marker = { home: large, version: "2.77.0", port: 10100, hostname: "127.0.0.1", deadlineAt: Date.now() + 5000 };
- writeFileSync(join(h.config, "config.json"), '{"hostname":"127.0.0.1"}\n');
- const seen: unknown[] = [];
- expect(() => admitUpdateRestartChild(["start", "--port", "10100"], {
- env: { [UPDATE_RESTART_CHILD_ENV]: JSON.stringify(marker) }, version: () => "2.77.0",
- checkHome: home => { seen.push(home); }, checkState: () => {},
- acquire: () => { throw new Error("lease busy"); },
- })).toThrow("lease busy");
- expect(seen).toEqual([large]);
+ expect(() => admitUpdateRestartChild(["start", "--port", "10100"], {
+ env: { [UPDATE_RESTART_CHILD_ENV]: JSON.stringify(marker) },
+ })).toThrow("update_restart_child_marker_invalid");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| throw new Error("update_restart_child_marker_invalid"); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a filesystem reports IDs above
Number.MAX_SAFE_INTEGER, distinct 64-bit IDs can map to the same JavaScript number—for example,Number(2n ** 60n) === Number(2n ** 60n + 1n). This check now admits those lossy values, andassertUpdateRestartHomelater compares the same rounded numbers viaJSON.stringify, so a config or Codex directory replaced during the handoff can be mistaken for the original and the child can start against an unexpected home. Preserve the IDs as bigint-derived decimal strings, or continue failing closed when exact identity cannot be represented; note that the production update path explicitly rejects Windows insrc/cli/update-restart.ts, so relaxing the guard does not enable the claimed Windows restart path.Useful? React with 👍 / 👎.