@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.
@@ -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 content = res.body?.result?.content;
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
- issues.push(`Payload "${payload}" ${label} (output: ${text.substring(0, 100)})`);
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 content = res.body?.result?.content;
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
- issues.push(`Payload "${payload}" ${label} (output: ${text.substring(0, 100)})`);
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) {
@@ -2,7 +2,7 @@ import {
2
2
  SPEC_BASE,
3
3
  TEST_DEFINITIONS,
4
4
  runComplianceSuite
5
- } from "../chunk-CH2E27X5.js";
5
+ } from "../chunk-FKTEFLK5.js";
6
6
 
7
7
  // src/mcp/server.ts
8
8
  import { existsSync, readFileSync } from "fs";
package/dist/runner.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  parseSSEResponse,
9
9
  previewTests,
10
10
  runComplianceSuite
11
- } from "./chunk-CH2E27X5.js";
11
+ } from "./chunk-FKTEFLK5.js";
12
12
  export {
13
13
  SPEC_BASE,
14
14
  SPEC_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcp-compliance",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "CLI tool and MCP server that tests MCP servers for spec compliance",
5
5
  "license": "MIT",
6
6
  "author": "Yaw Labs <contact@yaw.sh> (https://yaw.sh)",