Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b577c5d
fix(codex): preserve hardlink names in catalog heal paths
luvs01 Oct 23, 2026
b8f5df6
fix(codex): retain native catalog leaf aliases where safe
luvs01 Oct 23, 2026
77e37fe
fix(codex): fence uncertain catalog entry identity
luvs01 Oct 23, 2026
d7d8804
fix(codex): retain uncertainty after catalog hop exhaustion
luvs01 Oct 23, 2026
03cabdf
fix(catalog): retain uncertainty after symlink lookup failures
luvs01 Oct 23, 2026
ab8dfda
merge: align catalog heal path fixes with current dev
luvs01 Oct 23, 2026
9e5a917
fix(test): promote Bun 1.4.4 with independent runner role verification
luvs01 Oct 23, 2026
1deb67d
docs: align Docker Bun comments with unified role defaults
luvs01 Oct 23, 2026
41f08ff
test: use a draining TLS origin for early upload replies
luvs01 Oct 23, 2026
a9c3d12
test: confine draining origin to buffered HTTP/1 upload
luvs01 Oct 23, 2026
f011a94
merge: align test-runner role branch with dev 2.93.0 dependency baseline
luvs01 Oct 24, 2026
42a2439
fix(codex): resolve catalog link target parents with filesystem seman…
luvs01 Oct 24, 2026
e37982a
fix(codex): match platform .. semantics in catalog link targets
luvs01 Oct 24, 2026
0b443cc
fix(codex): refuse non-directory crossings in POSIX link targets
luvs01 Oct 24, 2026
123a43a
merge: integrate current dev into catalog path validation
luvs01 Oct 24, 2026
c478805
fix(test): drain account fixture config ACL work before removal
luvs01 Oct 24, 2026
d55cc88
Merge Bun 1.4.4 runner into fixture cleanup validation
luvs01 Oct 24, 2026
f4d60d9
fix(config): skip optional directory hardening for non-directories
luvs01 Oct 24, 2026
412d90b
Merge current dev into catalog self-heal
luvs01 Oct 24, 2026
192687b
Merge shared Bun 1.4.4 baseline into catalog self-heal
luvs01 Oct 24, 2026
a554968
Merge catalog entry-identity candidate into config hardening stack
luvs01 Oct 24, 2026
9a6973a
Merge dev323 into catalog heal path refresh
luvs01 Oct 25, 2026
cf72010
Merge commit '50afcec56514992944d890e6577f709eb7075cad' into codex/76…
luvs01 Oct 25, 2026
d06cf9b
Merge commit 'cf72010f480d607bdf390717c28f62be240b0981' into codex/76…
luvs01 Oct 25, 2026
433c9f4
Merge remote-tracking branch 'origin/dev' into fix/catalog-heal-7610-…
lidge-jun Oct 26, 2026
ed4bd97
test(codex): reproduce catalog observation and reanchor regressions
lidge-jun Oct 26, 2026
44e1276
fix(codex): revalidate catalog evidence and reanchor selected publica…
lidge-jun Oct 26, 2026
d951d71
fix(codex): retain recovery baseline when publication proof fails
lidge-jun Oct 26, 2026
bd00b69
Merge remote-tracking branch 'origin/codex/catalog-heal-hardlink-path…
lidge-jun Oct 26, 2026
51ab52c
Merge origin/dev into #7688 after #7610 landed
lidge-jun Oct 26, 2026
faeead3
Merge remote-tracking branch 'origin/dev' into lb-7688
lidge-jun Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/config/paths.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { chmodSync, existsSync } from "node:fs";
import { chmodSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { flushWindowsSecretAclReapsBeforeRemoval, hardenSecretDirAsync, windowsSecretAclApplies, withWindowsSecretAclSettled } from "../lib/windows-secret-acl";
Expand Down Expand Up @@ -37,7 +37,8 @@ export function hardenConfigDir(): void {
// The guard runs before any mutation: refusing after chmod/ACL would already
// have changed the protected directory used by the test-home boundary.
assertNotRealHomeUnderTest(dir);
if (!existsSync(dir)) return;
// Follow directory links as before, but never apply directory permissions to a file.
try { if (!statSync(dir).isDirectory()) return; } catch { return; }
// Windows privacy is enforced by NTFS ACLs. chmod only changes DOS attributes there;
// doing it before the ACL lookup can invalidate its ctime-based successful observation.
if (!windowsSecretAclApplies()) {
Expand Down
2 changes: 1 addition & 1 deletion structure/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ returns unavailable for missing, invalid, or persistently conflicting config. It
test seam and the mutation types remain re-exported through `src/config.ts`. Successful, repaired, and salvaged file loads record their source path in the private WeakMap owned by `src/config/rebase-provenance.ts`; defaults and synthetic objects do not acquire it. This metadata is never serialized. Inventory drift cannot turn a file-backed instance into a synthetic discovery writer. Read publication may use the detached policy projection described by the [catalog contract](catalog.md#shared-catalog), without changing the live merge baseline; changing the resolved config home refuses publication.

`src/config/paths.ts` owns `OPENCODEX_HOME` expansion and resolution, the config directory and `config.json` path. A relative home is resolved once per distinct raw environment value, so a later working-directory change cannot
silently move the active installation. Config reads in this leaf and `src/config/load-degrade.ts` use NTFS ACLs on Windows and private POSIX modes elsewhere. Windows reads omit `chmod` before ACL hardening so repeated loads preserve successful object/freshness observations; external metadata changes still invalidate them. Directory flights keep their existing flush/reap lifecycle, and file/required-write failure policy is unchanged. `tests/windows/windows-config-read-acl-memo.test.ts` covers repeated loads, external invalidation and POSIX permissions.
silently move the active installation. Config reads in this leaf and `src/config/load-degrade.ts` use NTFS ACLs on Windows and private POSIX modes elsewhere. Optional directory hardening follows directory symlinks/junctions but skips non-directory or unstatable targets before starting chmod or ACL work. Windows reads omit `chmod` before ACL hardening so repeated loads preserve successful object/freshness observations; external metadata changes still invalidate them. Directory flights keep their existing flush/reap lifecycle, and file/required-write failure policy is unchanged. `tests/windows/windows-config-read-acl-memo.test.ts` covers repeated loads, non-directory and directory-link behavior, external invalidation and POSIX permissions.

Intercept writers use `withConfigDirHardeningSettled` from `src/config/paths.ts` after their last unrelated await. The registry includes the optional config flight, ancestors and retained timeout children; one existing ACL harden deadline bounds admission, including newly overlapping work. Expiry throws sanitized `WindowsSecretAclSettleTimeoutError` (`ETIMEDOUT`) without running the writer or relinquishing child cleanup ownership. The final empty-registry check and synchronous callback share one turn; the callback contains no await. Picker cleanup independently gates both SQLite owner inspection and post-untrust acknowledgement; profile and model-snapshot publication re-enter after trust/discovery awaits. Lexical/canonical aliases and uncertain identities drain conservatively. Removal retains its separate at-or-below scope and actual-exit requirement.

Expand Down
22 changes: 15 additions & 7 deletions tests/codex-integration/codex-account-slot-identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { flushConfigDirHardening } from "../../src/config/paths";
import { saveCodexAccountCredential } from "../../src/codex/account-store";
import { codexAccountUnusableReason } from "../../src/codex/account-usability";
import { reconcileMainCodexAccountRuntimeState, resetMainCodexAccountIdentityTrackingForTests } from "../../src/codex/account-lifecycle";
Expand All @@ -13,7 +14,7 @@ import { clearAccountQuota } from "../../src/codex/quota";
import { clearCodexUpstreamHealth, clearThreadAccountMap } from "../../src/codex/routing";
import { runPassiveRead } from "../../src/lib/passive-read";
import type { OcxConfig } from "../../src/types";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { drainAndRemoveFixtureRoots } from "../helpers/fixture-teardown";

/**
* Security proof for the slot identity fields on the account-list API: `slotId`, `slotKind`
Expand Down Expand Up @@ -94,14 +95,21 @@ beforeEach(() => {
resetRuntime();
});

afterEach(() => {
afterEach(async () => {
globalThis.fetch = originalFetch;
resetRuntime();
for (const [name, value] of Object.entries(saved)) {
if (value === undefined) delete process.env[name];
else process.env[name] = value;
}
removeTreeWithRetry(home);
await drainAndRemoveFixtureRoots({
roots: [{ path: home }],
restoreEnvironment: () => {
for (const [name, value] of Object.entries(saved)) {
if (value === undefined) delete process.env[name];
else process.env[name] = value;
}
},
}, {
// The tracked flight belongs to the nested config directory, not its fixture root.
settleConfigFlights: () => flushConfigDirHardening(join(home, "ocx")),
});
});

describe("Codex account slot identity metadata", () => {
Expand Down
21 changes: 15 additions & 6 deletions tests/codex-integration/codex-auth-headless-accounts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { flushConfigDirHardening } from "../../src/config/paths";
import { getCodexAccountCredential, saveCodexAccountCredential } from "../../src/codex/account-store";
import { resetMainCodexAccountIdentityTrackingForTests } from "../../src/codex/account-lifecycle";
import { clearAccountNeedsReauth, isAccountNeedsReauth, markAccountNeedsReauth } from "../../src/codex/account-runtime-state";
Expand All @@ -19,6 +20,7 @@ import { clearAccountQuota } from "../../src/codex/quota";
import { resetQuotaQueryBackoffForTests } from "../../src/codex/quota-query-backoff";
import { resetLifecycleDrainStateForTests } from "../../src/server/lifecycle";
import type { OcxConfig } from "../../src/types";
import { drainAndRemoveFixtureRoots } from "../helpers/fixture-teardown";
import { removeTreeWithRetry } from "../helpers/remove-tree";

let root: string;
Expand Down Expand Up @@ -96,16 +98,23 @@ beforeEach(() => {
resetRuntimeState();
});

afterEach(() => {
afterEach(async () => {
defaultHomeSpy?.mockRestore();
defaultHomeSpy = undefined;
globalThis.fetch = originalFetch;
resetRuntimeState();
if (previousOcxHome === undefined) delete process.env.OPENCODEX_HOME;
else process.env.OPENCODEX_HOME = previousOcxHome;
if (previousCodexHome === undefined) delete process.env.CODEX_HOME;
else process.env.CODEX_HOME = previousCodexHome;
removeTreeWithRetry(root);
await drainAndRemoveFixtureRoots({
roots: [{ path: root }],
restoreEnvironment: () => {
if (previousOcxHome === undefined) delete process.env.OPENCODEX_HOME;
else process.env.OPENCODEX_HOME = previousOcxHome;
if (previousCodexHome === undefined) delete process.env.CODEX_HOME;
else process.env.CODEX_HOME = previousCodexHome;
},
}, {
// Tests can replace OPENCODEX_HOME; await the original fixture's exact flight key.
settleConfigFlights: () => flushConfigDirHardening(join(root, "ocx")),
});
});

describe("account listing without a native Codex home", () => {
Expand Down
34 changes: 33 additions & 1 deletion tests/windows/windows-config-read-acl-memo.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, expect, test } from "bun:test";
import { chmodSync, mkdtempSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, statSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { loadConfig } from "../../src/config";
Expand Down Expand Up @@ -71,6 +71,38 @@ test("repeated Windows config reads preserve successful ACL observations", async
expect(readFileSync(join(home, "config.json"), "utf8")).toBe(before);
});

for (const platform of ["win32", "darwin"]) {
test(`optional directory hardening leaves a regular file unchanged on ${platform}`, async () => {
Object.defineProperty(process, "platform", { value: platform, configurable: true });
acl.setPlatformForTests(platform);
const path = join(home, "not-a-config-directory");
writeFileSync(path, "fixture-unsafe-root");
const mode = statSync(path).mode;
process.env.OPENCODEX_HOME = path;

hardenConfigDir();
await flushConfigDirHardeningForTests();

expect(calls).toEqual([]);
expect(readFileSync(path, "utf8")).toBe("fixture-unsafe-root");
expect(statSync(path).mode).toBe(mode);
});
}

test("optional directory hardening still follows a directory symlink or junction", async () => {
const target = join(home, "directory-target");
const link = join(home, "directory-link");
mkdirSync(target);
symlinkSync(target, link, originalPlatform === "win32" ? "junction" : "dir");
process.env.OPENCODEX_HOME = link;

hardenConfigDir();
await flushConfigDirHardeningForTests();

expect(calls.length).toBeGreaterThan(0);
expect(statSync(link).isDirectory()).toBe(true);
});

test("an external metadata change still invalidates a Windows file ACL observation", () => {
const path = join(home, "auth.json");
let freshness = 1n;
Expand Down
Loading