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:
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user