@frontmcp/adapters 1.7.1 → 1.8.0

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/esm/index.mjs CHANGED
@@ -893,8 +893,35 @@ function createOpenApiTool(openapiTool, options, logger) {
893
893
  method: openapiTool.metadata.method.toUpperCase(),
894
894
  headers,
895
895
  body: serializedBody,
896
- signal: controller.signal
896
+ signal: controller.signal,
897
+ // SECURITY (GHSA-qh67-4345-cw2q): never auto-follow redirects. `fetch` defaults to
898
+ // `redirect: 'follow'`, which re-sends the request to a destination the UPSTREAM
899
+ // chose, not the operator. Only `baseUrl` is validated, and only for its scheme, so
900
+ // a 3xx is an unvalidated hop — to a cloud metadata address or an RFC1918 host.
901
+ // undici also strips only `Authorization` and `Cookie` across origins and forwards
902
+ // custom headers, which is exactly how this adapter injects API keys, so following
903
+ // hands the backend credential to the redirect target. `manual` makes the 3xx
904
+ // visible here so it can be refused instead of chased.
905
+ redirect: "manual"
897
906
  });
907
+ if (response.status >= 300 && response.status < 400) {
908
+ logger.warn(`[${openapiTool.name}] upstream returned a redirect; not followed`, {
909
+ status: response.status
910
+ });
911
+ return {
912
+ content: [
913
+ {
914
+ type: "text",
915
+ text: JSON.stringify({
916
+ status: response.status,
917
+ error: `Upstream returned a redirect (${response.status}); not followed to protect injected credentials.`
918
+ })
919
+ }
920
+ ],
921
+ isError: true,
922
+ _meta: { status: response.status, errorCode: "OPENAPI_REDIRECT_NOT_FOLLOWED" }
923
+ };
924
+ }
898
925
  const apiResponse = await parseResponse(response, { maxResponseSize: options.maxResponseSize });
899
926
  let transformedData = apiResponse.data;
900
927
  if (postToolTransform) {
@@ -893,8 +893,35 @@ function createOpenApiTool(openapiTool, options, logger) {
893
893
  method: openapiTool.metadata.method.toUpperCase(),
894
894
  headers,
895
895
  body: serializedBody,
896
- signal: controller.signal
896
+ signal: controller.signal,
897
+ // SECURITY (GHSA-qh67-4345-cw2q): never auto-follow redirects. `fetch` defaults to
898
+ // `redirect: 'follow'`, which re-sends the request to a destination the UPSTREAM
899
+ // chose, not the operator. Only `baseUrl` is validated, and only for its scheme, so
900
+ // a 3xx is an unvalidated hop — to a cloud metadata address or an RFC1918 host.
901
+ // undici also strips only `Authorization` and `Cookie` across origins and forwards
902
+ // custom headers, which is exactly how this adapter injects API keys, so following
903
+ // hands the backend credential to the redirect target. `manual` makes the 3xx
904
+ // visible here so it can be refused instead of chased.
905
+ redirect: "manual"
897
906
  });
907
+ if (response.status >= 300 && response.status < 400) {
908
+ logger.warn(`[${openapiTool.name}] upstream returned a redirect; not followed`, {
909
+ status: response.status
910
+ });
911
+ return {
912
+ content: [
913
+ {
914
+ type: "text",
915
+ text: JSON.stringify({
916
+ status: response.status,
917
+ error: `Upstream returned a redirect (${response.status}); not followed to protect injected credentials.`
918
+ })
919
+ }
920
+ ],
921
+ isError: true,
922
+ _meta: { status: response.status, errorCode: "OPENAPI_REDIRECT_NOT_FOLLOWED" }
923
+ };
924
+ }
898
925
  const apiResponse = await parseResponse(response, { maxResponseSize: options.maxResponseSize });
899
926
  let transformedData = apiResponse.data;
900
927
  if (postToolTransform) {
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/adapters",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Adapters for the FrontMCP framework",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -64,15 +64,15 @@
64
64
  "node": ">=24.0.0"
65
65
  },
66
66
  "dependencies": {
67
- "@frontmcp/auth": "1.7.1",
68
- "@frontmcp/di": "1.7.1",
69
- "@frontmcp/sdk": "1.7.1",
70
- "@frontmcp/utils": "1.7.1",
67
+ "@frontmcp/auth": "1.8.0",
68
+ "@frontmcp/di": "1.8.0",
69
+ "@frontmcp/sdk": "1.8.0",
70
+ "@frontmcp/utils": "1.8.0",
71
71
  "js-yaml": "^4.1.0",
72
72
  "mcp-from-openapi": "2.5.1",
73
73
  "openapi-types": "^12.1.3"
74
74
  },
75
75
  "peerDependencies": {
76
- "@frontmcp/lazy-zod": "1.7.1"
76
+ "@frontmcp/lazy-zod": "1.8.0"
77
77
  }
78
78
  }
