Reject non-object JSON roots in skills validator

readJsonObject fails when catalog files parse to null, arrays,
or other non-object roots so structural checks cannot be skipped.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
Cursor Agent
2026-07-10 13:43:22 +00:00
parent f1995f2c8f
commit 1588750090
2 changed files with 21 additions and 13 deletions

View File

@@ -30,4 +30,4 @@ this file.
- Access-review skill records entry notes only after successful API writes - Access-review skill records entry notes only after successful API writes
- Release checksums derived from `npm pack` tarball contents - Release checksums derived from `npm pack` tarball contents
- Validation enforces `.claude-plugin/marketplace.json` structure and Codex - Validation enforces `.claude-plugin/marketplace.json` structure and Codex
marketplace shape; `package.json` files list trimmed to paths that exist marketplace shape; rejects non-object JSON roots in catalog files

View File

@@ -45,13 +45,21 @@ function fail(message) {
failed = true; failed = true;
} }
function readJson(label, path) { function readJsonObject(label, path) {
let value;
try { try {
return JSON.parse(readFileSync(path, "utf8")); value = JSON.parse(readFileSync(path, "utf8"));
} catch (error) { } catch (error) {
fail(`${label} is not valid JSON: ${error.message}`); fail(`${label} is not valid JSON: ${error.message}`);
return null; return undefined;
} }
if (value == null || typeof value !== "object" || Array.isArray(value)) {
fail(`${label}: root must be a JSON object`);
return undefined;
}
return value;
} }
function requireNonEmptyString(label, field, value) { function requireNonEmptyString(label, field, value) {
@@ -79,8 +87,8 @@ for (const [label, path] of [
if (!existsSync(path)) { if (!existsSync(path)) {
continue; continue;
} }
const manifest = readJson(label, path); const manifest = readJsonObject(label, path);
if (manifest == null) { if (manifest === undefined) {
continue; continue;
} }
requireNonEmptyString(label, "name", manifest.name); requireNonEmptyString(label, "name", manifest.name);
@@ -94,7 +102,7 @@ for (const [label, path] of [
const packageJsonPath = join(root, "package.json"); const packageJsonPath = join(root, "package.json");
const packageName = existsSync(packageJsonPath) const packageName = existsSync(packageJsonPath)
? readJson("package.json", packageJsonPath)?.name ? readJsonObject("package.json", packageJsonPath)?.name
: null; : null;
function validateClaudeMarketplace(label, path) { function validateClaudeMarketplace(label, path) {
@@ -102,8 +110,8 @@ function validateClaudeMarketplace(label, path) {
return; return;
} }
const marketplace = readJson(label, path); const marketplace = readJsonObject(label, path);
if (marketplace == null) { if (marketplace === undefined) {
return; return;
} }
@@ -152,8 +160,8 @@ function validateCodexMarketplace(label, path) {
return; return;
} }
const marketplace = readJson(label, path); const marketplace = readJsonObject(label, path);
if (marketplace == null) { if (marketplace === undefined) {
return; return;
} }
@@ -209,8 +217,8 @@ validateCodexMarketplace(
const mcpPath = join(root, ".mcp.json"); const mcpPath = join(root, ".mcp.json");
if (existsSync(mcpPath)) { if (existsSync(mcpPath)) {
const mcpConfig = readJson(".mcp.json", mcpPath); const mcpConfig = readJsonObject(".mcp.json", mcpPath);
if (mcpConfig != null) { if (mcpConfig !== undefined) {
const servers = mcpConfig.mcpServers ?? {}; const servers = mcpConfig.mcpServers ?? {};
for (const [name, config] of Object.entries(servers)) { for (const [name, config] of Object.entries(servers)) {
if (config?.headers?.Authorization != null) { if (config?.headers?.Authorization != null) {