{"version":3,"file":"index.mjs","names":["#handler","#policy","#hardened"],"sources":["../../src/security/mcp-request-validation.ts"],"sourcesContent":["import type { McpHandlerRequestOptions } from \"@modelcontextprotocol/server\";\nimport type { McpFetchHandler } from \"../auth/mcp-resource-server.ts\";\nimport { hardenMcpFetch, resolveMcpHttpSecurity } from \"./mcp-http-security.ts\";\nimport type { McpCorsOptions, McpHttpSecurityPolicy } from \"./mcp-http-security.ts\";\n\nexport interface McpRequestValidationOptions {\n\t/** Hostnames only; do not include ports or schemes. */\n\treadonly allowedHostnames: readonly string[];\n\t/** Origin hostnames only. A missing Origin header is accepted by the official validator. */\n\treadonly allowedOriginHostnames: readonly string[];\n\t/** CORS handling for admitted origins; defaults to on (origin validation is always on here). */\n\treadonly cors?: McpCorsOptions | boolean;\n\t/** Request-body byte cap; defaults to 1 MiB, matching the runtime. `false` disables it. */\n\treadonly maxBodyBytes?: number | false;\n}\n\n/**\n * Explicit Host and Origin validation ahead of an MCP fetch handler. This\n * facade owns the full pre-dispatch posture — host/origin validation, the body\n * cap, and CORS decoration — so composing it never silently drops the runtime's\n * default protections for the requests it admits.\n */\nexport class McpValidatedServer implements McpFetchHandler {\n\treadonly notify: McpFetchHandler[\"notify\"];\n\treadonly bus: McpFetchHandler[\"bus\"];\n\n\treadonly #handler: McpFetchHandler;\n\treadonly #policy: McpHttpSecurityPolicy;\n\treadonly #hardened: ReturnType<typeof hardenMcpFetch>;\n\n\tconstructor(handler: McpFetchHandler, options: McpRequestValidationOptions) {\n\t\tthis.#handler = handler;\n\t\t// Keep the historical non-empty-allowlist contract, then delegate to the\n\t\t// shared hardened pipeline for the actual gating.\n\t\tconst allowedHostnames = normalizeAllowlist(options.allowedHostnames, \"allowedHostnames\");\n\t\tconst allowedOriginHostnames = normalizeAllowlist(\n\t\t\toptions.allowedOriginHostnames,\n\t\t\t\"allowedOriginHostnames\",\n\t\t);\n\t\tthis.#policy = resolveMcpHttpSecurity({\n\t\t\tallowedHostnames,\n\t\t\tallowedOriginHostnames,\n\t\t\t...(options.cors === undefined ? {} : { cors: options.cors }),\n\t\t\t...(options.maxBodyBytes === undefined ? {} : { maxBodyBytes: options.maxBodyBytes }),\n\t\t});\n\t\tthis.#hardened = hardenMcpFetch(handler, this.#policy);\n\t\tthis.notify = handler.notify;\n\t\tthis.bus = handler.bus;\n\t}\n\n\tget policy(): McpHttpSecurityPolicy {\n\t\treturn this.#policy;\n\t}\n\n\tfetch(request: Request, options?: McpHandlerRequestOptions): Promise<Response> {\n\t\treturn this.#hardened.fetch(request, options);\n\t}\n\n\tclose(): Promise<void> {\n\t\treturn this.#handler.close();\n\t}\n}\n\nfunction normalizeAllowlist(values: readonly string[], field: string): string[] {\n\tif (values.length === 0) throw new TypeError(`${field} cannot be empty.`);\n\treturn values.map((value, index) => {\n\t\tconst normalized = value.trim().toLowerCase();\n\t\tif (normalized.length === 0) {\n\t\t\tthrow new TypeError(`${field}[${String(index)}] cannot be empty.`);\n\t\t}\n\t\tif (normalized.includes(\"://\") || normalized.includes(\"/\")) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${field}[${String(index)}] must be a hostname without a scheme or path.`,\n\t\t\t);\n\t\t}\n\t\treturn normalized;\n\t});\n}\n"],"mappings":";;;;;;;;;AAsBA,IAAa,qBAAb,MAA2D;CAC1D;CACA;CAEA;CACA;CACA;CAEA,YAAY,SAA0B,SAAsC;EAC3E,KAAKA,WAAW;EAGhB,MAAM,mBAAmB,mBAAmB,QAAQ,kBAAkB,kBAAkB;EACxF,MAAM,yBAAyB,mBAC9B,QAAQ,wBACR,wBACD;EACA,KAAKC,UAAU,uBAAuB;GACrC;GACA;GACA,GAAI,QAAQ,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;GAC3D,GAAI,QAAQ,iBAAiB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc,QAAQ,aAAa;EACpF,CAAC;EACD,KAAKC,YAAY,eAAe,SAAS,KAAKD,OAAO;EACrD,KAAK,SAAS,QAAQ;EACtB,KAAK,MAAM,QAAQ;CACpB;CAEA,IAAI,SAAgC;EACnC,OAAO,KAAKA;CACb;CAEA,MAAM,SAAkB,SAAuD;EAC9E,OAAO,KAAKC,UAAU,MAAM,SAAS,OAAO;CAC7C;CAEA,QAAuB;EACtB,OAAO,KAAKF,SAAS,MAAM;CAC5B;AACD;AAEA,SAAS,mBAAmB,QAA2B,OAAyB;CAC/E,IAAI,OAAO,WAAW,GAAG,MAAM,IAAI,UAAU,GAAG,MAAM,kBAAkB;CACxE,OAAO,OAAO,KAAK,OAAO,UAAU;EACnC,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY;EAC5C,IAAI,WAAW,WAAW,GACzB,MAAM,IAAI,UAAU,GAAG,MAAM,GAAG,OAAO,KAAK,EAAE,mBAAmB;EAElE,IAAI,WAAW,SAAS,KAAK,KAAK,WAAW,SAAS,GAAG,GACxD,MAAM,IAAI,UACT,GAAG,MAAM,GAAG,OAAO,KAAK,EAAE,+CAC3B;EAED,OAAO;CACR,CAAC;AACF"}