fix(codex-config): close whole-branch review defects in the Codex panel
Plugins/marketplaces GET routes were silently empty because codex-cli.js
never unwrapped the CLI's {installed,available}/{marketplaces} objects
into arrays; backup writes bypassed the symlink-escape guard entirely;
readFileSafe was stricter than every other Codex reader, showing a
symlinked skill/rule in the list and then refusing to open it;
marketplaceAdd only accepted http(s) URLs, rejecting owner/repo sources
the CLI itself takes. Also: docs drift, a dead i18n key, and a scope-cast
hazard in the shared Editor/ConfirmDelete modals.
Fixes, one per finding: unwrap installed/marketplaces in codex-cli.js so
every list route keeps returning a bare list; guard the backup path in
codex-mutate.js with the same assertNotSymlinkEscape the write path
already used (Claude's cc-mutate.js has no such guard on its primary
write path either, so left untouched there); relax readFileSafe's
containment to lexical-only, matching the other four Codex readers,
while leaving the write side's realpath check untouched; reuse
cc-plugin-cli's assertSource for marketplaceAdd instead of a third
validator (ssh remains out of scope, no credential story for it yet).
Server: 1557/1557 (was 1548). Client: tsc -b clean, 469/469 tests.
This commit is contained in:
@@ -209,14 +209,20 @@ function writeArtifact(args) {
|
||||
assertNotSymlinkEscape(r.trustedRoot, r.target);
|
||||
|
||||
const existedBefore = fs.existsSync(r.filePath);
|
||||
const backup = existedBefore
|
||||
? createBackup({
|
||||
source: r.kind === "dir" ? r.target : r.filePath,
|
||||
backupRoot: backupRootFor(scope, type, { cwd }),
|
||||
type,
|
||||
name: path.basename(r.target),
|
||||
})
|
||||
: null;
|
||||
let backup = null;
|
||||
if (existedBefore) {
|
||||
const backupRoot = backupRootFor(scope, type, { cwd });
|
||||
// The backup write never went through the symlink-escape guard: a
|
||||
// `<root>/codex-config-backups` symlink would send the previous file's
|
||||
// body wherever it points. Same check as the target above, same reason.
|
||||
assertNotSymlinkEscape(r.trustedRoot, backupRoot);
|
||||
backup = createBackup({
|
||||
source: r.kind === "dir" ? r.target : r.filePath,
|
||||
backupRoot,
|
||||
type,
|
||||
name: path.basename(r.target),
|
||||
});
|
||||
}
|
||||
|
||||
if (r.kind === "dir") {
|
||||
fs.mkdirSync(r.target, { recursive: true });
|
||||
@@ -240,9 +246,11 @@ function deleteArtifact(args) {
|
||||
}
|
||||
assertNotSymlinkEscape(r.trustedRoot, r.target);
|
||||
|
||||
const backupRoot = backupRootFor(scope, type, { cwd });
|
||||
assertNotSymlinkEscape(r.trustedRoot, backupRoot);
|
||||
const backup = createBackup({
|
||||
source: r.target,
|
||||
backupRoot: backupRootFor(scope, type, { cwd }),
|
||||
backupRoot,
|
||||
type,
|
||||
name: path.basename(r.target),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user