package/index.js CHANGED
@@ -928,8 +928,35 @@ function createOpenApiTool(openapiTool, options, logger) {
928
928
  method: openapiTool.metadata.method.toUpperCase(),
929
929
  headers,
930
930
  body: serializedBody,
931
- signal: controller.signal
931
+ signal: controller.signal,
932
+ // SECURITY (GHSA-qh67-4345-cw2q): never auto-follow redirects. `fetch` defaults to
933
+ // `redirect: 'follow'`, which re-sends the request to a destination the UPSTREAM
934
+ // chose, not the operator. Only `baseUrl` is validated, and only for its scheme, so
935
+ // a 3xx is an unvalidated hop — to a cloud metadata address or an RFC1918 host.
936
+ // undici also strips only `Authorization` and `Cookie` across origins and forwards
937
+ // custom headers, which is exactly how this adapter injects API keys, so following
938
+ // hands the backend credential to the redirect target. `manual` makes the 3xx
939
+ // visible here so it can be refused instead of chased.
940
+ redirect: "manual"
932
941
  });
942
+ if (response.status >= 300 && response.status < 400) {
943
+ logger.warn(`[${openapiTool.name}] upstream returned a redirect; not followed`, {
944
+ status: response.status
945
+ });
946
+ return {
947
+ content: [
948
+ {
949
+ type: "text",
950
+ text: JSON.stringify({
951
+ status: response.status,
952
+ error: `Upstream returned a redirect (${response.status}); not followed to protect injected credentials.`
953
+ })
954
+ }
955
+ ],
956
+ isError: true,
957
+ _meta: { status: response.status, errorCode: "OPENAPI_REDIRECT_NOT_FOLLOWED" }
958
+ };
959
+ }
933
960
  const apiResponse = await parseResponse(response, { maxResponseSize: options.maxResponseSize });
934
961
  let transformedData = apiResponse.data;
935
962
  if (postToolTransform) {
package/openapi/index.js CHANGED
@@ -928,8 +928,35 @@ function createOpenApiTool(openapiTool, options, logger) {
928
928
  method: openapiTool.metadata.method.toUpperCase(),
929
929
  headers,
930
930
  body: serializedBody,
931
- signal: controller.signal
931
+ signal: controller.signal,
932
+ // SECURITY (GHSA-qh67-4345-cw2q): never auto-follow redirects. `fetch` defaults to
933
+ // `redirect: 'follow'`, which re-sends the request to a destination the UPSTREAM
934
+ // chose, not the operator. Only `baseUrl` is validated, and only for its scheme, so
935
+ // a 3xx is an unvalidated hop — to a cloud metadata address or an RFC1918 host.
936
+ // undici also strips only `Authorization` and `Cookie` across origins and forwards
937
+ // custom headers, which is exactly how this adapter injects API keys, so following
938
+ // hands the backend credential to the redirect target. `manual` makes the 3xx
939
+ // visible here so it can be refused instead of chased.
940
+ redirect: "manual"
932
941
  });
942
+ if (response.status >= 300 && response.status < 400) {
943
+ logger.warn(`[${openapiTool.name}] upstream returned a redirect; not followed`, {
944
+ status: response.status
945
+ });
946
+ return {
947
+ content: [
948
+ {
949
+ type: "text",
950
+ text: JSON.stringify({
951
+ status: response.status,
952
+ error: `Upstream returned a redirect (${response.status}); not followed to protect injected credentials.`
953
+ })
954
+ }
955
+ ],
956
+ isError: true,
957
+ _meta: { status: response.status, errorCode: "OPENAPI_REDIRECT_NOT_FOLLOWED" }
958
+ };
959
+ }
933
960
  const apiResponse = await parseResponse(response, { maxResponseSize: options.maxResponseSize });
934
961
  let transformedData = apiResponse.data;
935
962
  if (postToolTransform) {
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.tool.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAI1D,OAAO,KAAK,EAIV,qBAAqB,EAEtB,MAAM,iBAAiB,CAAC;AAMzB;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,cAAc,cA+QpH"}
1
+ {"version":3,"file":"openapi.tool.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAI1D,OAAO,KAAK,EAIV,qBAAqB,EAEtB,MAAM,iBAAiB,CAAC;AAMzB;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,cAAc,cA6SpH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/adapters",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Adapters for the FrontMCP framework",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -64,15 +64,15 @@
64
64
  "node": ">=24.0.0"
65
65
  },
66
66
  "dependencies": {
67
- "@frontmcp/auth": "1.7.1",
68
- "@frontmcp/di": "1.7.1",
69
- "@frontmcp/sdk": "1.7.1",
70
- "@frontmcp/utils": "1.7.1",
67
+ "@frontmcp/auth": "1.8.0",
68
+ "@frontmcp/di": "1.8.0",
69
+ "@frontmcp/sdk": "1.8.0",
70
+ "@frontmcp/utils": "1.8.0",
71
71
  "js-yaml": "^4.1.0",
72
72
  "mcp-from-openapi": "2.5.1",
73
73
  "openapi-types": "^12.1.3"
74
74
  },
75
75
  "peerDependencies": {
76
- "@frontmcp/lazy-zod": "1.7.1"
76
+ "@frontmcp/lazy-zod": "1.8.0"
77
77
  }
78
78
  }