fix(codex): close symlink escape on GET /file, pin project writes to server cwd
This commit is contained in:
@@ -22,6 +22,11 @@ const {
|
||||
isFileLike,
|
||||
MAX_FILE_BYTES,
|
||||
} = require("./cc-discovery");
|
||||
// Shared symlink-escape guard — the same check writeArtifact/deleteArtifact
|
||||
// use, so a read and a write of the same path agree on what "inside the
|
||||
// root" means. See codex-mutate.js's assertNotSymlinkEscape for the leaf +
|
||||
// ancestor symlink reasoning.
|
||||
const { assertNotSymlinkEscape } = require("./codex-mutate");
|
||||
|
||||
function safeReadText(absPath) {
|
||||
try {
|
||||
@@ -286,6 +291,48 @@ function readOverview(opts = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// ── Single-file body endpoint (with strict path containment) ───────────
|
||||
|
||||
/**
|
||||
* Read the body of one file living under CODEX_HOME, the project .codex
|
||||
* dir, or (only for a file literally named AGENTS.md) the project root.
|
||||
* Mirrors cc-discovery's readFileSafe, plus the same leaf/ancestor
|
||||
* symlink-escape guard the write side (codex-mutate.js) uses — a string-only
|
||||
* `isUnder` check alone would let a symlink dropped anywhere along the path
|
||||
* read arbitrary files outside the root.
|
||||
*/
|
||||
function readFileSafe(absPath, opts = {}) {
|
||||
const codexHome = getCodexHome();
|
||||
const projectCodexDir = getProjectCodexDir(opts.cwd);
|
||||
const projectRoot = getProjectRoot(opts.cwd);
|
||||
const resolved = path.resolve(absPath);
|
||||
|
||||
const inside = [codexHome, projectCodexDir, projectRoot].some((root) => isUnder(root, resolved));
|
||||
if (!inside) return { error: "path is outside allowed roots" };
|
||||
if (
|
||||
isUnder(projectRoot, resolved) &&
|
||||
!isUnder(projectCodexDir, resolved) &&
|
||||
path.basename(resolved) !== "AGENTS.md"
|
||||
) {
|
||||
return { error: "only AGENTS.md is readable from project root" };
|
||||
}
|
||||
|
||||
const trustedRoot = isUnder(codexHome, resolved)
|
||||
? codexHome
|
||||
: isUnder(projectCodexDir, resolved)
|
||||
? projectCodexDir
|
||||
: projectRoot;
|
||||
try {
|
||||
assertNotSymlinkEscape(trustedRoot, resolved);
|
||||
} catch (err) {
|
||||
return { error: err.message };
|
||||
}
|
||||
|
||||
const r = safeReadText(resolved);
|
||||
if (!r) return { error: "file not readable" };
|
||||
return { ok: true, file: resolved, ...r };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
readSkills,
|
||||
readAgents,
|
||||
@@ -293,5 +340,6 @@ module.exports = {
|
||||
readMemory,
|
||||
readConfig,
|
||||
readOverview,
|
||||
readFileSafe,
|
||||
readTomlMeta,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user