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:
+32
-5
@@ -30,6 +30,8 @@
|
||||
|
||||
const { execFile } = require("node:child_process");
|
||||
const { getCodexHome } = require("./codex-home");
|
||||
// Reused, not reimplemented — see marketplaceAdd's doc comment.
|
||||
const { assertSource } = require("./cc-plugin-cli");
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 120_000;
|
||||
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
||||
@@ -230,8 +232,18 @@ async function mcpRemove(name) {
|
||||
|
||||
// ── Plugins ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* `codex plugin list --json` returns `{installed: [...], available: [...]}`,
|
||||
* not a bare array — unlike `mcp list`. The installed list is what the
|
||||
* Plugins tab shows; unwrap it here so `cliRead` (codex-config.js) can keep
|
||||
* treating every list route the same way: `result.data` is the list.
|
||||
*/
|
||||
async function pluginList() {
|
||||
return runCodex(["plugin", "list", "--json"]);
|
||||
const result = await runCodex(["plugin", "list", "--json"]);
|
||||
if (result.data !== undefined && !Array.isArray(result.data)) {
|
||||
result.data = Array.isArray(result.data.installed) ? result.data.installed : [];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function pluginAdd(spec) {
|
||||
@@ -249,13 +261,28 @@ async function pluginRemove(spec) {
|
||||
|
||||
// ── Marketplaces ───────────────────────────────────────────────────────
|
||||
|
||||
/** Same object-not-array shape as `pluginList` — unwrap `marketplaces`. */
|
||||
async function marketplaceList() {
|
||||
return runCodex(["plugin", "marketplace", "list", "--json"]);
|
||||
const result = await runCodex(["plugin", "marketplace", "list", "--json"]);
|
||||
if (result.data !== undefined && !Array.isArray(result.data)) {
|
||||
result.data = Array.isArray(result.data.marketplaces) ? result.data.marketplaces : [];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function marketplaceAdd(url) {
|
||||
const u = assertUrl(url, "marketplace url");
|
||||
return serialize(() => runCodex(["plugin", "marketplace", "add", u]));
|
||||
/**
|
||||
* `codex plugin marketplace add <SOURCE>` accepts a local path, `owner/repo`,
|
||||
* an https git URL, or an ssh git URL — not just http(s), so `assertUrl`
|
||||
* rejected the common `owner/repo` form. Reuse cc-plugin-cli's `assertSource`
|
||||
* (same shape `claude plugin marketplace add` accepts) instead of writing a
|
||||
* third validator. That guard also excludes ssh remotes and `..` — kept as
|
||||
* is here too: ssh needs its own credential/host-key handling this dashboard
|
||||
* has no story for yet, and every other CLI-facing source guard in this repo
|
||||
* already draws that same line.
|
||||
*/
|
||||
async function marketplaceAdd(source) {
|
||||
const s = assertSource(source);
|
||||
return serialize(() => runCodex(["plugin", "marketplace", "add", s]));
|
||||
}
|
||||
|
||||
async function marketplaceRemove(name) {
|
||||
|
||||
Reference in New Issue
Block a user