fix(codex): escape-aware closing-quote scan, honest symlink coverage
readTomlMeta's trailing-comment stripper found the first quote character regardless of escaping, so a description containing \"escaped quotes\" before a real comment got silently truncated instead of dropped. The scan now skips a quote preceded by a backslash. The "never walks outside" containment test passed by construction (row.file is always built off a fixed root) and would not fail if the isUnder guard were removed. Replaced it with two tests that actually pin the regression risk: a skill dir and an agent .toml symlinked in from outside CODEX_HOME, asserting the row is discovered and its content read through the link. isUnder stays as a defensive check (matches cc-discovery.js), now commented as such.
This commit is contained in:
@@ -61,6 +61,8 @@ function readSkillsAt(scope, codexDir) {
|
||||
const skills = [];
|
||||
for (const ent of listDir(dir)) {
|
||||
const skillDir = path.join(dir, ent.name);
|
||||
// isUnder here is defensive only — ent.name comes from readdirSync and
|
||||
// can't carry a traversal segment, so this can never actually trip.
|
||||
if (!isDirLike(ent, skillDir) || !isUnder(codexDir, skillDir)) continue;
|
||||
const skillFile = path.join(skillDir, "SKILL.md");
|
||||
const read = safeReadText(skillFile);
|
||||
@@ -124,9 +126,16 @@ function readTomlMeta(text) {
|
||||
value = value.slice(1, -1);
|
||||
} else if (value.startsWith('"') || value.startsWith("'")) {
|
||||
// A quoted value with a trailing `# comment` outside the string —
|
||||
// e.g. model = "gpt-5.4" # default. Strip from the closing quote on.
|
||||
// e.g. model = "gpt-5.4" # default. Find the real closing quote
|
||||
// (skipping a `\"` escaped one) and strip everything after it.
|
||||
const quote = value[0];
|
||||
const closeIdx = value.indexOf(quote, 1);
|
||||
let closeIdx = -1;
|
||||
for (let i = 1; i < value.length; i++) {
|
||||
if (value[i] === quote && value[i - 1] !== "\\") {
|
||||
closeIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (closeIdx > 0) value = value.slice(1, closeIdx);
|
||||
else continue;
|
||||
} else if (/^(true|false|-?\d+(\.\d+)?)\s*(#.*)?$/.test(value)) {
|
||||
@@ -146,6 +155,8 @@ function readAgentsAt(scope, codexDir) {
|
||||
for (const ent of listDir(dir)) {
|
||||
if (!ent.name.endsWith(".toml")) continue;
|
||||
const file = path.join(dir, ent.name);
|
||||
// isUnder here is defensive only — ent.name comes from readdirSync and
|
||||
// can't carry a traversal segment, so this can never actually trip.
|
||||
if (!isFileLike(ent, file) || !isUnder(codexDir, file)) continue;
|
||||
const read = safeReadText(file);
|
||||
if (!read) continue;
|
||||
@@ -177,6 +188,8 @@ function readRulesAt(scope, codexDir) {
|
||||
for (const ent of listDir(dir)) {
|
||||
if (!ent.name.endsWith(".rules")) continue;
|
||||
const file = path.join(dir, ent.name);
|
||||
// isUnder here is defensive only — ent.name comes from readdirSync and
|
||||
// can't carry a traversal segment, so this can never actually trip.
|
||||
if (!isFileLike(ent, file) || !isUnder(codexDir, file)) continue;
|
||||
const read = safeReadText(file);
|
||||
if (!read) continue;
|
||||
|
||||
Reference in New Issue
Block a user