Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@uppy/companion: do not use unsafe call to JSON.stringify #5422

Merged
merged 2 commits into from
Aug 29, 2024

Conversation

aduh95
Copy link
Contributor

@aduh95 aduh95 commented Aug 21, 2024

Calling JSON.stringify on some objects (e.g. self-referencing objects) can lead to a TypeError; instead we can use util.inspect that is able to deal with those, and is basically guaranteed to never throw. It also means the output will be more readable by humans – but no as readable for machines.

Copy link
Contributor

github-actions bot commented Aug 21, 2024

Diff output files
diff --git a/packages/@uppy/companion/lib/server/emitter/redis-emitter.js b/packages/@uppy/companion/lib/server/emitter/redis-emitter.js
index aa4f768..62796bc 100644
--- a/packages/@uppy/companion/lib/server/emitter/redis-emitter.js
+++ b/packages/@uppy/companion/lib/server/emitter/redis-emitter.js
@@ -1,7 +1,12 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 const { EventEmitter } = require("node:events");
+const { default: safeStringify } = require("fast-safe-stringify");
 const logger = require("../logger");
+function replacer(key, value) {
+  // Remove the circular structure and internal ones
+  return key[0] === "_" || value === "[Circular]" ? undefined : value;
+}
 /**
  * This module simulates the builtin events.EventEmitter but with the use of redis.
  * This is useful for when companion is running on multiple instances and events need
@@ -134,7 +139,7 @@ module.exports = (redisClient, redisPubSubScope) => {
    * @param {string} eventName name of the event
    */
   function emit(eventName, ...args) {
-    runWhenConnected(() => publisher.publish(getPrefixedEventName(eventName), JSON.stringify(args)));
+    runWhenConnected(() => publisher.publish(getPrefixedEventName(eventName), safeStringify(args, replacer)));
   }
   /**
    * Remove all listeners of an event

The following build files have been modified and might affect the actual diff:

yarn.lock

Copy link
Member

@Murderlon Murderlon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit inefficient but should we temporarily add try/catch with JSON.stringify to this change as well so we can log the eventName and object?

I'd rather work towards not sending massive circular objects over redis than to silence the problem.

@Murderlon Murderlon requested a review from mifi August 22, 2024 08:46
@mifi
Copy link
Contributor

mifi commented Aug 22, 2024

Isn’t this a breaking change if the output from util.inspect is not the same as that from JSON.stringify? Wouldn’t using JSON.parse on an string that has been util.inspect’ed possibly fail? (I assume that JSON.stringify was not added here in order to make it human readable).
I agree we should prevent this from happening in the first place by making sure we dont try to publish such complex objects, however maybe a try/catch would be best for now if we want to be able to identify/eliminate such cases?

@Murderlon
Copy link
Member

I don't think it's breaking, in fact it's less breaking as the current code crashed in production and this won't.

@mifi
Copy link
Contributor

mifi commented Aug 22, 2024

I mean breaking as in api breaking: if util.inspect changes the api and someone is consuming the event on redis, then it could break their implementation

Copy link

New and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher

🚮 Removed packages: npm/[email protected]), npm/[email protected]), npm/[email protected]), npm/[email protected]), npm/[email protected])

View full report↗︎


const logger = require('../logger')

function replacer(key, value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to put replacer inside the function call so typescript can automatically infer the types for key and value, but we can also do that later when refactoring to typescript


const logger = require('../logger')

function replacer(key, value) {
// Remove the circular structure and internal ones
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Remove the circular structure and internal ones
// Remove the circular references and internal properties
// so that they don't get exposed to the browser (uppy client)

@Murderlon
Copy link
Member

Shall we also stop sending the entire req object in emits in this PR?

Over Slack we concluded that it wouldn't be a breaking change.

@mifi
Copy link
Contributor

mifi commented Aug 29, 2024

You mean stop sending the whole Error object and instead just send message? Yesterday I agreed yes, but now I realised that I misread the code and I thought it was only message being used by Uppy. However I can also see that the whole error object structure is being passed as cause for the created error, meaning somebody could be depending on cause when catching an error. If we can consider removing cause as not being a breaking change, then sure we can do it.

@Murderlon
Copy link
Member

Discussed just now. Let's keep things for now and not block this PR any further.

@aduh95 aduh95 merged commit ededd0b into transloadit:main Aug 29, 2024
21 checks passed
@aduh95 aduh95 deleted the util-inspect-instead-of-json branch August 29, 2024 12:47
github-actions bot added a commit that referenced this pull request Aug 29, 2024
| Package                | Version | Package                | Version |
| ---------------------- | ------- | ---------------------- | ------- |
| @uppy/aws-s3           |   4.1.0 | @uppy/informer         |   4.1.0 |
| @uppy/box              |   3.1.0 | @uppy/instagram        |   4.1.0 |
| @uppy/companion        |   5.1.0 | @uppy/locales          |   4.1.0 |
| @uppy/companion-client |   4.1.0 | @uppy/onedrive         |   4.1.0 |
| @uppy/compressor       |   2.1.0 | @uppy/remote-sources   |   2.2.0 |
| @uppy/core             |   4.2.0 | @uppy/screen-capture   |   4.1.0 |
| @uppy/dashboard        |   4.1.0 | @uppy/tus              |   4.1.0 |
| @uppy/dropbox          |   4.1.0 | @uppy/unsplash         |   4.1.0 |
| @uppy/facebook         |   4.1.0 | @uppy/url              |   4.1.0 |
| @uppy/google-drive     |   4.1.0 | @uppy/xhr-upload       |   4.1.0 |
| @uppy/google-photos    |   0.3.0 | @uppy/zoom             |   3.1.0 |
| @uppy/image-editor     |   3.1.0 | uppy                   |   4.3.0 |

- @uppy/core,@uppy/dashboard: Pass container to `UIPlugin.render` for non-Preact integration (Merlijn Vos / #5437)
- @uppy/companion: do not use unsafe call to `JSON.stringify` (Antoine du Hamel / #5422)
- meta: Fix yarn.lock (Murderlon)
- @uppy/locales: Fix locale-pack for en_US (Merlijn Vos / #5431)
- meta: Add tsconfig to packages in private/ (Merlijn Vos / #5432)
- @uppy/remote-sources: support companionKeysParams (Merlijn Vos / #5434)
- @uppy/aws-s3,@uppy/box,@uppy/compressor,@uppy/dropbox,@uppy/facebook,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/screen-capture,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/xhr-upload,@uppy/zoom: export plugin options (Antoine du Hamel / #5433)
- docs: correctly list exported components (Merlijn Vos / #5417)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants