serialize-javascript icon indicating copy to clipboard operation
serialize-javascript copied to clipboard

Version 3.1.0 is not working in React-Native when is not in debugging mode or when is generating release

Open danilojpferreira opened this issue 4 years ago • 7 comments

Hello, I'm using serialize-javascript in React-Native instead of JSON.stringify function. The version 3.1.0 is not working in React-Native when the debugger is off or in the release apk (because there is no debugger).

The issue is: Error: Secure random number generation is not supported by this browser. Use Chrome, Firefox or Internet Explorer 11.

Reproduce:

  1. In React-Native project install with npm, npx or yarn the package serialize-javascript.
  2. Create a serialize function:
import serializeJs from 'serialize-javascript';
...

const clearArrayUndefinedValues = (array) => {
 try {
   if (!Array.isArray(array))
     throw new Error('Invalid argument. Must be an array.');

   const filteredArr = array.reduce((arr, current) => {
     if (typeof current === 'object' && current !== null) {
       if (Array.isArray(current))
         return [...arr, clearArrayUndefinedValues(current)];
       // eslint-disable-next-line no-use-before-define
       return [...arr, clearObjectUndefinedValues(current)];
     } else if (current !== undefined) return [...arr, current];

     return arr;
   }, []);

   return filteredArr;
 } catch (error) {
   console.log(
     '##\t Error log in clearArrayUndefinedValues on app/shared/utils.js ->',
     error
   );
   return null;
 }
};

const clearObjectUndefinedValues = (object) => {
 try {
   if (typeof object !== 'object')
     throw new Error('Invalid argument. Must be an object.');

   if (object === null) throw new Error('Invalid argument. Must not be null.');

   const filtered = Object.keys(object).reduce((obj, key) => {
     const { [key]: current } = object;

     if (typeof current === 'object' && current !== null) {
       if (Array.isArray(current))
         return { ...obj, [key]: clearArrayUndefinedValues(current) };

       return { ...obj, [key]: clearObjectUndefinedValues(current) };
     } else if (current !== undefined) return { ...obj, [key]: current };

     return obj;
   }, {});

   return filtered;
 } catch (error) {
   console.log(
     '##\t Error log in clearObjectUndefinedValues on app/shared/utils.js ->',
     error
   );
   return null;
 }
};

export const serialize = (object) => {
 try {
   if (typeof object !== 'object')
     throw new Error('Invalid argument. Must be an object.');

   if (object === null) throw new Error('Invalid argument. Must not be null.');

   if (Array.isArray(object))
     return serializeJs(clearArrayUndefinedValues(object));

   return serializeJs(clearObjectUndefinedValues(object));
 } catch (error) {
   console.log('##\t Error log in serialize on app/shared/utils.js ->', error);
   return null;
 }
};
  1. Use a serialize function in any place of your code:
import { serialize } from '../shared/utils';
...
const  JSONToObject = serialize(data);

My set is:

MacOs 10.15.5 i7 2.2Ghz 16GB

Running app in simulators:

iPhone 11 os 13.5 Pixel 3 os Android API 29.

Chrome version:

83.0.4103.61 64 bits

The previous version (3.0.0) working well. There are a prevision/way to fix this to work without debugger?

danilojpferreira avatar Jun 05 '20 00:06 danilojpferreira

I'm not familiar with React Native, but as the error message says, you need crypto.randomBytes (for Node.js) or Crypto.getRandomValues (for browsers). If you can apply something like the crypto pollyfill libs for React Native it might work well.

okuryu avatar Jun 05 '20 14:06 okuryu

I tried to find something like this but I've no success. Idk how to fix this.

danilojpferreira avatar Jun 12 '20 12:06 danilojpferreira

I also bumped into this issue.

It happens because of the following single line (and the dependency on randombytes library, introduced by it): https://github.com/yahoo/serialize-javascript/blob/05a322492aff6c3f03ac86a93a2627782dad7b11/index.js#L38

This comes from this commit: https://github.com/yahoo/serialize-javascript/commit/f21a6fb3ace2353413761e79717b2d210ba6ccbd

I don't really understand, why random UUIDs are necessary during JS serialization. Lazy to dig in too deep, but my guess is that original implementation just cut some corners, and used in-place UID instead of building a separate index of transformed entities, thus the correct fix of the problem should be not relying on a more randomized UUID, but re-writing the algo to not use UUIDs at all.

The working workaround for RN is to shim randombytes with https://www.npmjs.com/package/react-native-randombytes, but it requires some efforts to setup, and alias the randombytes for 3-rd party packages.

birdofpreyru avatar Jun 14 '20 12:06 birdofpreyru

To add to @birdofpreyru 's analysis:

  1. Problem in randombytes: Missing node support The actual issue comes from randombytes not supporting node. It determines the "old browser" label (which will always throw) in browser.js:
    if (crypto && crypto.getRandomValues) {
      module.exports = randomBytes
    } else {
      module.exports = oldBrowser
    }
    
    It could be that we come across this issue not because this library does not support node, but because there might be an issue in your build setup, as explained below.
  2. Why is a random UID necessary? It is used by serialize-javascript only once during initialization here, evidently for security reasons.
  3. Simple, node-only workaround Put this code anywhere at the very beginning of your code:
    import nodeCrypto from 'crypto';
    // const nodeCrypto = __non_webpack_require__('crypto'); // use this instead, if you are working on a Webpack@4 `umd` build
    
    /**
    * @see https://github.com/yahoo/serialize-javascript/issues/87
    */
    (function hackfixes() {
      // eslint-disable-next-line global-require
      if (!globalThis.crypto) {
        globalThis.crypto = {};
      }
      if (!globalThis.crypto.getRandomValues) {
        globalThis.crypto.getRandomValues = (buf) => {
          const bytes = nodeCrypto.randomBytes(buf.length);
          buf.set(bytes);
          return buf;
        };
      }
    })();
    
  4. Portable workaround As pointed out here, you can use the get-random-values polyfill. The annoying part is that it would be quite hacky to tell randombytes to use that.
  5. NOTE: I'm using Webpack@4 to produce a umd build, and webpack@4 is not very good at that. This often translates it to Webpack deciding to use browser-only shims.

Domiii avatar May 03 '21 08:05 Domiii

Why is a random UID necessary?

It is used by serialize-javascript only once during initialization here, evidently for security reasons.

I had a second brief look at the code, and it still looks to me like a lame implementation rather than a security feature: UID is only used inside temporary placeholders during stringification, and there is no UID included into stringified result. In my current understanding, if here: https://github.com/yahoo/serialize-javascript/blob/45fb0f1d51af5606eb9c77336780e2017cdef9bd/index.js#L94-L117 instead of writing out indices of detected (extracted into auxiliary arrays) functions / objects into intermediate string as text, one would bother to index them in a better way (say, having a separate mapping object saying for each extracted function / object at which location in the output string it should be inserted in the end), it would not be necessary to use any UID.

birdofpreyru avatar May 05 '21 11:05 birdofpreyru

Why didn't you close this issue? it's for version 3.x

amerllica avatar Nov 22 '22 16:11 amerllica

@amerllica The code in question, thus the issue, is still present in the current version v6.0.0.

birdofpreyru avatar Nov 22 '22 16:11 birdofpreyru