Skip to content

Commit 8295f59

Browse files
authored
fix: preserve hook trust in runtime shadows
Merge PR #485.
1 parent 1edb5c0 commit 8295f59

2 files changed

Lines changed: 475 additions & 4 deletions

File tree

scripts/codex.js

Lines changed: 153 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,150 @@ function createRuntimeRotationShadowHome(originalCodexHome) {
24942494
return mkdtempSync(join(shadowRoot, "codex-multi-auth-runtime-home-"));
24952495
}
24962496

2497+
function parseHookStateTableKey(line) {
2498+
const basicStringMatch =
2499+
/^\s*\[\s*hooks\.state\.("(?:[^"\\]|\\.)*")\s*\]\s*$/.exec(line);
2500+
if (basicStringMatch) {
2501+
try {
2502+
const parsed = JSON.parse(basicStringMatch[1]);
2503+
return typeof parsed === "string" ? parsed : null;
2504+
} catch {
2505+
return null;
2506+
}
2507+
}
2508+
2509+
const literalStringMatch = /^\s*\[\s*hooks\.state\.'([^']*)'\s*\]\s*$/.exec(
2510+
line,
2511+
);
2512+
return literalStringMatch ? literalStringMatch[1] : null;
2513+
}
2514+
2515+
function isTomlTableLine(line) {
2516+
return /^\s*\[\[?\s*(?:"(?:[^"\\]|\\.)*"|'[^']*'|[A-Za-z0-9_-]+)(?:\s*\.|\s*\]\]?\s*(?:#.*)?$)/.test(
2517+
line,
2518+
);
2519+
}
2520+
2521+
// Keep these TOML block scan helpers aligned with test/codex-bin-wrapper.test.ts.
2522+
function createTomlBlockScanState() {
2523+
return {
2524+
arrayDepth: 0,
2525+
multilineStringDelimiter: null,
2526+
};
2527+
}
2528+
2529+
function isTopLevelTomlBlockScanState(state) {
2530+
return state.arrayDepth === 0 && state.multilineStringDelimiter === null;
2531+
}
2532+
2533+
function updateTomlBlockScanState(line, state) {
2534+
for (let index = 0; index < line.length; index += 1) {
2535+
if (state.multilineStringDelimiter) {
2536+
const closeIndex = line.indexOf(state.multilineStringDelimiter, index);
2537+
if (closeIndex < 0) {
2538+
return;
2539+
}
2540+
index = closeIndex + state.multilineStringDelimiter.length - 1;
2541+
state.multilineStringDelimiter = null;
2542+
continue;
2543+
}
2544+
2545+
if (line[index] === "#") {
2546+
return;
2547+
}
2548+
if (line.startsWith('"""', index) || line.startsWith("'''", index)) {
2549+
state.multilineStringDelimiter = line.slice(index, index + 3);
2550+
index += 2;
2551+
continue;
2552+
}
2553+
if (line[index] === '"') {
2554+
index += 1;
2555+
for (; index < line.length; index += 1) {
2556+
if (line[index] === "\\") {
2557+
index += 1;
2558+
} else if (line[index] === '"') {
2559+
break;
2560+
}
2561+
}
2562+
continue;
2563+
}
2564+
if (line[index] === "'") {
2565+
const closeIndex = line.indexOf("'", index + 1);
2566+
if (closeIndex < 0) return;
2567+
index = closeIndex;
2568+
continue;
2569+
}
2570+
if (line[index] === "[") {
2571+
state.arrayDepth += 1;
2572+
} else if (line[index] === "]" && state.arrayDepth > 0) {
2573+
state.arrayDepth -= 1;
2574+
}
2575+
}
2576+
}
2577+
2578+
function mirrorRuntimeShadowHookTrustState(
2579+
rawConfig,
2580+
originalCodexHome,
2581+
shadowCodexHome,
2582+
tomlStringLiteral,
2583+
) {
2584+
const sourceHooksPath = join(originalCodexHome, "hooks.json");
2585+
const shadowHooksPath = join(shadowCodexHome, "hooks.json");
2586+
if (sourceHooksPath === shadowHooksPath) {
2587+
return rawConfig;
2588+
}
2589+
2590+
const lineEnding = rawConfig.includes("\r\n") ? "\r\n" : "\n";
2591+
const lines = rawConfig.length > 0 ? rawConfig.split(/\r?\n/) : [];
2592+
const sourcePrefix = `${sourceHooksPath}:`;
2593+
const existingHookStateKeys = new Set();
2594+
for (const line of lines) {
2595+
const key = parseHookStateTableKey(line);
2596+
if (key) {
2597+
existingHookStateKeys.add(key);
2598+
}
2599+
}
2600+
2601+
const output = [];
2602+
let changed = false;
2603+
for (let index = 0; index < lines.length; index += 1) {
2604+
const line = lines[index];
2605+
const key = parseHookStateTableKey(line);
2606+
output.push(line);
2607+
if (!key || !key.startsWith(sourcePrefix)) {
2608+
continue;
2609+
}
2610+
2611+
const blockLines = [];
2612+
let nextIndex = index + 1;
2613+
const blockState = createTomlBlockScanState();
2614+
for (; nextIndex < lines.length; nextIndex += 1) {
2615+
const nextLine = lines[nextIndex];
2616+
if (
2617+
isTopLevelTomlBlockScanState(blockState) &&
2618+
isTomlTableLine(nextLine)
2619+
) {
2620+
break;
2621+
}
2622+
blockLines.push(nextLine);
2623+
updateTomlBlockScanState(nextLine, blockState);
2624+
}
2625+
output.push(...blockLines);
2626+
index = nextIndex - 1;
2627+
const shadowKey = `${shadowHooksPath}:${key.slice(sourcePrefix.length)}`;
2628+
if (existingHookStateKeys.has(shadowKey)) {
2629+
continue;
2630+
}
2631+
output.push("");
2632+
output.push(`[hooks.state.${tomlStringLiteral(shadowKey)}]`);
2633+
output.push(...blockLines);
2634+
existingHookStateKeys.add(shadowKey);
2635+
changed = true;
2636+
}
2637+
2638+
return changed ? output.join(lineEnding) : rawConfig;
2639+
}
2640+
24972641
function createRuntimeRotationProxyCodexHome(
24982642
baseEnv,
24992643
proxyBaseUrl,
@@ -2535,10 +2679,15 @@ function createRuntimeRotationProxyCodexHome(
25352679
const rawConfig = existsSync(originalConfigPath)
25362680
? readFileSync(originalConfigPath, "utf8")
25372681
: "";
2538-
const runtimeConfig = configTomlModule.rewriteConfigTomlForRuntimeRotationProvider(
2539-
rawConfig,
2540-
proxyBaseUrl,
2541-
clientApiKey,
2682+
const runtimeConfig = mirrorRuntimeShadowHookTrustState(
2683+
configTomlModule.rewriteConfigTomlForRuntimeRotationProvider(
2684+
rawConfig,
2685+
proxyBaseUrl,
2686+
clientApiKey,
2687+
),
2688+
originalCodexHome,
2689+
shadowCodexHome,
2690+
configTomlModule.tomlStringLiteral,
25422691
);
25432692
const runtimeConfigPath = join(shadowCodexHome, "config.toml");
25442693
writeFileSync(runtimeConfigPath, runtimeConfig, "utf8");

0 commit comments

Comments
 (0)