@yawlabs/mcp-compliance 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CH2E27X5.js → chunk-FKTEFLK5.js} +40 -6
- package/dist/index.js +40 -6
- package/dist/mcp/server.js +1 -1
- package/dist/runner.js +1 -1
- package/package.json +1 -1
|
@@ -1228,14 +1228,20 @@ var STDIO_INCOMPATIBLE_IDS = /* @__PURE__ */ new Set([
|
|
|
1228
1228
|
"error-parse-code",
|
|
1229
1229
|
"error-invalid-request-code",
|
|
1230
1230
|
// Security tests that are inherently HTTP-layer (auth headers,
|
|
1231
|
-
// sessions, CORS, TLS, rate limits, RFC 9728 metadata).
|
|
1231
|
+
// sessions, CORS, TLS, rate limits, RFC 9728 metadata). For stdio
|
|
1232
|
+
// servers these don't apply — the parent process owns the trust
|
|
1233
|
+
// boundary, not the server.
|
|
1232
1234
|
"security-tls-required",
|
|
1233
1235
|
"security-oauth-metadata",
|
|
1234
1236
|
"security-token-in-uri",
|
|
1235
1237
|
"security-rate-limiting",
|
|
1236
1238
|
"security-cors-headers",
|
|
1237
1239
|
"security-origin-validation",
|
|
1238
|
-
"security-session-not-auth"
|
|
1240
|
+
"security-session-not-auth",
|
|
1241
|
+
"security-auth-required",
|
|
1242
|
+
"security-auth-malformed",
|
|
1243
|
+
"security-www-authenticate",
|
|
1244
|
+
"security-session-entropy"
|
|
1239
1245
|
]);
|
|
1240
1246
|
function supportsTransport(def, kind) {
|
|
1241
1247
|
if (!def) return true;
|
|
@@ -1299,8 +1305,11 @@ async function runComplianceSuite(target, options = {}) {
|
|
|
1299
1305
|
return !options.skip.includes(category) && !options.skip.includes(id);
|
|
1300
1306
|
}
|
|
1301
1307
|
return true;
|
|
1308
|
+
}, looksRejected2 = function(text, isErrorFlag) {
|
|
1309
|
+
if (isErrorFlag) return true;
|
|
1310
|
+
return REJECTION_PATTERNS.some((p) => p.test(text));
|
|
1302
1311
|
};
|
|
1303
|
-
var buildHeaders = buildHeaders2, shouldRun = shouldRun2;
|
|
1312
|
+
var buildHeaders = buildHeaders2, shouldRun = shouldRun2, looksRejected = looksRejected2;
|
|
1304
1313
|
const backendUrl = resolvedTarget.type === "http" ? resolvedTarget.url : "";
|
|
1305
1314
|
const userHeaders = resolvedTarget.type === "http" ? resolvedTarget.headers ?? options.headers ?? {} : {};
|
|
1306
1315
|
const displayUrl = resolvedTarget.type === "http" ? resolvedTarget.url : `stdio:${resolvedTarget.command}${resolvedTarget.args?.length ? ` ${resolvedTarget.args.join(" ")}` : ""}`;
|
|
@@ -3284,25 +3293,50 @@ async function runComplianceSuite(target, options = {}) {
|
|
|
3284
3293
|
}
|
|
3285
3294
|
}
|
|
3286
3295
|
);
|
|
3296
|
+
const REJECTION_PATTERNS = [
|
|
3297
|
+
/access denied/i,
|
|
3298
|
+
/permission denied/i,
|
|
3299
|
+
/not allowed/i,
|
|
3300
|
+
/not permitted/i,
|
|
3301
|
+
/outside (allowed|permitted)/i,
|
|
3302
|
+
/forbidden/i,
|
|
3303
|
+
/unauthorized/i,
|
|
3304
|
+
/invalid (path|input|argument|parameter|request)/i,
|
|
3305
|
+
/(payload|request) (rejected|blocked|refused)/i,
|
|
3306
|
+
/enoent|eacces|eperm/i,
|
|
3307
|
+
/sandbox(ed)?/i,
|
|
3308
|
+
/(no such file|file not found)/i,
|
|
3309
|
+
/\binvalid\b.*\b(input|json|argument|parameter|character)/i
|
|
3310
|
+
];
|
|
3287
3311
|
async function runInjectionTest(toolName, paramName, payloads, detectPattern, label) {
|
|
3288
3312
|
const issues = [];
|
|
3313
|
+
let defended = 0;
|
|
3289
3314
|
for (const payload of payloads) {
|
|
3290
3315
|
try {
|
|
3291
3316
|
const res = await rpc("tools/call", { name: toolName, arguments: { [paramName]: payload } });
|
|
3292
|
-
const
|
|
3317
|
+
const result = res.body?.result;
|
|
3318
|
+
const content = result?.content;
|
|
3319
|
+
const isErrorFlag = result?.isError === true;
|
|
3293
3320
|
if (Array.isArray(content)) {
|
|
3294
3321
|
const text = content.map((c) => c.text || "").join(" ");
|
|
3295
3322
|
if (detectPattern.test(text)) {
|
|
3296
|
-
|
|
3323
|
+
if (looksRejected2(text, isErrorFlag)) {
|
|
3324
|
+
defended++;
|
|
3325
|
+
} else {
|
|
3326
|
+
issues.push(`Payload "${payload}" ${label} (output: ${text.substring(0, 100)})`);
|
|
3327
|
+
}
|
|
3328
|
+
} else {
|
|
3329
|
+
defended++;
|
|
3297
3330
|
}
|
|
3298
3331
|
}
|
|
3299
3332
|
} catch {
|
|
3333
|
+
defended++;
|
|
3300
3334
|
}
|
|
3301
3335
|
}
|
|
3302
3336
|
if (issues.length > 0) return { passed: false, details: issues.join("; ") };
|
|
3303
3337
|
return {
|
|
3304
3338
|
passed: true,
|
|
3305
|
-
details: `Tested ${payloads.length} payloads against ${toolName}.${paramName} \u2014 no ${label.split(" ")[0]} detected`
|
|
3339
|
+
details: defended === payloads.length ? `Tested ${payloads.length} payloads against ${toolName}.${paramName} \u2014 server defended (rejected or sanitized)` : `Tested ${payloads.length} payloads against ${toolName}.${paramName} \u2014 no ${label.split(" ")[0]} detected`
|
|
3306
3340
|
};
|
|
3307
3341
|
}
|
|
3308
3342
|
if (toolNames.length > 0) {
|
package/dist/index.js
CHANGED
|
@@ -1372,14 +1372,20 @@ var STDIO_INCOMPATIBLE_IDS = /* @__PURE__ */ new Set([
|
|
|
1372
1372
|
"error-parse-code",
|
|
1373
1373
|
"error-invalid-request-code",
|
|
1374
1374
|
// Security tests that are inherently HTTP-layer (auth headers,
|
|
1375
|
-
// sessions, CORS, TLS, rate limits, RFC 9728 metadata).
|
|
1375
|
+
// sessions, CORS, TLS, rate limits, RFC 9728 metadata). For stdio
|
|
1376
|
+
// servers these don't apply — the parent process owns the trust
|
|
1377
|
+
// boundary, not the server.
|
|
1376
1378
|
"security-tls-required",
|
|
1377
1379
|
"security-oauth-metadata",
|
|
1378
1380
|
"security-token-in-uri",
|
|
1379
1381
|
"security-rate-limiting",
|
|
1380
1382
|
"security-cors-headers",
|
|
1381
1383
|
"security-origin-validation",
|
|
1382
|
-
"security-session-not-auth"
|
|
1384
|
+
"security-session-not-auth",
|
|
1385
|
+
"security-auth-required",
|
|
1386
|
+
"security-auth-malformed",
|
|
1387
|
+
"security-www-authenticate",
|
|
1388
|
+
"security-session-entropy"
|
|
1383
1389
|
]);
|
|
1384
1390
|
function supportsTransport(def, kind) {
|
|
1385
1391
|
if (!def) return true;
|
|
@@ -1443,8 +1449,11 @@ async function runComplianceSuite(target, options = {}) {
|
|
|
1443
1449
|
return !options.skip.includes(category) && !options.skip.includes(id);
|
|
1444
1450
|
}
|
|
1445
1451
|
return true;
|
|
1452
|
+
}, looksRejected2 = function(text, isErrorFlag) {
|
|
1453
|
+
if (isErrorFlag) return true;
|
|
1454
|
+
return REJECTION_PATTERNS.some((p) => p.test(text));
|
|
1446
1455
|
};
|
|
1447
|
-
var buildHeaders = buildHeaders2, shouldRun = shouldRun2;
|
|
1456
|
+
var buildHeaders = buildHeaders2, shouldRun = shouldRun2, looksRejected = looksRejected2;
|
|
1448
1457
|
const backendUrl = resolvedTarget.type === "http" ? resolvedTarget.url : "";
|
|
1449
1458
|
const userHeaders = resolvedTarget.type === "http" ? resolvedTarget.headers ?? options.headers ?? {} : {};
|
|
1450
1459
|
const displayUrl = resolvedTarget.type === "http" ? resolvedTarget.url : `stdio:${resolvedTarget.command}${resolvedTarget.args?.length ? ` ${resolvedTarget.args.join(" ")}` : ""}`;
|
|
@@ -3428,25 +3437,50 @@ async function runComplianceSuite(target, options = {}) {
|
|
|
3428
3437
|
}
|
|
3429
3438
|
}
|
|
3430
3439
|
);
|
|
3440
|
+
const REJECTION_PATTERNS = [
|
|
3441
|
+
/access denied/i,
|
|
3442
|
+
/permission denied/i,
|
|
3443
|
+
/not allowed/i,
|
|
3444
|
+
/not permitted/i,
|
|
3445
|
+
/outside (allowed|permitted)/i,
|
|
3446
|
+
/forbidden/i,
|
|
3447
|
+
/unauthorized/i,
|
|
3448
|
+
/invalid (path|input|argument|parameter|request)/i,
|
|
3449
|
+
/(payload|request) (rejected|blocked|refused)/i,
|
|
3450
|
+
/enoent|eacces|eperm/i,
|
|
3451
|
+
/sandbox(ed)?/i,
|
|
3452
|
+
/(no such file|file not found)/i,
|
|
3453
|
+
/\binvalid\b.*\b(input|json|argument|parameter|character)/i
|
|
3454
|
+
];
|
|
3431
3455
|
async function runInjectionTest(toolName, paramName, payloads, detectPattern, label) {
|
|
3432
3456
|
const issues = [];
|
|
3457
|
+
let defended = 0;
|
|
3433
3458
|
for (const payload of payloads) {
|
|
3434
3459
|
try {
|
|
3435
3460
|
const res = await rpc("tools/call", { name: toolName, arguments: { [paramName]: payload } });
|
|
3436
|
-
const
|
|
3461
|
+
const result = res.body?.result;
|
|
3462
|
+
const content = result?.content;
|
|
3463
|
+
const isErrorFlag = result?.isError === true;
|
|
3437
3464
|
if (Array.isArray(content)) {
|
|
3438
3465
|
const text = content.map((c) => c.text || "").join(" ");
|
|
3439
3466
|
if (detectPattern.test(text)) {
|
|
3440
|
-
|
|
3467
|
+
if (looksRejected2(text, isErrorFlag)) {
|
|
3468
|
+
defended++;
|
|
3469
|
+
} else {
|
|
3470
|
+
issues.push(`Payload "${payload}" ${label} (output: ${text.substring(0, 100)})`);
|
|
3471
|
+
}
|
|
3472
|
+
} else {
|
|
3473
|
+
defended++;
|
|
3441
3474
|
}
|
|
3442
3475
|
}
|
|
3443
3476
|
} catch {
|
|
3477
|
+
defended++;
|
|
3444
3478
|
}
|
|
3445
3479
|
}
|
|
3446
3480
|
if (issues.length > 0) return { passed: false, details: issues.join("; ") };
|
|
3447
3481
|
return {
|
|
3448
3482
|
passed: true,
|
|
3449
|
-
details: `Tested ${payloads.length} payloads against ${toolName}.${paramName} \u2014 no ${label.split(" ")[0]} detected`
|
|
3483
|
+
details: defended === payloads.length ? `Tested ${payloads.length} payloads against ${toolName}.${paramName} \u2014 server defended (rejected or sanitized)` : `Tested ${payloads.length} payloads against ${toolName}.${paramName} \u2014 no ${label.split(" ")[0]} detected`
|
|
3450
3484
|
};
|
|
3451
3485
|
}
|
|
3452
3486
|
if (toolNames.length > 0) {
|
package/dist/mcp/server.js
CHANGED
package/dist/runner.js
CHANGED
package/package.json
CHANGED