Merge pull request #1434 from kaitranntt/codex/fix-unbounded-recursion-in-receipt-generation

fix(persist): bound Codex translator receipt scan
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-05-30 15:46:29 -04:00
commit 93756489a5
3 files changed
+104 -15

No files matched your search

+57 -15
View File
@@ -1,5 +1,8 @@
export const CODEX_TRANSLATOR_URL_MARKER = '/api/provider/codex';
const MAX_CODEX_TRANSLATOR_SCAN_DEPTH = 100;
const MAX_CODEX_TRANSLATOR_SCAN_NODES = 10000;
function formatSettingsPathSegment(basePath: string, segment: string | number): string {
if (typeof segment === 'number') {
return `${basePath}[${segment}]`;
@@ -13,23 +16,62 @@ function formatSettingsPathSegment(basePath: string, segment: string | number):
}
export function findCodexTranslatorUrlPaths(value: unknown, path = ''): string[] {
if (typeof value === 'string') {
return value.includes(CODEX_TRANSLATOR_URL_MARKER) ? [path || '(root)'] : [];
const matches: string[] = [];
const stack: Array<{ value: unknown; path: string; depth: number }> = [{ value, path, depth: 0 }];
const seen = new WeakSet<object>();
let visitedNodes = 0;
while (stack.length > 0 && visitedNodes < MAX_CODEX_TRANSLATOR_SCAN_NODES) {
const item = stack.pop();
if (!item) {
break;
}
visitedNodes += 1;
if (typeof item.value === 'string') {
if (item.value.includes(CODEX_TRANSLATOR_URL_MARKER)) {
matches.push(item.path || '(root)');
}
continue;
}
if (typeof item.value !== 'object' || item.value === null) {
continue;
}
if (seen.has(item.value)) {
continue;
}
seen.add(item.value);
if (item.depth >= MAX_CODEX_TRANSLATOR_SCAN_DEPTH) {
continue;
}
if (Array.isArray(item.value)) {
for (let index = item.value.length - 1; index >= 0; index -= 1) {
stack.push({
value: item.value[index],
path: formatSettingsPathSegment(item.path, index),
depth: item.depth + 1,
});
}
continue;
}
const entries = Object.entries(item.value);
for (let index = entries.length - 1; index >= 0; index -= 1) {
const [key, child] = entries[index];
stack.push({
value: child,
path: formatSettingsPathSegment(item.path, key),
depth: item.depth + 1,
});
}
}
if (Array.isArray(value)) {
return value.flatMap((item, index) =>
findCodexTranslatorUrlPaths(item, formatSettingsPathSegment(path, index))
);
}
if (typeof value === 'object' && value !== null) {
return Object.entries(value).flatMap(([key, item]) =>
findCodexTranslatorUrlPaths(item, formatSettingsPathSegment(path, key))
);
}
return [];
return matches;
}
export function formatSettingsPathList(paths: string[]): string {