fastmcp 3.35.0 → 4.0.1

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.
@@ -863,7 +863,11 @@ var OAuthProxy = class {
863
863
  transactions = /* @__PURE__ */ new Map();
864
864
  constructor(config) {
865
865
  this.config = {
866
- allowedRedirectUriPatterns: ["https://*", "http://localhost:*"],
866
+ // Empty by default. Framework users must explicitly configure the URIs they
867
+ // trust, per RFC 6819 §4.1.5. The previous default (`["https://*", "http://localhost:*"]`)
868
+ // allowed open DCR registration of any https URL, enabling CWE-601 open-redirect
869
+ // attacks against /oauth/authorize.
870
+ allowedRedirectUriPatterns: [],
867
871
  authorizationCodeTtl: DEFAULT_AUTHORIZATION_CODE_TTL,
868
872
  consentRequired: true,
869
873
  enableTokenSwap: true,
@@ -913,6 +917,15 @@ var OAuthProxy = class {
913
917
  "Only 'code' response type is supported"
914
918
  );
915
919
  }
920
+ if (params.client_id !== this.config.upstreamClientId) {
921
+ throw new OAuthProxyError("invalid_client", "Unknown client_id");
922
+ }
923
+ if (!this.registeredClients.has(params.redirect_uri)) {
924
+ throw new OAuthProxyError(
925
+ "invalid_request",
926
+ "redirect_uri is not registered for this client"
927
+ );
928
+ }
916
929
  if (params.code_challenge && !params.code_challenge_method) {
917
930
  throw new OAuthProxyError(
918
931
  "invalid_request",
@@ -950,6 +963,9 @@ var OAuthProxy = class {
950
963
  "Only authorization_code grant type is supported"
951
964
  );
952
965
  }
966
+ if (request.client_id !== this.config.upstreamClientId) {
967
+ throw new OAuthProxyError("invalid_client", "Unknown client_id");
968
+ }
953
969
  const clientCode = this.clientCodes.get(request.code);
954
970
  if (!clientCode) {
955
971
  throw new OAuthProxyError(
@@ -1063,6 +1079,13 @@ var OAuthProxy = class {
1063
1079
  if (!transaction) {
1064
1080
  throw new OAuthProxyError("invalid_request", "Invalid or expired state");
1065
1081
  }
1082
+ if (!this.registeredClients.has(transaction.clientCallbackUrl)) {
1083
+ this.transactions.delete(state);
1084
+ throw new OAuthProxyError(
1085
+ "invalid_request",
1086
+ "Transaction callback URL is not registered"
1087
+ );
1088
+ }
1066
1089
  const upstreamTokens = await this.exchangeUpstreamCode(code, transaction);
1067
1090
  const clientCode = this.generateAuthorizationCode(
1068
1091
  transaction,
@@ -1098,6 +1121,12 @@ var OAuthProxy = class {
1098
1121
  }
1099
1122
  if (action === "deny") {
1100
1123
  this.transactions.delete(transactionId);
1124
+ if (!this.registeredClients.has(transaction.clientCallbackUrl)) {
1125
+ throw new OAuthProxyError(
1126
+ "invalid_request",
1127
+ "Transaction callback URL is not registered"
1128
+ );
1129
+ }
1101
1130
  const redirectUrl = new URL(transaction.clientCallbackUrl);
1102
1131
  redirectUrl.searchParams.set("error", "access_denied");
1103
1132
  redirectUrl.searchParams.set(
@@ -1176,7 +1205,9 @@ var OAuthProxy = class {
1176
1205
  },
1177
1206
  registeredAt: /* @__PURE__ */ new Date()
1178
1207
  };
1179
- this.registeredClients.set(request.redirect_uris[0], client);
1208
+ for (const uri of request.redirect_uris) {
1209
+ this.registeredClients.set(uri, client);
1210
+ }
1180
1211
  const response = {
1181
1212
  client_id: clientId,
1182
1213
  client_id_issued_at: Math.floor(Date.now() / 1e3),
@@ -1287,11 +1318,16 @@ var OAuthProxy = class {
1287
1318
  method: "POST"
1288
1319
  });
1289
1320
  if (!tokenResponse.ok) {
1290
- const error = await tokenResponse.json();
1291
- throw new OAuthProxyError(
1292
- error.error || "server_error",
1293
- error.error_description
1294
- );
1321
+ let errorCode = "server_error";
1322
+ let errorDescription;
1323
+ try {
1324
+ const error = await tokenResponse.json();
1325
+ errorCode = error.error || "server_error";
1326
+ errorDescription = error.error_description;
1327
+ } catch {
1328
+ errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;
1329
+ }
1330
+ throw new OAuthProxyError(errorCode, errorDescription);
1295
1331
  }
1296
1332
  const tokens = await this.parseTokenResponse(tokenResponse);
1297
1333
  return {
@@ -1425,11 +1461,16 @@ var OAuthProxy = class {
1425
1461
  method: "POST"
1426
1462
  });
1427
1463
  if (!tokenResponse.ok) {
1428
- const error = await tokenResponse.json();
1429
- throw new OAuthProxyError(
1430
- error.error || "invalid_grant",
1431
- error.error_description
1432
- );
1464
+ let errorCode = "invalid_grant";
1465
+ let errorDescription;
1466
+ try {
1467
+ const error = await tokenResponse.json();
1468
+ errorCode = error.error || "invalid_grant";
1469
+ errorDescription = error.error_description;
1470
+ } catch {
1471
+ errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;
1472
+ }
1473
+ throw new OAuthProxyError(errorCode, errorDescription);
1433
1474
  }
1434
1475
  const tokens = await this.parseTokenResponse(tokenResponse);
1435
1476
  return {
@@ -1737,11 +1778,16 @@ var OAuthProxy = class {
1737
1778
  method: "POST"
1738
1779
  });
1739
1780
  if (!tokenResponse.ok) {
1740
- const error = await tokenResponse.json();
1741
- throw new OAuthProxyError(
1742
- error.error || "invalid_grant",
1743
- error.error_description || "Upstream refresh failed"
1744
- );
1781
+ let errorCode = "invalid_grant";
1782
+ let errorDescription = "Upstream refresh failed";
1783
+ try {
1784
+ const error = await tokenResponse.json();
1785
+ errorCode = error.error || "invalid_grant";
1786
+ errorDescription = error.error_description || "Upstream refresh failed";
1787
+ } catch {
1788
+ errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;
1789
+ }
1790
+ throw new OAuthProxyError(errorCode, errorDescription);
1745
1791
  }
1746
1792
  const tokens = await this.parseTokenResponse(tokenResponse);
1747
1793
  return {
@@ -1764,21 +1810,28 @@ var OAuthProxy = class {
1764
1810
  }, 6e4);
1765
1811
  }
1766
1812
  /**
1767
- * Validate redirect URI against allowed patterns
1813
+ * Validate a redirect URI against the configured allow-list.
1814
+ *
1815
+ * Returns `true` only if the URI is syntactically valid AND matches one of
1816
+ * the explicitly configured `allowedRedirectUriPatterns`. An empty or unset
1817
+ * pattern list means DCR will reject every URI — framework users must
1818
+ * opt-in by listing the exact URIs (or wildcards) they trust.
1819
+ *
1820
+ * Prior versions also fell back to allowing any https URL or localhost,
1821
+ * which enabled attackers to DCR an arbitrary URL and then abuse it via
1822
+ * /oauth/authorize (CWE-601). Do not re-introduce that fallback.
1768
1823
  */
1769
1824
  validateRedirectUri(uri) {
1770
1825
  try {
1771
- const url = new URL(uri);
1772
- const patterns = this.config.allowedRedirectUriPatterns || [];
1773
- for (const pattern of patterns) {
1774
- if (this.matchesPattern(uri, pattern)) {
1775
- return true;
1776
- }
1777
- }
1778
- return url.protocol === "https:" || url.hostname === "localhost" || url.hostname === "127.0.0.1";
1826
+ new URL(uri);
1779
1827
  } catch {
1780
1828
  return false;
1781
1829
  }
1830
+ const patterns = this.config.allowedRedirectUriPatterns || [];
1831
+ if (patterns.length === 0) {
1832
+ return false;
1833
+ }
1834
+ return patterns.some((pattern) => this.matchesPattern(uri, pattern));
1782
1835
  }
1783
1836
  };
1784
1837
  var OAuthProxyError = class extends Error {
@@ -1883,10 +1936,10 @@ var AzureProvider = class extends AuthProvider {
1883
1936
  }
1884
1937
  createProxy() {
1885
1938
  return new OAuthProxy({
1886
- allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns ?? [
1887
- "http://localhost:*",
1888
- "https://*"
1889
- ],
1939
+ // No fallback default: framework users must explicitly list the URIs
1940
+ // they trust. A previous default of ["http://localhost:*", "https://*"]
1941
+ // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.
1942
+ allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,
1890
1943
  baseUrl: this.config.baseUrl,
1891
1944
  consentRequired: this.config.consentRequired ?? true,
1892
1945
  encryptionKey: this.config.encryptionKey,
@@ -1917,10 +1970,10 @@ var GitHubProvider = class extends AuthProvider {
1917
1970
  }
1918
1971
  createProxy() {
1919
1972
  return new OAuthProxy({
1920
- allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns ?? [
1921
- "http://localhost:*",
1922
- "https://*"
1923
- ],
1973
+ // No fallback default: framework users must explicitly list the URIs
1974
+ // they trust. A previous default of ["http://localhost:*", "https://*"]
1975
+ // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.
1976
+ allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,
1924
1977
  baseUrl: this.config.baseUrl,
1925
1978
  consentRequired: this.config.consentRequired ?? true,
1926
1979
  encryptionKey: this.config.encryptionKey,
@@ -1951,10 +2004,10 @@ var GoogleProvider = class extends AuthProvider {
1951
2004
  }
1952
2005
  createProxy() {
1953
2006
  return new OAuthProxy({
1954
- allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns ?? [
1955
- "http://localhost:*",
1956
- "https://*"
1957
- ],
2007
+ // No fallback default: framework users must explicitly list the URIs
2008
+ // they trust. A previous default of ["http://localhost:*", "https://*"]
2009
+ // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.
2010
+ allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,
1958
2011
  baseUrl: this.config.baseUrl,
1959
2012
  consentRequired: this.config.consentRequired ?? true,
1960
2013
  encryptionKey: this.config.encryptionKey,
@@ -1987,10 +2040,10 @@ var OAuthProvider = class extends AuthProvider {
1987
2040
  }
1988
2041
  createProxy() {
1989
2042
  return new OAuthProxy({
1990
- allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns ?? [
1991
- "http://localhost:*",
1992
- "https://*"
1993
- ],
2043
+ // No fallback default: framework users must explicitly list the URIs
2044
+ // they trust. A previous default of ["http://localhost:*", "https://*"]
2045
+ // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.
2046
+ allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,
1994
2047
  baseUrl: this.config.baseUrl,
1995
2048
  consentRequired: this.config.consentRequired ?? true,
1996
2049
  encryptionKey: this.config.encryptionKey,
@@ -2325,4 +2378,4 @@ export {
2325
2378
  DiskStore,
2326
2379
  JWKSVerifier
2327
2380
  };
2328
- //# sourceMappingURL=chunk-H4VC4YTC.js.map
2381
+ //# sourceMappingURL=chunk-UN72PIH2.js.map