payload 3.45.0 → 3.46.0-canary.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.
@@ -3,6 +3,7 @@ import { fetch as undiciFetch } from 'undici';
3
3
  * A "safe" version of undici's fetch that prevents SSRF attacks.
4
4
  *
5
5
  * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.
6
+ * - Validates domain names by resolving them to IP addresses and checking if they're safe.
6
7
  * - Undici was used because it supported interceptors as well as "credentials: include". Native fetch
7
8
  */
8
9
  export declare const safeFetch: (input: import("undici").RequestInfo, init?: import("undici").RequestInit | undefined) => Promise<import("undici").Response>;
@@ -1 +1 @@
1
- {"version":3,"file":"safeFetch.d.ts","sourceRoot":"","sources":["../../src/uploads/safeFetch.ts"],"names":[],"mappings":"AAGA,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AAmCpD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,8HA0BrB,CAAA"}
1
+ {"version":3,"file":"safeFetch.d.ts","sourceRoot":"","sources":["../../src/uploads/safeFetch.ts"],"names":[],"mappings":"AAIA,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AAiDpD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,8HAoCrB,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { lookup } from 'dns/promises';
1
2
  import ipaddr from 'ipaddr.js';
2
3
  import { Agent, fetch as undiciFetch } from 'undici';
3
4
  const isSafeIp = (ip)=>{
@@ -19,12 +20,23 @@ const isSafeIp = (ip)=>{
19
20
  }
20
21
  return true;
21
22
  };
23
+ /**
24
+ * Checks if a hostname or IP address is safe to fetch from.
25
+ * @param hostname a hostname or IP address
26
+ * @returns
27
+ */ const isSafe = async (hostname)=>{
28
+ try {
29
+ if (ipaddr.isValid(hostname)) {
30
+ return isSafeIp(hostname);
31
+ }
32
+ const { address } = await lookup(hostname);
33
+ return isSafeIp(address);
34
+ } catch (_ignore) {
35
+ return false;
36
+ }
37
+ };
22
38
  const ssrfFilterInterceptor = (dispatch)=>{
23
39
  return (opts, handler)=>{
24
- const url = new URL(opts.origin?.toString() + opts.path);
25
- if (!isSafeIp(url.hostname)) {
26
- throw new Error(`Blocked unsafe attempt to ${url}`);
27
- }
28
40
  return dispatch(opts, handler);
29
41
  };
30
42
  };
@@ -33,10 +45,16 @@ const safeDispatcher = new Agent().compose(ssrfFilterInterceptor);
33
45
  * A "safe" version of undici's fetch that prevents SSRF attacks.
34
46
  *
35
47
  * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.
48
+ * - Validates domain names by resolving them to IP addresses and checking if they're safe.
36
49
  * - Undici was used because it supported interceptors as well as "credentials: include". Native fetch
37
50
  */ export const safeFetch = async (...args)=>{
38
- const [url, options] = args;
51
+ const [unverifiedUrl, options] = args;
39
52
  try {
53
+ const url = new URL(unverifiedUrl);
54
+ const isHostnameSafe = await isSafe(url.hostname);
55
+ if (!isHostnameSafe) {
56
+ throw new Error(`Blocked unsafe attempt to ${url.toString()}`);
57
+ }
40
58
  return await undiciFetch(url, {
41
59
  ...options,
42
60
  dispatcher: safeDispatcher
@@ -49,10 +67,12 @@ const safeDispatcher = new Agent().compose(ssrfFilterInterceptor);
49
67
  throw new Error(error.cause.message);
50
68
  } else {
51
69
  let stringifiedUrl = undefined;
52
- if (typeof url === 'string' || url instanceof URL) {
53
- stringifiedUrl = url;
54
- } else if (url instanceof Request) {
55
- stringifiedUrl = url.url;
70
+ if (typeof unverifiedUrl === 'string') {
71
+ stringifiedUrl = unverifiedUrl;
72
+ } else if (unverifiedUrl instanceof URL) {
73
+ stringifiedUrl = unverifiedUrl.toString();
74
+ } else if (unverifiedUrl instanceof Request) {
75
+ stringifiedUrl = unverifiedUrl.url;
56
76
  }
57
77
  throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`);
58
78
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { Dispatcher } from 'undici'\n\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: Dispatcher.DispatcherComposeInterceptor = (dispatch) => {\n return (opts, handler) => {\n const url = new URL(opts.origin?.toString() + opts.path)\n if (!isSafeIp(url.hostname)) {\n throw new Error(`Blocked unsafe attempt to ${url}`)\n }\n return dispatch(opts, handler)\n }\n}\n\nconst safeDispatcher = new Agent().compose(ssrfFilterInterceptor)\n\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>) => {\n const [url, options] = args\n try {\n return await undiciFetch(url, {\n ...options,\n dispatcher: safeDispatcher,\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined | URL = undefined\n if (typeof url === 'string' || url instanceof URL) {\n stringifiedUrl = url\n } else if (url instanceof Request) {\n stringifiedUrl = url.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["ipaddr","Agent","fetch","undiciFetch","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","dispatch","opts","handler","url","URL","origin","toString","path","hostname","Error","safeDispatcher","compose","safeFetch","args","options","dispatcher","error","cause","message","includes","stringifiedUrl","undefined","Request"],"mappings":"AAEA,OAAOA,YAAY,YAAW;AAC9B,SAASC,KAAK,EAAEC,SAASC,WAAW,QAAQ,SAAQ;AAEpD,MAAMC,WAAW,CAACC;IAChB,IAAI;QACF,IAAI,CAACA,IAAI;YACP,OAAO;QACT;QAEA,IAAI,CAACL,OAAOM,OAAO,CAACD,KAAK;YACvB,OAAO;QACT;QAEA,MAAME,kBAAkBP,OAAOQ,KAAK,CAACH;QACrC,MAAMI,QAAQF,gBAAgBE,KAAK;QACnC,IAAIA,UAAU,WAAW;YACvB,OAAO,MAAM,mBAAmB;;QAClC;IACF,EAAE,OAAOC,QAAQ;QACf,OAAO;IACT;IACA,OAAO;AACT;AAEA,MAAMC,wBAAiE,CAACC;IACtE,OAAO,CAACC,MAAMC;QACZ,MAAMC,MAAM,IAAIC,IAAIH,KAAKI,MAAM,EAAEC,aAAaL,KAAKM,IAAI;QACvD,IAAI,CAACf,SAASW,IAAIK,QAAQ,GAAG;YAC3B,MAAM,IAAIC,MAAM,CAAC,0BAA0B,EAAEN,KAAK;QACpD;QACA,OAAOH,SAASC,MAAMC;IACxB;AACF;AAEA,MAAMQ,iBAAiB,IAAIrB,QAAQsB,OAAO,CAACZ;AAE3C;;;;;CAKC,GACD,OAAO,MAAMa,YAAY,OAAO,GAAGC;IACjC,MAAM,CAACV,KAAKW,QAAQ,GAAGD;IACvB,IAAI;QACF,OAAO,MAAMtB,YAAYY,KAAK;YAC5B,GAAGW,OAAO;YACVC,YAAYL;QACd;IACF,EAAE,OAAOM,OAAO;QACd,IAAIA,iBAAiBP,OAAO;YAC1B,IAAIO,MAAMC,KAAK,YAAYR,SAASO,MAAMC,KAAK,CAACC,OAAO,CAACC,QAAQ,CAAC,WAAW;gBAC1E,kFAAkF;gBAClF,2DAA2D;gBAC3D,MAAM,IAAIV,MAAMO,MAAMC,KAAK,CAACC,OAAO;YACrC,OAAO;gBACL,IAAIE,iBAA2CC;gBAC/C,IAAI,OAAOlB,QAAQ,YAAYA,eAAeC,KAAK;oBACjDgB,iBAAiBjB;gBACnB,OAAO,IAAIA,eAAemB,SAAS;oBACjCF,iBAAiBjB,IAAIA,GAAG;gBAC1B;gBAEA,MAAM,IAAIM,MAAM,CAAC,qBAAqB,EAAEW,eAAe,EAAE,EAAEJ,MAAME,OAAO,EAAE;YAC5E;QACF;QACA,MAAMF;IACR;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { Dispatcher } from 'undici'\n\nimport { lookup } from 'dns/promises'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\n/**\n * Checks if a hostname or IP address is safe to fetch from.\n * @param hostname a hostname or IP address\n * @returns\n */\nconst isSafe = async (hostname: string) => {\n try {\n if (ipaddr.isValid(hostname)) {\n return isSafeIp(hostname)\n }\n\n const { address } = await lookup(hostname)\n return isSafeIp(address)\n } catch (_ignore) {\n return false\n }\n}\n\nconst ssrfFilterInterceptor: Dispatcher.DispatcherComposeInterceptor = (dispatch) => {\n return (opts, handler) => {\n return dispatch(opts, handler)\n }\n}\n\nconst safeDispatcher = new Agent().compose(ssrfFilterInterceptor)\n\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>) => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n const isHostnameSafe = await isSafe(url.hostname)\n if (!isHostnameSafe) {\n throw new Error(`Blocked unsafe attempt to ${url.toString()}`)\n }\n\n return await undiciFetch(url, {\n ...options,\n dispatcher: safeDispatcher,\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","isSafe","hostname","address","_ignore","ssrfFilterInterceptor","dispatch","opts","handler","safeDispatcher","compose","safeFetch","args","unverifiedUrl","options","url","URL","isHostnameSafe","Error","toString","dispatcher","error","cause","message","includes","stringifiedUrl","undefined","Request"],"mappings":"AAEA,SAASA,MAAM,QAAQ,eAAc;AACrC,OAAOC,YAAY,YAAW;AAC9B,SAASC,KAAK,EAAEC,SAASC,WAAW,QAAQ,SAAQ;AAEpD,MAAMC,WAAW,CAACC;IAChB,IAAI;QACF,IAAI,CAACA,IAAI;YACP,OAAO;QACT;QAEA,IAAI,CAACL,OAAOM,OAAO,CAACD,KAAK;YACvB,OAAO;QACT;QAEA,MAAME,kBAAkBP,OAAOQ,KAAK,CAACH;QACrC,MAAMI,QAAQF,gBAAgBE,KAAK;QACnC,IAAIA,UAAU,WAAW;YACvB,OAAO,MAAM,mBAAmB;;QAClC;IACF,EAAE,OAAOC,QAAQ;QACf,OAAO;IACT;IACA,OAAO;AACT;AAEA;;;;CAIC,GACD,MAAMC,SAAS,OAAOC;IACpB,IAAI;QACF,IAAIZ,OAAOM,OAAO,CAACM,WAAW;YAC5B,OAAOR,SAASQ;QAClB;QAEA,MAAM,EAAEC,OAAO,EAAE,GAAG,MAAMd,OAAOa;QACjC,OAAOR,SAASS;IAClB,EAAE,OAAOC,SAAS;QAChB,OAAO;IACT;AACF;AAEA,MAAMC,wBAAiE,CAACC;IACtE,OAAO,CAACC,MAAMC;QACZ,OAAOF,SAASC,MAAMC;IACxB;AACF;AAEA,MAAMC,iBAAiB,IAAIlB,QAAQmB,OAAO,CAACL;AAE3C;;;;;;CAMC,GACD,OAAO,MAAMM,YAAY,OAAO,GAAGC;IACjC,MAAM,CAACC,eAAeC,QAAQ,GAAGF;IAEjC,IAAI;QACF,MAAMG,MAAM,IAAIC,IAAIH;QAEpB,MAAMI,iBAAiB,MAAMhB,OAAOc,IAAIb,QAAQ;QAChD,IAAI,CAACe,gBAAgB;YACnB,MAAM,IAAIC,MAAM,CAAC,0BAA0B,EAAEH,IAAII,QAAQ,IAAI;QAC/D;QAEA,OAAO,MAAM1B,YAAYsB,KAAK;YAC5B,GAAGD,OAAO;YACVM,YAAYX;QACd;IACF,EAAE,OAAOY,OAAO;QACd,IAAIA,iBAAiBH,OAAO;YAC1B,IAAIG,MAAMC,KAAK,YAAYJ,SAASG,MAAMC,KAAK,CAACC,OAAO,CAACC,QAAQ,CAAC,WAAW;gBAC1E,kFAAkF;gBAClF,2DAA2D;gBAC3D,MAAM,IAAIN,MAAMG,MAAMC,KAAK,CAACC,OAAO;YACrC,OAAO;gBACL,IAAIE,iBAAqCC;gBACzC,IAAI,OAAOb,kBAAkB,UAAU;oBACrCY,iBAAiBZ;gBACnB,OAAO,IAAIA,yBAAyBG,KAAK;oBACvCS,iBAAiBZ,cAAcM,QAAQ;gBACzC,OAAO,IAAIN,yBAAyBc,SAAS;oBAC3CF,iBAAiBZ,cAAcE,GAAG;gBACpC;gBAEA,MAAM,IAAIG,MAAM,CAAC,qBAAqB,EAAEO,eAAe,EAAE,EAAEJ,MAAME,OAAO,EAAE;YAC5E;QACF;QACA,MAAMF;IACR;AACF,EAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload",
3
- "version": "3.45.0",
3
+ "version": "3.46.0-canary.1",
4
4
  "description": "Node, React, Headless CMS and Application Framework built on Next.js",
5
5
  "keywords": [
6
6
  "admin panel",
@@ -101,7 +101,7 @@
101
101
  "undici": "7.10.0",
102
102
  "uuid": "10.0.0",
103
103
  "ws": "^8.16.0",
104
- "@payloadcms/translations": "3.45.0"
104
+ "@payloadcms/translations": "3.46.0-canary.1"
105
105
  },
106
106
  "devDependencies": {
107
107
  "@hyrious/esbuild-plugin-commonjs": "0.2.6",