@cyanheads/mcp-ts-core 0.6.14 → 0.6.15

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/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent Protocol
2
2
 
3
- **Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.6.14
3
+ **Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.6.15
4
4
  **npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) · **Docker:** [ghcr.io/cyanheads/mcp-ts-core](https://ghcr.io/cyanheads/mcp-ts-core)
5
5
 
6
6
  > **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never edit a file before reading it.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div align="center">
7
7
 
8
- [![Version](https://img.shields.io/badge/Version-0.6.14-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE)
8
+ [![Version](https://img.shields.io/badge/Version-0.6.15-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE)
9
9
 
10
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: Scaffolded devcheck passes green on a fresh `init` (depcheck wired up); security-pass skill v1.1 expands coverage to resources, prompts, HTTP deployment surface, sampling, roots, telemetry, and schema strictness
3
+ breaking: false
4
+ ---
5
+
6
+ # 0.6.15 — 2026-04-23
7
+
8
+ Fixes a broken first-run experience in scaffolded servers and ships a substantial expansion of the `security-pass` audit skill.
9
+
10
+ ## Fixed
11
+
12
+ - **`templates/package.json`** — added `"depcheck": "^1.4.7"` to `devDependencies`. `scripts/devcheck.ts` (copied verbatim into scaffolds) invokes `node_modules/.bin/depcheck` directly, but the template never declared the dependency — so `bun install && bun run devcheck` on a fresh `@cyanheads/mcp-ts-core init` project failed the Unused Dependencies step with `ENOENT`.
13
+ - **`templates/devcheck.config.json`** — appended `"depcheck"` to `depcheck.ignores`. `depcheck` is CLI-only (never `import`ed), so once added to `devDependencies` it self-flags as unused unless ignored. Resolves [#49](https://github.com/cyanheads/mcp-ts-core/issues/49).
14
+
15
+ ## Changed
16
+
17
+ - **`skills/security-pass/`** — bumped from v1.0 → v1.1. Broadened injection-surface coverage, added HTTP deployment surface as a first-class section, and strengthened several axes:
18
+ - **Axis 1 (injection vector)** now covers resource content, prompt templates, and definition metadata (`description`, `title`, `annotations`, `inputSchema` field descriptions). Adds checks for templated descriptions ("tool poisoning") and mid-session description mutation ("rug-pull").
19
+ - **New deployment-surface section** under "Build the map" for HTTP/SSE transports: bind address, Origin allowlist (DNS rebinding), session ID source and auth binding, unauthenticated route leakage, MCP Authorization spec compliance (PKCE, token `aud`, resource indicators).
20
+ - **Axis 3 (destructive ops)** — requires elicit responses to be schema-validated (the returned payload is LLM-mediated, not user-direct) and consent scoped to a concrete target, not generic.
21
+ - **Axis 5 (input sinks)** — adds sampling responses (`ctx.sample` result is untrusted input), roots-derived paths, schema strictness (`.strict()` on inputs, no `.passthrough()` / `.catchall()` on outputs), and ReDoS-safe regex/glob bounds.
22
+ - **Axis 7 (leakage back)** — broadened beyond `ctx.log` to `console.*`, OpenTelemetry span attributes, Sentry breadcrumbs, and constant-time comparison requirements for secret / token / HMAC equality checks.
23
+ - **Axis 8 (resource bounds)** — adds `JSON.parse` / Zod parse size+depth limits, per-tenant per-tool rate limits, and concurrency caps on long-running tools.
24
+ - **Quick sanity pass** — adds npm provenance check for new security-critical deps.
25
+ - **`fuzzTool`** guidance moved to Step 1 so it runs in parallel with manual axis walks, feeding Axis 5 / Axis 8 triage.
26
+ - Mirrored to `.agents/skills/security-pass/` and `.claude/skills/security-pass/` to clear the devcheck Skills Sync warning introduced with the sync check in 0.6.14.
@@ -1,4 +1,4 @@
1
- {"level":50,"time":1776980769962,"env":"testing","version":"0.0.0-test","pid":37808,"requestId":"OVT1K-CNGIB","timestamp":"2026-04-23T21:46:09.961Z","operation":"HandleToolRequest","input":{"message":"blocked"},"critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"0a31a16b8e1ef00f4861dc55307c97ca3c475ea7e0c056a72f9d9abc9e666565","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"0a31a16b8e1ef00f4861dc55307c97ca3c475ea7e0c056a72f9d9abc9e666565","toolName":"scoped_echo","input":{"message":"blocked"},"requestId":"OVT1K-CNGIB","timestamp":"2026-04-23T21:46:09.961Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:68:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:101:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1776980770606,"env":"testing","version":"0.6.14","pid":37812,"requestId":"VSJZ5-S5BH9","timestamp":"2026-04-23T21:46:10.606Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"VSJZ5-S5BH9","timestamp":"2026-04-23T21:46:10.606Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
- {"level":50,"time":1776980770622,"env":"testing","version":"0.6.14","pid":37812,"requestId":"9YIAG-QSYTE","timestamp":"2026-04-23T21:46:10.622Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"9YIAG-QSYTE","timestamp":"2026-04-23T21:46:10.622Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
- {"level":50,"time":1776980770626,"env":"testing","version":"0.6.14","pid":37812,"requestId":"V19S7-NN43O","timestamp":"2026-04-23T21:46:10.626Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"V19S7-NN43O","timestamp":"2026-04-23T21:46:10.626Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
1
+ {"level":50,"time":1776994404554,"env":"testing","version":"0.0.0-test","pid":90736,"requestId":"2HYYK-DP0GV","timestamp":"2026-04-24T01:33:24.553Z","operation":"HandleToolRequest","input":{"message":"blocked"},"critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"f334772c59555f74dab21cde1a66ce1ed8b6584137f26a0b2c522936e8f77d9e","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"f334772c59555f74dab21cde1a66ce1ed8b6584137f26a0b2c522936e8f77d9e","toolName":"scoped_echo","input":{"message":"blocked"},"requestId":"2HYYK-DP0GV","timestamp":"2026-04-24T01:33:24.553Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:68:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:101:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
+ {"level":50,"time":1776994404869,"env":"testing","version":"0.6.15","pid":90768,"requestId":"SM09A-GLQJP","timestamp":"2026-04-24T01:33:24.868Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"SM09A-GLQJP","timestamp":"2026-04-24T01:33:24.868Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
+ {"level":50,"time":1776994404882,"env":"testing","version":"0.6.15","pid":90768,"requestId":"NWSZB-RSSE7","timestamp":"2026-04-24T01:33:24.882Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"NWSZB-RSSE7","timestamp":"2026-04-24T01:33:24.882Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
+ {"level":50,"time":1776994404886,"env":"testing","version":"0.6.15","pid":90768,"requestId":"U3CD0-HCUAL","timestamp":"2026-04-24T01:33:24.886Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"U3CD0-HCUAL","timestamp":"2026-04-24T01:33:24.886Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
@@ -1,4 +1,4 @@
1
- {"level":50,"time":1776980769962,"env":"testing","version":"0.0.0-test","pid":37808,"requestId":"OVT1K-CNGIB","timestamp":"2026-04-23T21:46:09.961Z","operation":"HandleToolRequest","input":{"message":"blocked"},"critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"0a31a16b8e1ef00f4861dc55307c97ca3c475ea7e0c056a72f9d9abc9e666565","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"0a31a16b8e1ef00f4861dc55307c97ca3c475ea7e0c056a72f9d9abc9e666565","toolName":"scoped_echo","input":{"message":"blocked"},"requestId":"OVT1K-CNGIB","timestamp":"2026-04-23T21:46:09.961Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:68:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:101:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1776980770606,"env":"testing","version":"0.6.14","pid":37812,"requestId":"VSJZ5-S5BH9","timestamp":"2026-04-23T21:46:10.606Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"VSJZ5-S5BH9","timestamp":"2026-04-23T21:46:10.606Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
- {"level":50,"time":1776980770622,"env":"testing","version":"0.6.14","pid":37812,"requestId":"9YIAG-QSYTE","timestamp":"2026-04-23T21:46:10.622Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"9YIAG-QSYTE","timestamp":"2026-04-23T21:46:10.622Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
- {"level":50,"time":1776980770626,"env":"testing","version":"0.6.14","pid":37812,"requestId":"V19S7-NN43O","timestamp":"2026-04-23T21:46:10.626Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"V19S7-NN43O","timestamp":"2026-04-23T21:46:10.626Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
1
+ {"level":50,"time":1776994404554,"env":"testing","version":"0.0.0-test","pid":90736,"requestId":"2HYYK-DP0GV","timestamp":"2026-04-24T01:33:24.553Z","operation":"HandleToolRequest","input":{"message":"blocked"},"critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"f334772c59555f74dab21cde1a66ce1ed8b6584137f26a0b2c522936e8f77d9e","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"f334772c59555f74dab21cde1a66ce1ed8b6584137f26a0b2c522936e8f77d9e","toolName":"scoped_echo","input":{"message":"blocked"},"requestId":"2HYYK-DP0GV","timestamp":"2026-04-24T01:33:24.553Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:68:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:101:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
+ {"level":50,"time":1776994404869,"env":"testing","version":"0.6.15","pid":90768,"requestId":"SM09A-GLQJP","timestamp":"2026-04-24T01:33:24.868Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"SM09A-GLQJP","timestamp":"2026-04-24T01:33:24.868Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
+ {"level":50,"time":1776994404882,"env":"testing","version":"0.6.15","pid":90768,"requestId":"NWSZB-RSSE7","timestamp":"2026-04-24T01:33:24.882Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"NWSZB-RSSE7","timestamp":"2026-04-24T01:33:24.882Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
+ {"level":50,"time":1776994404886,"env":"testing","version":"0.6.15","pid":90768,"requestId":"U3CD0-HCUAL","timestamp":"2026-04-24T01:33:24.886Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"U3CD0-HCUAL","timestamp":"2026-04-24T01:33:24.886Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:119:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:168:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "mcpName": "io.github.cyanheads/mcp-ts-core",
5
5
  "description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
6
6
  "main": "dist/core/index.js",
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: security-pass
3
3
  description: >
4
- Review an MCP server for common security gaps: tool output as LLM injection, scope blast radius, destructive ops without consent, upstream auth shape, input sinks, tenant isolation, leaked data, unbounded resources. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
4
+ Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / sampling / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: audit
10
10
  ---
@@ -35,33 +35,53 @@ Gather before starting. Ask if unclear:
35
35
 
36
36
  ### 1. Build the map
37
37
 
38
- Surface what you're auditing before diving in.
38
+ Surface what you're auditing before diving in. Paths below assume the `mcp-ts-core` layout — adjust to your repo.
39
39
 
40
40
  ```bash
41
41
  find src/mcp-server/tools/definitions -name "*.tool.ts" | sort
42
42
  find src/mcp-server/resources/definitions -name "*.resource.ts" 2>/dev/null | sort
43
+ find src/mcp-server/prompts/definitions -name "*.prompt.ts" 2>/dev/null | sort
43
44
  find src/services -maxdepth 1 -mindepth 1 -type d | sort
44
45
  ```
45
46
 
46
- Note: tool count, auth mode, storage provider, upstream APIs, which tools have `destructiveHint`, which services hold module-scope state.
47
+ Note: tool / resource / prompt counts, auth mode, storage provider, upstream APIs, which tools have `destructiveHint`, which handlers use `ctx.sample` or `ctx.elicit`, which services hold module-scope state, whether the server reads `roots`.
48
+
49
+ **If transport is streamable HTTP or SSE**, also capture:
50
+
51
+ - Bind address (`127.0.0.1` for local, or `0.0.0.0` / public interface?)
52
+ - Origin allowlist (DNS rebinding mitigation) — configured, or wildcard / missing?
53
+ - Session ID source (framework CSPRNG, or builder-supplied?) and binding to auth identity
54
+ - Any unauthenticated routes (`/healthz`, `/sse`, metadata endpoints) — do they leak tool lists or tenant hints?
55
+ - MCP Authorization spec: if implemented, PKCE enforced, token audience (`aud`) checked, resource indicators used
47
56
 
48
57
  Use `TaskCreate` — one task per axis. Mark complete as you go.
49
58
 
59
+ **Run `fuzzTool` in parallel.** `@cyanheads/mcp-ts-core/testing/fuzz` catches crashes, memory leaks, and prototype pollution automatically on each tool — start it now so results are ready when you reach Axis 5.
60
+
50
61
  ### 2. Walk the eight axes
51
62
 
52
- #### Axis 1 — Tool output as LLM injection vector
63
+ #### Axis 1 — LLM-facing surfaces as injection vector
64
+
65
+ Anything the server sends to the client that reaches the LLM's context is a potential injection surface: tool output, resource content, prompt text, and the metadata the LLM reads to decide what to call. Relayed upstream content (tickets, scraped text, emails, DB rows) can carry adversarial instructions even when your code is honest.
53
66
 
54
- Tool output enters the next LLM turn. Relayed upstream content (tickets, scraped text, emails, DB rows) can contain adversarial instructions even when your code is honest.
67
+ **Look in:**
55
68
 
56
- **Look in:** every `*.tool.ts` — `output` schema + `format()`.
69
+ - Every `*.tool.ts` — `output` schema + `format()`
70
+ - Every `*.resource.ts` — content returned from `resources/read`
71
+ - Every `*.prompt.ts` — templated message content
72
+ - Every definition file — `description`, `title`, `annotations`, and `inputSchema` field descriptions (templated from untrusted data?)
57
73
 
58
74
  **Check:**
59
75
 
60
- - Handlers that return raw upstream text without structural framing?
76
+ - Handlers that return raw upstream text / DB rows without structural framing?
61
77
  - Does `format()` wrap untrusted content in delimiters (blockquote, fenced code, `<data>` tags)?
62
78
  - Output schema distinguishes "data" fields from free-form text?
79
+ - Resource content (`resources/read`) framed the same way tool output is?
80
+ - Prompt templates interpolate untrusted data without escaping — treating tenant-controlled strings as trusted instructions?
81
+ - Tool / resource / prompt **descriptions** templated from runtime data? Static strings are safer; templated descriptions enable "tool poisoning" (adversarial metadata steering the LLM toward a dangerous tool).
82
+ - Descriptions mutated mid-session? Rug-pull surface: client approved the v1 description, server now advertises v2 behavior.
63
83
 
64
- **Smell:** `return { body: await fetch(url).then(r => r.text()) }` rendered directly in `format()`.
84
+ **Smell:** `return { body: await fetch(url).then(r => r.text()) }` rendered directly in `format()`. Or: `description: \`Look up ${tenant.customLabel}\`` where `customLabel` is tenant-supplied.
65
85
 
66
86
  #### Axis 2 — Scope granularity
67
87
 
@@ -96,8 +116,10 @@ grep -rn "ctx.elicit" src/mcp-server/tools/definitions/
96
116
 
97
117
  - Each destructive handler calls `ctx.elicit` before the side effect?
98
118
  - Fallback when client doesn't support elicit — refuses, not silently proceeds?
119
+ - Elicit **response** validated against a Zod schema before use? The returned payload is LLM-mediated, not user-direct — "user confirmed" does not mean "user authored these exact fields."
120
+ - Consent is scoped to the specific target (e.g., record ID rendered in the prompt), not a generic "proceed?"
99
121
 
100
- **Smell:** `destructiveHint: true` file with no `ctx.elicit?.(...)` in it.
122
+ **Smell:** `destructiveHint: true` file with no `ctx.elicit?.(...)` in it. Or: `const { confirmed } = await ctx.elicit(...)` without a schema — `confirmed` could be anything.
101
123
 
102
124
  #### Axis 4 — Upstream auth shape
103
125
 
@@ -116,7 +138,7 @@ What credentials the server holds, and the blast radius if one leaks.
116
138
 
117
139
  #### Axis 5 — Input sinks
118
140
 
119
- LLM-supplied inputs feel internal but aren't. Classic sinks apply, amplified.
141
+ LLM-supplied inputs feel internal but aren't. Classic sinks apply, amplified. Sampling responses and roots-derived paths are MCP-specific sinks that look internal but carry LLM/client trust.
120
142
 
121
143
  **Look in:** all handlers.
122
144
 
@@ -132,17 +154,30 @@ grep -rnE "\b(exec|spawn|execSync|spawnSync)\b" src/
132
154
 
133
155
  # Merges — prototype pollution
134
156
  grep -rn "Object.assign\b\|structuredClone" src/
157
+
158
+ # Sampling — LLM-generated content flowing back into server logic
159
+ grep -rn "ctx.sample\|sampling/createMessage" src/
160
+
161
+ # Roots — client-shared filesystem
162
+ grep -rn "roots/list\|ctx.roots" src/
163
+
164
+ # Schema laxity — fields sneaking past validation
165
+ grep -rn "\.passthrough()\|\.catchall(" src/mcp-server/
135
166
  ```
136
167
 
137
168
  **Check:**
138
169
 
139
170
  - URL-taking tools block private IPs, `file://`, `ftp://`, `localhost`, DNS rebind?
140
171
  - Path-taking tools canonicalize (`path.resolve` + assert `startsWith(root + sep)`)?
172
+ - Roots-derived paths: resolved result stays within *one* declared root (iterate and assert), not assumed-safe because "the client said so"?
141
173
  - Shell-using tools use an allowlist (never string-concat)?
142
- - Regex / query / expression inputs bounded?
174
+ - Regex / glob / filter inputs bounded (length cap, complexity limits, execution timeout) — ReDoS-safe?
143
175
  - User-JSON merges reject `__proto__`, `constructor`, `prototype` keys?
176
+ - **Input schemas `.strict()`** — unknown fields rejected, not silently passed to downstream code that destructures with `...rest`?
177
+ - **Output schemas without `.passthrough()` / `.catchall()`** — no accidental exfiltration of fields your schema didn't declare?
178
+ - Sampling responses (`ctx.sample` result) treated as untrusted input — schema-validated before reaching any other sink, never concatenated into prompts, shells, or queries?
144
179
 
145
- **Smell:** `z.string().url()` with no allowlist; `readFile(input.path)` with no canonicalization.
180
+ **Smell:** `z.string().url()` with no allowlist; `readFile(input.path)` with no canonicalization; `await ctx.sample(...)` result interpolated into a shell, SQL, or URL.
146
181
 
147
182
  #### Axis 6 — Tenant isolation
148
183
 
@@ -166,13 +201,15 @@ grep -rn "^let " src/services/
166
201
 
167
202
  #### Axis 7 — Leakage back
168
203
 
169
- What accidentally reaches the LLM, user, or logs.
204
+ What accidentally reaches the LLM, user, or observability sinks.
170
205
 
171
- **Look in:** `throw new McpError(...)` sites, `McpError.data` fields, output schemas, `ctx.log.*` calls.
206
+ **Look in:** `throw new McpError(...)` sites, `McpError.data` fields, output schemas, and every logging / telemetry surface — not just `ctx.log`.
172
207
 
173
208
  ```bash
174
209
  grep -rn "new McpError" src/
175
- grep -rn "ctx.log\." src/
210
+ grep -rnE "\b(ctx\.log|console\.(log|info|warn|error|debug)|logger\.)" src/
211
+ grep -rnE "(Sentry\.|captureException|setTag|setContext|addBreadcrumb)" src/
212
+ grep -rnE "(setAttribute|setAttributes|span\.)" src/ # OpenTelemetry
176
213
  ```
177
214
 
178
215
  **Check:**
@@ -181,18 +218,22 @@ grep -rn "ctx.log\." src/
181
218
  - Output schemas include token prefixes, internal IDs, session identifiers?
182
219
  - `format()` renders fields that shouldn't leave the server?
183
220
  - `ctx.log.info(msg, body)` where `body` is the raw request (may contain secrets)?
221
+ - `console.*` calls near auth / token / request-body handling — bypasses structured redaction?
222
+ - OpenTelemetry span attributes / Sentry breadcrumbs carry tokens, PII, or full request bodies?
223
+ - Secret / token / HMAC comparisons use `===` or `==` instead of constant-time (`timingSafeEqual` / `crypto.timingSafeEqual`) — leaks length and prefix via timing?
184
224
 
185
- **Smell:** `throw new McpError(code, upstream.message, { raw: upstream.body })`.
225
+ **Smell:** `throw new McpError(code, upstream.message, { raw: upstream.body })`. Or: `if (apiKey === expected)` on a request-auth path.
186
226
 
187
227
  #### Axis 8 — Resource bounds
188
228
 
189
229
  Unbounded = DoS of self, upstream, or the LLM's context window (billing-DoS is real).
190
230
 
191
- **Look in:** handlers with loops, pagination, retries.
231
+ **Look in:** handlers with loops, pagination, retries, or inputs that feed `JSON.parse` / schema validation.
192
232
 
193
233
  ```bash
194
234
  grep -rnE "while\s*\(|for\s*\(.*of" src/mcp-server/tools/definitions/
195
235
  grep -rn "cursor\|nextPage\|paginate" src/
236
+ grep -rn "JSON.parse\b" src/
196
237
  ```
197
238
 
198
239
  **Check:**
@@ -201,8 +242,11 @@ grep -rn "cursor\|nextPage\|paginate" src/
201
242
  - Retry logic has max attempts + exponential backoff?
202
243
  - Output size proportional to input — is there a ceiling?
203
244
  - Tools callable in a loop fail-fast on degenerate input (empty string, `0`, `null`)?
245
+ - `JSON.parse` / Zod `.parse()` inputs have a size + nesting-depth limit applied before parse?
246
+ - **Per-tenant per-tool** call rate limit (a single tenant looping `delete_record` 10k/sec hits you before it hits upstream)?
247
+ - Concurrency cap on long-running tools so one tenant can't starve the event loop?
204
248
 
205
- **Smell:** `while (cursor) { results.push(...); cursor = next; }` with no max count.
249
+ **Smell:** `while (cursor) { results.push(...); cursor = next; }` with no max count. Or: `JSON.parse(await req.text())` with no `Content-Length` check upstream.
206
250
 
207
251
  ### 3. Quick sanity pass
208
252
 
@@ -210,11 +254,11 @@ Fast, sometimes high-leverage. Outside the eight axes.
210
254
 
211
255
  - `bun audit` — any direct high/critical?
212
256
  - `package.json` — `postinstall` / lifecycle scripts on added deps?
257
+ - New deps have npm provenance? `npm view <pkg> --json | jq .dist.attestations` — missing attestation on a security-critical dep is a yellow flag
213
258
  - `.env.example` — placeholder values only, never real?
214
259
  - Server-specific `ConfigSchema` — fails loudly on missing required keys (not silent defaults)?
215
260
  - Any `process.env.*` reads outside the config parser (bypasses validation)?
216
-
217
- **Automated assist.** `fuzzTool` from `@cyanheads/mcp-ts-core/testing/fuzz` catches crashes, memory leaks, and prototype pollution automatically — run it on each tool as a cheap first pass.
261
+ - Collect `fuzzTool` results from Step 1 — triage crashes / leaks as Axis 5 / Axis 8 findings.
218
262
 
219
263
  ### 4. Report
220
264
 
@@ -264,14 +308,16 @@ End with:
264
308
  ## Checklist
265
309
 
266
310
  - [ ] Scope confirmed (whole server / module / diff)
267
- - [ ] Map built: tool count, services, upstream APIs, auth mode
268
- - [ ] Axis 1 tool output framing reviewed
311
+ - [ ] Map built: tools / resources / prompts, services, upstream APIs, auth mode, sampling / elicit / roots usage
312
+ - [ ] Deployment surface reviewed (if HTTP): bind address, Origin allowlist, session ID, unauth routes, auth-spec compliance
313
+ - [ ] `fuzzTool` started in parallel
314
+ - [ ] Axis 1 — LLM-facing surfaces (tool / resource / prompt output + descriptions) framed and static
269
315
  - [ ] Axis 2 — scope granularity audited
270
- - [ ] Axis 3 — destructive ops verified to elicit
316
+ - [ ] Axis 3 — destructive ops verified to elicit, elicit response schema-validated
271
317
  - [ ] Axis 4 — upstream auth + token passthrough reviewed
272
- - [ ] Axis 5 — input sinks (URL / path / shell / proto) checked
318
+ - [ ] Axis 5 — input sinks (URL / path / roots / shell / proto / sampling / schema strictness / ReDoS) checked
273
319
  - [ ] Axis 6 — tenant isolation: module-scope state swept
274
- - [ ] Axis 7 — leakage back: errors / outputs / logs reviewed
275
- - [ ] Axis 8 — resource bounds on loops / retries / pagination
276
- - [ ] Quick sanity pass: `bun audit`, lifecycle scripts, `.env.example`, config validation
320
+ - [ ] Axis 7 — leakage back: errors / outputs / `ctx.log` / `console.*` / telemetry / constant-time comparisons
321
+ - [ ] Axis 8 — resource bounds on loops / retries / pagination / parse size+depth / per-tenant rate
322
+ - [ ] Quick sanity pass: `bun audit`, lifecycle scripts, `.env.example`, config validation, new-dep provenance
277
323
  - [ ] Report: summary → grouped findings → numbered options
@@ -5,6 +5,7 @@
5
5
  "pino-pretty",
6
6
  "typescript",
7
7
  "tsc-alias",
8
+ "depcheck",
8
9
  "@cyanheads/mcp-ts-core"
9
10
  ],
10
11
  "ignorePatterns": []
@@ -57,6 +57,7 @@
57
57
  "devDependencies": {
58
58
  "@biomejs/biome": "^2.4.7",
59
59
  "@types/node": "^25.6.0",
60
+ "depcheck": "^1.4.7",
60
61
  "ignore": "^7.0.5",
61
62
  "tsc-alias": "^1.8.16",
62
63
  "tsx": "^4.19.0",