فهرست منبع

fix(security): enhance get url params (#839)

* fix(security): enhance get url params

* fix(security): object ts

* fix(security): log injection
Yiwei Mao 3 ماه پیش
والد
کامیت
3182771e08

+ 2 - 1
apps/cli/src/create-app/index.ts

@@ -119,7 +119,8 @@ export const createApp = async (projectName?: string) => {
         fs.unlinkSync(tempTarballPath);
         return true;
       } catch (error) {
-        console.error(`Error downloading or extracting package: ${error}`);
+        console.error(`Error downloading or extracting package`);
+        console.error(error);
         return false;
       }
     };

+ 2 - 1
apps/cli/src/utils/npm.ts

@@ -110,7 +110,8 @@ export async function loadNpm(packageName: string): Promise<LoadedNpmPkg> {
 
     return new LoadedNpmPkg(packageName, packageLatestVersion, packagePath);
   } catch (error) {
-    console.error(`Error downloading or extracting package: ${error}`);
+    console.error(`Error downloading or extracting package`);
+    console.error(error);
     throw error;
   }
 }

+ 2 - 1
apps/create-app/src/index.ts

@@ -125,7 +125,8 @@ const main = async () => {
 
         return true;
       } catch (error) {
-        console.error(`Error downloading or extracting package: ${error}`);
+        console.error(`Error downloading or extracting package`);
+        console.error(error);
         return false;
       }
     };

+ 35 - 9
packages/canvas-engine/free-layout-core/src/utils/get-url-params.ts

@@ -4,21 +4,47 @@
  */
 
 export function getUrlParams(): Record<string, string> {
-  return location.search
+  const paramsMap = new Map<string, string>();
+
+  location.search
     .replace(/^\?/, '')
     .split('&')
-    .reduce((res: Record<string, string>, key) => {
-      if (!key) return res;
+    .forEach((key) => {
+      if (!key) return;
 
       const [k, v] = key.split('=');
 
       if (k) {
-        // Prevent prototype pollution attack, filter dangerous attribute names
-        if (k === '__proto__' || k === 'constructor' || k === 'prototype') {
-          return res;
+        // Decode URL-encoded parameter names and values
+        const decodedKey = decodeURIComponent(k.trim());
+        const decodedValue = v ? decodeURIComponent(v.trim()) : '';
+
+        // Prevent prototype pollution by filtering dangerous property names
+        const dangerousProps = [
+          '__proto__',
+          'constructor',
+          'prototype',
+          '__defineGetter__',
+          '__defineSetter__',
+          '__lookupGetter__',
+          '__lookupSetter__',
+          'hasOwnProperty',
+          'isPrototypeOf',
+          'propertyIsEnumerable',
+          'toString',
+          'valueOf',
+          'toLocaleString',
+        ];
+
+        if (dangerousProps.includes(decodedKey.toLowerCase())) {
+          return;
         }
-        res[k] = v || '';
+
+        // Use Map to prevent prototype pollution
+        paramsMap.set(decodedKey, decodedValue);
       }
-      return res;
-    }, Object.create(null));
+    });
+
+  // Convert Map to plain object while maintaining API compatibility
+  return Object.fromEntries(paramsMap);
 }

+ 38 - 9
packages/common/utils/src/objects.ts

@@ -30,6 +30,35 @@ export function notEmpty<T>(arg: T | undefined | null): arg is T {
   return arg !== undefined && arg !== null;
 }
 
+/**
+ * filter dangerous key, prevent prototype pollution injection
+ * @param key key to be filtered
+ * @returns filtered key
+ */
+export const safeKey = (key: string): string => {
+  const dangerousProps = [
+    '__proto__',
+    'constructor',
+    'prototype',
+    '__defineGetter__',
+    '__defineSetter__',
+    '__lookupGetter__',
+    '__lookupSetter__',
+    'hasOwnProperty',
+    'isPrototypeOf',
+    'propertyIsEnumerable',
+    'toString',
+    'valueOf',
+    'toLocaleString',
+  ];
+
+  if (dangerousProps.includes(key.toLowerCase())) {
+    return '';
+  }
+
+  return key;
+};
+
 /**
  * `true` if the argument is an empty object. Otherwise, `false`.
  */
@@ -38,15 +67,15 @@ export function isEmpty(arg: Object): boolean {
 }
 
 export const each = <T = any, K = string>(obj: any, fn: (value: T, key: K) => void) =>
-  keys(obj).forEach(key => fn(obj[key], key as any));
+  keys(obj).forEach((key) => fn(obj[key], key as any));
 
 export const values = (obj: any) =>
-  Object.values ? Object.values(obj) : keys(obj).map(k => obj[k]);
+  Object.values ? Object.values(obj) : keys(obj).map((k) => obj[k]);
 
 export const filter = (obj: any, fn: (value: any, key: string) => boolean, dest?: any) =>
   keys(obj).reduce(
     (output, key) => (fn(obj[key], key) ? Object.assign(output, { [key]: obj[key] }) : output),
-    dest || {},
+    dest || {}
   );
 
 export const pick = (obj: any, fields: string[], dest?: any) =>
@@ -58,7 +87,7 @@ export const omit = (obj: any, fields: string[], dest?: any) =>
 export const reduce = <V = any, R = any>(
   obj: any,
   fn: (res: R, value: V, key: string) => any,
-  res: R = {} as R,
+  res: R = {} as R
 ) => keys(obj).reduce((r, k) => fn(r, obj[k], k), res);
 
 export const mapValues = <V = any>(obj: any, fn: (value: V, key: string) => any) =>
@@ -108,7 +137,7 @@ export function setByKey(
   key: string,
   newValue: any,
   autoCreateObject = true,
-  clone = false,
+  clone = false
 ): any {
   if (typeof target !== 'object' || !key) return target;
   if (clone) {
@@ -119,18 +148,18 @@ export function setByKey(
   while (targetKeys.length > 0) {
     key = targetKeys.shift()!;
     if (targetKeys.length === 0) {
-      target[key] = newValue;
+      target[safeKey(key)] = newValue;
       return originTarget;
     }
     if (typeof target[key] !== 'object') {
       if (!autoCreateObject) return originTarget;
-      target[key] = {};
+      target[safeKey(key)] = {};
     }
     if (clone) {
       if (Array.isArray(target[key])) {
-        target[key] = target[key].slice();
+        target[safeKey(key)] = target[key].slice();
       } else {
-        target[key] = { ...target[key] };
+        target[safeKey(key)] = { ...target[key] };
       }
     }
     target = target[key];