fix: preserve TOML table semantics during Codex startup trust

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 12:14:38 -05:00
1 parent 598e190cb3
commit a200495cc7
3 files changed
+110 -11

No files matched your search

@@ -129,6 +129,52 @@ and require fresh Linux CI. Keep ARM startup sign-off unresolved until proven
on a compatible target; package installation and direct execution alone do not
qualify the native runner. All Docker/native builds remain remote.
Checkpoint `598e190cb308f4d7f8f43c188c2e7d88c3fc90d1` passes
[ordinary CI](https://github.com/paperclipai/paperclip/actions/runs/38279960223):
both required gates, 54 successful checks/statuses, and four conditional skips.
Fresh review is 5/5; all 15 review threads are resolved. Human code-owner approval
is still absent. The test-drive serves `2026.1005.0+350.git.598e190cb3`; all four
execution configurations and both historical native runs survive restart.
The verified Apple Silicon daemon remains unchanged, `needsBuild: false`, with
no binary override, native compilation, Docker build, or provider turn locally.
Receipts: `/private/tmp/ci-final-598e190-ci-review-receipt.json` and
`/private/tmp/pr2-final-preservation-after.json`.
The [direct/native ARM diagnostic](https://github.com/paperclipai/paperclip/actions/runs/38280515437)
records GNU libc 2.39, AppArmor enabled with restricted unprivileged user
namespaces, an unconfined current profile, zero effective capabilities, and no
seccomp filter. Native startup still fails at bubblewrap loopback `RTM_NEWADDR`.
The direct control stopped before Codex spawn at a TOML preservation guard,
so it does not establish the sandbox failure's cause. Receipt:
`/private/tmp/ci-final-arm-direct-diagnostic-598-receipt.json`.
Pure, integrity-bound public `smol-toml` 1.9.1 reproduction identifies that guard
failure: its null-prototype tables differ from the writer's spread-created
ordinary objects. Preserve the parser's tables while changing only the intended
trust field; keep strict comparison, original comments/bytes, private writes,
and malformed-input rejection. No parser pin, semantic bypass, or permission
change. The existing suite passes 22 controls across both table representations;
real 1.9.1 treatment repairs five valid configurations and keeps two invalid
configurations unchanged. Runner/server typechecks pass. Receipt:
`/private/tmp/codex-default-startup-trust-fix-receipt.json`.
The [four-target performance qualifier](https://github.com/paperclipai/paperclip/actions/runs/38281631416)
uses literal `598e190` product/workflow source. Observed Linux x64/ARM64 and Apple
Silicon builds pass with actual concurrent JavaScript/native preparation.
Cache restore is exercised; observed results are cold misses, not measured
speedups. The cache writer is reachable through the existing verified master
canary push, with exact source and repository guards; no write-gate expansion
is needed. Linux daemon bytes match the measured GNU 2.39 baseline. Remaining
producer/consumer results must be recorded before qualification claims.
Next action: freeze the narrow trust correction, repeat the bounded direct
control with explicit corrected-tooling provenance, and retain native startup
as mandatory. A private namespace negative control and bounded read-only
AppArmor journal may help distinguish host denial. Do not modify host policy,
relax Codex's sandbox, or substitute direct execution for native qualification.
Require fresh CI/review after the correction. ARM startup and managed staging
remain unresolved; no merge or production recommendation is made.
## Current addition: Linux ARM64
The user now requires ARM Linux support as well. Treat ARM Linux as ARM64
@@ -5,16 +5,66 @@ import {
rmSync,
writeFileSync,
realpathSync,
statSync,
symlinkSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execFileSync } from "node:child_process";
import { describe, it, expect } from "vitest";
import { beforeEach, afterEach, describe, it, expect, vi } from "vitest";
import { parse } from "smol-toml";
import { trustCodexStartupRoot } from "./codex-startup-trust.js";
describe("isolated Codex startup trust", () => {
const parserStyle = vi.hoisted(() => ({ nullPrototypeTables: false }));
vi.mock("smol-toml", async (importOriginal) => {
const actual = await importOriginal<typeof import("smol-toml")>();
const setTablePrototypes = (value: unknown): void => {
if (value === null || typeof value !== "object" || value instanceof Date) return;
if (Array.isArray(value)) {
value.forEach(setTablePrototypes);
return;
}
Object.values(value).forEach(setTablePrototypes);
Object.setPrototypeOf(value, parserStyle.nullPrototypeTables ? null : Object.prototype);
};
return {
...actual,
parse: (...args: Parameters<typeof actual.parse>) => {
const result = actual.parse(...args);
// Exercise both table representations independently of the installed
// parser version while retaining the real TOML values.
setTablePrototypes(result);
return result;
},
};
});
describe.each([
{ name: "ordinary tables", nullPrototypeTables: false },
{ name: "null-prototype tables", nullPrototypeTables: true },
])("isolated Codex startup trust with $name", ({ nullPrototypeTables }) => {
beforeEach(() => { parserStyle.nullPrototypeTables = nullPrototypeTables; });
afterEach(() => { parserStyle.nullPrototypeTables = false; });
it.each(["missing", "empty"])("initializes %s config in an isolated home", (kind) => {
const temp = realpathSync(mkdtempSync(join(tmpdir(), "codex-trust-empty-")));
try {
const cwd = join(temp, "project");
const home = join(temp, "home");
mkdirSync(cwd);
if (kind === "empty") {
mkdirSync(home);
writeFileSync(join(home, "config.toml"), "");
}
trustCodexStartupRoot(home, cwd);
const path = join(home, "config.toml");
const updated = readFileSync(path, "utf8");
expect(parse(updated)).toEqual({ projects: { [cwd]: { trust_level: "trusted" } } });
expect(statSync(path).mode & 0o777).toBe(0o600);
trustCodexStartupRoot(home, cwd);
expect(readFileSync(path, "utf8")).toBe(updated);
} finally { rmSync(temp, { recursive: true, force: true }); }
});
it.each(["missing", "table", "empty-table", "inline", "empty-inline", "inline-projects"])(
"preserves managed comments and unrelated content with %s project configuration", (kind) => {
const temp = realpathSync(mkdtempSync(join(tmpdir(), "codex-trust-comments-")));
@@ -60,15 +110,17 @@ describe("isolated Codex startup trust", () => {
mkdirSync(home);
writeFileSync(
join(home, "config.toml"),
'model = "test"\n[mcp_servers.fixture]\nurl = "http://localhost/example"\n',
'model = "test"\nstarted = 2026-10-12T12:00:00Z\nlimits = [inf, nan, 9007199254740991]\n[mcp_servers.fixture]\nurl = "http://localhost/example"\n',
);
const original = readFileSync(join(home, "config.toml"), "utf8");
const unrelated = parse(original);
trustCodexStartupRoot(home, cwd);
trustCodexStartupRoot(home, cwd);
expect(parse(readFileSync(join(home, "config.toml"), "utf8"))).toEqual({
model: "test",
mcp_servers: { fixture: { url: "http://localhost/example" } },
...unrelated,
projects: { [cwd]: { trust_level: "trusted" } },
});
expect(readFileSync(join(home, "config.toml"), "utf8")).toContain(original);
expect(readFileSync(join(home, "config.toml"), "utf8")).not.toContain(
`[projects."${temp}"]`,
);
@@ -92,24 +92,25 @@ export function trustCodexStartupRoot(codexHome: string, cwd: string): void {
const path = join(codexHome, "config.toml");
const source = existsSync(path) ? readFileSync(path, "utf8") : "";
const config = parse(source);
const projects = config.projects ?? {};
// Retain the parser's table prototypes so strict candidate comparison also
// works with parsers that return null-prototype TOML tables.
const projects = config.projects ?? parse("");
if (
typeof projects !== "object" ||
Array.isArray(projects) ||
projects instanceof Date
)
throw new Error("codex_startup_trust_invalid_projects");
const project = projects[root] ?? {};
const project = projects[root] ?? parse("");
if (
typeof project !== "object" ||
Array.isArray(project) ||
project instanceof Date
)
throw new Error("codex_startup_trust_invalid_project");
config.projects = {
...projects,
[root]: { ...project, trust_level: "trusted" },
};
project.trust_level = "trusted";
projects[root] = project;
config.projects = projects;
const updated = editTrust(source, root, config);
if (updated === source) return;
const temporary = resolve(codexHome, `config.toml.${randomUUID()}.tmp`);