@payloadcms/next 3.32.0-internal.30bda70 → 3.32.0-internal.7b8361c

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.
@@ -1 +1 @@
1
- {"version":3,"file":"getSafeRedirect.d.ts","sourceRoot":"","sources":["../../src/utilities/getSafeRedirect.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,kBACX,MAAM,GAAG,MAAM,EAAE,aACtB,MAAM,KACf,MAmBF,CAAA"}
1
+ {"version":3,"file":"getSafeRedirect.d.ts","sourceRoot":"","sources":["../../src/utilities/getSafeRedirect.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,kBACX,MAAM,GAAG,MAAM,EAAE,aACtB,MAAM,KACf,MA8BF,CAAA"}
@@ -2,13 +2,23 @@ export const getSafeRedirect = (redirectParam, fallback = '/') => {
2
2
  if (typeof redirectParam !== 'string') {
3
3
  return fallback;
4
4
  }
5
- // Ensures that any leading or trailing whitespace doesn’t affect the checks
6
- const redirectPath = redirectParam.trim();
5
+ // Normalize and decode the path
6
+ let redirectPath;
7
+ try {
8
+ redirectPath = decodeURIComponent(redirectParam.trim());
9
+ } catch {
10
+ return fallback // invalid encoding
11
+ ;
12
+ }
7
13
  const isSafeRedirect =
8
14
  // Must start with a single forward slash (e.g., "/admin")
9
15
  redirectPath.startsWith('/') &&
10
- // Prevent protocol-relative URLs (e.g., "//evil.com")
16
+ // Prevent protocol-relative URLs (e.g., "//example.com")
11
17
  !redirectPath.startsWith('//') &&
18
+ // Prevent encoded slashes that could resolve to protocol-relative
19
+ !redirectPath.startsWith('/%2F') &&
20
+ // Prevent backslash-based escape attempts (e.g., "/\\/example.com", "/\\\\example.com", "/\\example.com")
21
+ !redirectPath.startsWith('/\\/') && !redirectPath.startsWith('/\\\\') && !redirectPath.startsWith('/\\') &&
12
22
  // Prevent javascript-based schemes (e.g., "/javascript:alert(1)")
13
23
  !redirectPath.toLowerCase().startsWith('/javascript:') &&
14
24
  // Prevent attempts to redirect to full URLs using "/http:" or "/https:"
@@ -1 +1 @@
1
- {"version":3,"file":"getSafeRedirect.js","names":["getSafeRedirect","redirectParam","fallback","redirectPath","trim","isSafeRedirect","startsWith","toLowerCase"],"sources":["../../src/utilities/getSafeRedirect.ts"],"sourcesContent":["export const getSafeRedirect = (\n redirectParam: string | string[],\n fallback: string = '/',\n): string => {\n if (typeof redirectParam !== 'string') {\n return fallback\n }\n\n // Ensures that any leading or trailing whitespace doesn’t affect the checks\n const redirectPath = redirectParam.trim()\n\n const isSafeRedirect =\n // Must start with a single forward slash (e.g., \"/admin\")\n redirectPath.startsWith('/') &&\n // Prevent protocol-relative URLs (e.g., \"//evil.com\")\n !redirectPath.startsWith('//') &&\n // Prevent javascript-based schemes (e.g., \"/javascript:alert(1)\")\n !redirectPath.toLowerCase().startsWith('/javascript:') &&\n // Prevent attempts to redirect to full URLs using \"/http:\" or \"/https:\"\n !redirectPath.toLowerCase().startsWith('/http')\n\n return isSafeRedirect ? redirectPath : fallback\n}\n"],"mappings":"AAAA,OAAO,MAAMA,eAAA,GAAkBA,CAC7BC,aAAA,EACAC,QAAA,GAAmB,GAAG;EAEtB,IAAI,OAAOD,aAAA,KAAkB,UAAU;IACrC,OAAOC,QAAA;EACT;EAEA;EACA,MAAMC,YAAA,GAAeF,aAAA,CAAcG,IAAI;EAEvC,MAAMC,cAAA;EACJ;EACAF,YAAA,CAAaG,UAAU,CAAC;EACxB;EACA,CAACH,YAAA,CAAaG,UAAU,CAAC;EACzB;EACA,CAACH,YAAA,CAAaI,WAAW,GAAGD,UAAU,CAAC;EACvC;EACA,CAACH,YAAA,CAAaI,WAAW,GAAGD,UAAU,CAAC;EAEzC,OAAOD,cAAA,GAAiBF,YAAA,GAAeD,QAAA;AACzC","ignoreList":[]}
1
+ {"version":3,"file":"getSafeRedirect.js","names":["getSafeRedirect","redirectParam","fallback","redirectPath","decodeURIComponent","trim","isSafeRedirect","startsWith","toLowerCase"],"sources":["../../src/utilities/getSafeRedirect.ts"],"sourcesContent":["export const getSafeRedirect = (\n redirectParam: string | string[],\n fallback: string = '/',\n): string => {\n if (typeof redirectParam !== 'string') {\n return fallback\n }\n\n // Normalize and decode the path\n let redirectPath: string\n try {\n redirectPath = decodeURIComponent(redirectParam.trim())\n } catch {\n return fallback // invalid encoding\n }\n\n const isSafeRedirect =\n // Must start with a single forward slash (e.g., \"/admin\")\n redirectPath.startsWith('/') &&\n // Prevent protocol-relative URLs (e.g., \"//example.com\")\n !redirectPath.startsWith('//') &&\n // Prevent encoded slashes that could resolve to protocol-relative\n !redirectPath.startsWith('/%2F') &&\n // Prevent backslash-based escape attempts (e.g., \"/\\\\/example.com\", \"/\\\\\\\\example.com\", \"/\\\\example.com\")\n !redirectPath.startsWith('/\\\\/') &&\n !redirectPath.startsWith('/\\\\\\\\') &&\n !redirectPath.startsWith('/\\\\') &&\n // Prevent javascript-based schemes (e.g., \"/javascript:alert(1)\")\n !redirectPath.toLowerCase().startsWith('/javascript:') &&\n // Prevent attempts to redirect to full URLs using \"/http:\" or \"/https:\"\n !redirectPath.toLowerCase().startsWith('/http')\n\n return isSafeRedirect ? redirectPath : fallback\n}\n"],"mappings":"AAAA,OAAO,MAAMA,eAAA,GAAkBA,CAC7BC,aAAA,EACAC,QAAA,GAAmB,GAAG;EAEtB,IAAI,OAAOD,aAAA,KAAkB,UAAU;IACrC,OAAOC,QAAA;EACT;EAEA;EACA,IAAIC,YAAA;EACJ,IAAI;IACFA,YAAA,GAAeC,kBAAA,CAAmBH,aAAA,CAAcI,IAAI;EACtD,EAAE,MAAM;IACN,OAAOH,QAAA,CAAS;IAAA;EAClB;EAEA,MAAMI,cAAA;EACJ;EACAH,YAAA,CAAaI,UAAU,CAAC;EACxB;EACA,CAACJ,YAAA,CAAaI,UAAU,CAAC;EACzB;EACA,CAACJ,YAAA,CAAaI,UAAU,CAAC;EACzB;EACA,CAACJ,YAAA,CAAaI,UAAU,CAAC,WACzB,CAACJ,YAAA,CAAaI,UAAU,CAAC,YACzB,CAACJ,YAAA,CAAaI,UAAU,CAAC;EACzB;EACA,CAACJ,YAAA,CAAaK,WAAW,GAAGD,UAAU,CAAC;EACvC;EACA,CAACJ,YAAA,CAAaK,WAAW,GAAGD,UAAU,CAAC;EAEzC,OAAOD,cAAA,GAAiBH,YAAA,GAAeD,QAAA;AACzC","ignoreList":[]}
@@ -0,0 +1,32 @@
1
+ import { getSafeRedirect } from './getSafeRedirect';
2
+ const fallback = '/admin' // default fallback if the input is unsafe or invalid
3
+ ;
4
+ describe('getSafeRedirect', () => {
5
+ // Valid - safe redirect paths
6
+ it.each([['/dashboard'], ['/admin/settings'], ['/projects?id=123'], ['/hello-world']])('should allow safe relative path: %s', input => {
7
+ // If the input is a clean relative path, it should be returned as-is
8
+ expect(getSafeRedirect(input, fallback)).toBe(input);
9
+ });
10
+ // Invalid types or empty inputs
11
+ it.each(['', null, undefined, 123, {}, []])('should fallback on invalid or non-string input: %s', input => {
12
+ // If the input is not a valid string, it should return the fallback
13
+ expect(getSafeRedirect(input, fallback)).toBe(fallback);
14
+ });
15
+ // Unsafe redirect patterns
16
+ it.each(['//example.com', '/javascript:alert(1)', '/JavaScript:alert(1)', '/http://unknown.com', '/https://unknown.com', '/%2Funknown.com', '/\\/unknown.com', '/\\\\unknown.com', '/\\unknown.com', '%2F%2Funknown.com', '%2Fjavascript:alert(1)'])('should block unsafe redirect: %s', input => {
17
+ // All of these should return the fallback because they’re unsafe
18
+ expect(getSafeRedirect(input, fallback)).toBe(fallback);
19
+ });
20
+ // Input with extra spaces should still be properly handled
21
+ it('should trim whitespace before evaluating', () => {
22
+ // A valid path with surrounding spaces should still be accepted
23
+ expect(getSafeRedirect(' /dashboard ', fallback)).toBe('/dashboard');
24
+ // An unsafe path with spaces should still be rejected
25
+ expect(getSafeRedirect(' //example.com ', fallback)).toBe(fallback);
26
+ });
27
+ // If decoding the input fails (e.g., invalid percent encoding), it should not crash
28
+ it('should return fallback on invalid encoding', () => {
29
+ expect(getSafeRedirect('%E0%A4%A', fallback)).toBe(fallback);
30
+ });
31
+ });
32
+ //# sourceMappingURL=getSafeRedirect.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getSafeRedirect.spec.js","names":["getSafeRedirect","fallback","describe","it","each","input","expect","toBe","undefined"],"sources":["../../src/utilities/getSafeRedirect.spec.ts"],"sourcesContent":["import { getSafeRedirect } from './getSafeRedirect'\n\nconst fallback = '/admin' // default fallback if the input is unsafe or invalid\n\ndescribe('getSafeRedirect', () => {\n // Valid - safe redirect paths\n it.each([['/dashboard'], ['/admin/settings'], ['/projects?id=123'], ['/hello-world']])(\n 'should allow safe relative path: %s',\n (input) => {\n // If the input is a clean relative path, it should be returned as-is\n expect(getSafeRedirect(input, fallback)).toBe(input)\n },\n )\n\n // Invalid types or empty inputs\n it.each(['', null, undefined, 123, {}, []])(\n 'should fallback on invalid or non-string input: %s',\n (input) => {\n // If the input is not a valid string, it should return the fallback\n expect(getSafeRedirect(input as any, fallback)).toBe(fallback)\n },\n )\n\n // Unsafe redirect patterns\n it.each([\n '//example.com', // protocol-relative URL\n '/javascript:alert(1)', // JavaScript scheme\n '/JavaScript:alert(1)', // case-insensitive JavaScript\n '/http://unknown.com', // disguised external redirect\n '/https://unknown.com', // disguised external redirect\n '/%2Funknown.com', // encoded slash — could resolve to //\n '/\\\\/unknown.com', // escaped slash\n '/\\\\\\\\unknown.com', // double escaped slashes\n '/\\\\unknown.com', // single escaped slash\n '%2F%2Funknown.com', // fully encoded protocol-relative path\n '%2Fjavascript:alert(1)', // encoded JavaScript scheme\n ])('should block unsafe redirect: %s', (input) => {\n // All of these should return the fallback because they’re unsafe\n expect(getSafeRedirect(input, fallback)).toBe(fallback)\n })\n\n // Input with extra spaces should still be properly handled\n it('should trim whitespace before evaluating', () => {\n // A valid path with surrounding spaces should still be accepted\n expect(getSafeRedirect(' /dashboard ', fallback)).toBe('/dashboard')\n\n // An unsafe path with spaces should still be rejected\n expect(getSafeRedirect(' //example.com ', fallback)).toBe(fallback)\n })\n\n // If decoding the input fails (e.g., invalid percent encoding), it should not crash\n it('should return fallback on invalid encoding', () => {\n expect(getSafeRedirect('%E0%A4%A', fallback)).toBe(fallback)\n })\n})\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ;AAEhC,MAAMC,QAAA,GAAW,SAAS;AAAA;AAE1BC,QAAA,CAAS,mBAAmB;EAC1B;EACAC,EAAA,CAAGC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,kBAAkB,EAAE,CAAC,mBAAmB,EAAE,CAAC,eAAe,CAAC,EACnF,uCACCC,KAAA;IACC;IACAC,MAAA,CAAON,eAAA,CAAgBK,KAAA,EAAOJ,QAAA,GAAWM,IAAI,CAACF,KAAA;EAChD;EAGF;EACAF,EAAA,CAAGC,IAAI,CAAC,CAAC,IAAI,MAAMI,SAAA,EAAW,KAAK,CAAC,GAAG,EAAE,CAAC,EACxC,sDACCH,KAAA;IACC;IACAC,MAAA,CAAON,eAAA,CAAgBK,KAAA,EAAcJ,QAAA,GAAWM,IAAI,CAACN,QAAA;EACvD;EAGF;EACAE,EAAA,CAAGC,IAAI,CAAC,CACN,iBACA,wBACA,wBACA,uBACA,wBACA,mBACA,mBACA,oBACA,kBACA,qBACA,yBACD,EAAE,oCAAqCC,KAAA;IACtC;IACAC,MAAA,CAAON,eAAA,CAAgBK,KAAA,EAAOJ,QAAA,GAAWM,IAAI,CAACN,QAAA;EAChD;EAEA;EACAE,EAAA,CAAG,4CAA4C;IAC7C;IACAG,MAAA,CAAON,eAAA,CAAgB,oBAAoBC,QAAA,GAAWM,IAAI,CAAC;IAE3D;IACAD,MAAA,CAAON,eAAA,CAAgB,uBAAuBC,QAAA,GAAWM,IAAI,CAACN,QAAA;EAChE;EAEA;EACAE,EAAA,CAAG,8CAA8C;IAC/CG,MAAA,CAAON,eAAA,CAAgB,YAAYC,QAAA,GAAWM,IAAI,CAACN,QAAA;EACrD;AACF","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/next",
3
- "version": "3.32.0-internal.30bda70",
3
+ "version": "3.32.0-internal.7b8361c",
4
4
  "homepage": "https://payloadcms.com",
5
5
  "repository": {
6
6
  "type": "git",
@@ -79,9 +79,9 @@
79
79
  "react-diff-viewer-continued": "4.0.5",
80
80
  "sass": "1.77.4",
81
81
  "uuid": "10.0.0",
82
- "@payloadcms/graphql": "3.32.0-internal.30bda70",
83
- "@payloadcms/ui": "3.32.0-internal.30bda70",
84
- "@payloadcms/translations": "3.32.0-internal.30bda70"
82
+ "@payloadcms/graphql": "3.32.0-internal.7b8361c",
83
+ "@payloadcms/ui": "3.32.0-internal.7b8361c",
84
+ "@payloadcms/translations": "3.32.0-internal.7b8361c"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@babel/cli": "7.26.4",
@@ -100,12 +100,12 @@
100
100
  "eslint-plugin-react-compiler": "19.0.0-beta-714736e-20250131",
101
101
  "swc-plugin-transform-remove-imports": "3.1.0",
102
102
  "@payloadcms/eslint-config": "3.28.0",
103
- "payload": "3.32.0-internal.30bda70"
103
+ "payload": "3.32.0-internal.7b8361c"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "graphql": "^16.8.1",
107
107
  "next": "^15.2.3",
108
- "payload": "3.32.0-internal.30bda70"
108
+ "payload": "3.32.0-internal.7b8361c"
109
109
  },
110
110
  "engines": {
111
111
  "node": "^18.20.2 || >=20.9.0"