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
- Release checksums derived from `npm pack` tarball contents
- 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;
}
function readJson(label, path) {
function readJsonObject(label, path) {
let value;
try {
return JSON.parse(readFileSync(path, "utf8"));
value = JSON.parse(readFileSync(path, "utf8"));
} catch (error) {
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) {
@@ -79,8 +87,8 @@ for (const [label, path] of [
if (!existsSync(path)) {
continue;
}
const manifest = readJson(label, path);
if (manifest == null) {
const manifest = readJsonObject(label, path);
if (manifest === undefined) {
continue;
}
requireNonEmptyString(label, "name", manifest.name);
@@ -94,7 +102,7 @@ for (const [label, path] of [
const packageJsonPath = join(root, "package.json");
const packageName = existsSync(packageJsonPath)
? readJson("package.json", packageJsonPath)?.name
? readJsonObject("package.json", packageJsonPath)?.name
: null;
function validateClaudeMarketplace(label, path) {
@@ -102,8 +110,8 @@ function validateClaudeMarketplace(label, path) {
return;
}
const marketplace = readJson(label, path);
if (marketplace == null) {
const marketplace = readJsonObject(label, path);
if (marketplace === undefined) {
return;
}
@@ -152,8 +160,8 @@ function validateCodexMarketplace(label, path) {
return;
}
const marketplace = readJson(label, path);
if (marketplace == null) {
const marketplace = readJsonObject(label, path);
if (marketplace === undefined) {
return;
}
@@ -209,8 +217,8 @@ validateCodexMarketplace(
const mcpPath = join(root, ".mcp.json");
if (existsSync(mcpPath)) {
const mcpConfig = readJson(".mcp.json", mcpPath);
if (mcpConfig != null) {
const mcpConfig = readJsonObject(".mcp.json", mcpPath);
if (mcpConfig !== undefined) {
const servers = mcpConfig.mcpServers ?? {};
for (const [name, config] of Object.entries(servers)) {
if (config?.headers?.Authorization != null) {