From b6646d7868dffb217a0d5e83be90fa60b2099314 Mon Sep 17 00:00:00 2001 From: jamaljsr <1356600+jamaljsr@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:44:18 -0500 Subject: [PATCH] app: patch vulnerable production dependencies Bumps the two production dependencies flagged by `yarn audit` that have fixes available. http-proxy-middleware goes to 2.0.10 for the Host-header routing bypass. It also needs a `resolutions` entry, otherwise yarn keeps a second copy at 2.0.9 under react-scripts > webpack-dev-server because the existing `^2.0.0` range is already satisfied. react-router-dom goes to 6.30.4 for the untrusted-path open redirect. It is pinned exactly rather than with a caret: @remix-run/router is a direct dependency at an exact version and react-router-dom pins its own copy exactly too, so a caret here would let a future patch release install a second copy of the router and hand a history built by a different module instance. That bump pulls in a few related changes: - react-router 6.4 folded the `history` package into @remix-run/router, so the store now imports `createBrowserHistory` from there. Passing our own history object is discouraged and would bundle history twice. - @remix-run/router's type declarations use accessors in interfaces, which needs TypeScript 4.3 or newer, so typescript goes to 4.9.5. TS 4.4 also began typing `catch` variables as `unknown`, so `handleError` now takes `unknown` and narrows before reading `.message`, and the five sites that read it directly narrow first as well. - @types/history and @types/react-router-dom are v5-era leftovers that now conflict with the types react-router-dom ships itself. - The new history only accepts one active listener, but RouterStore and both need one, so RouterStore subscribes once and fans updates out. It also needs `v5Compat` so listeners fire on push() and replace() rather than only on back/forward. reactour still pulls in a vulnerable lodash.pick with no patched version published. reactour 1.18.0 is the end of the v1 line and v2 is a rewrite, so that one is left alone for now. --- app/package.json | 10 ++-- app/src/__tests__/store/appView.spec.ts | 10 ++++ app/src/components/auth/AuthPage.tsx | 2 +- app/src/store/store.ts | 6 +- app/src/store/stores/authStore.ts | 4 +- app/src/store/stores/batchStore.ts | 4 +- app/src/store/stores/routerStore.ts | 29 ++++++++-- app/src/store/views/appView.ts | 4 +- app/src/util/appStorage.ts | 5 +- app/yarn.lock | 77 ++++++++----------------- 10 files changed, 80 insertions(+), 71 deletions(-) diff --git a/app/package.json b/app/package.json index 449bd22d..31b4dafe 100644 --- a/app/package.json +++ b/app/package.json @@ -22,6 +22,7 @@ "@emotion/react": "11.4.0", "@emotion/styled": "11.3.0", "@improbable-eng/grpc-web": "0.14.0", + "@remix-run/router": "1.23.3", "@types/react-collapse": "^5.0.1", "big.js": "6.1.1", "bootstrap": "4.6.1", @@ -31,7 +32,7 @@ "date-fns": "2.14.0", "debug": "4.3.1", "file-saver": "2.0.2", - "http-proxy-middleware": "2.0.9", + "http-proxy-middleware": "2.0.10", "i18next": "19.5.1", "i18next-browser-languagedetector": "5.0.0", "lodash": "4.18.1", @@ -48,7 +49,7 @@ "react-collapse": "^5.1.1", "react-dom": "17.0.2", "react-i18next": "13.5.0", - "react-router-dom": "^6.3.0", + "react-router-dom": "6.30.4", "react-toastify": "6.0.6", "react-virtualized": "9.21.2", "reactour": "1.18.0", @@ -71,13 +72,11 @@ "@types/debug": "4.1.5", "@types/file-saver": "2.0.1", "@types/google-protobuf": "3.15.10", - "@types/history": "4.7.6", "@types/jest": "27.4.1", "@types/lodash": "4.14.157", "@types/node": "14.0.14", "@types/react": "17.0.13", "@types/react-dom": "17.0.8", - "@types/react-router-dom": "^5.3.3", "@types/react-virtualized": "9.21.10", "@types/reactour": "1.17.1", "@types/semver": "^7.3.9", @@ -96,10 +95,11 @@ "sass": "1.43.4", "storybook": "7.5.3", "ts-protoc-gen": "0.15.0", - "typescript": "4.1.6", + "typescript": "4.9.5", "webpack": "5.89.0" }, "resolutions": { + "http-proxy-middleware": "2.0.10", "strip-ansi": "6.0.1", "jackspeak": "2.1.1", "wrap-ansi": "7.0.0" diff --git a/app/src/__tests__/store/appView.spec.ts b/app/src/__tests__/store/appView.spec.ts index abeaac78..a8235f4b 100644 --- a/app/src/__tests__/store/appView.spec.ts +++ b/app/src/__tests__/store/appView.spec.ts @@ -36,6 +36,16 @@ describe('AppView', () => { expect(store.alerts.size).toBe(1); }); + it('should handle values thrown that are not Errors', () => { + // Nothing in the language guarantees a thrown value is an Error, and + // showing the user an empty alert is worse than showing the raw value. + store.handleError('something went wrong', 'title'); + expect(store.alerts.size).toBe(1); + const alert = values(store.alerts)[0]; + expect(alert.message).toBe('something went wrong'); + expect(alert.title).toBe('title'); + }); + it('should handle authentication errors', () => { rootStore.authStore.authenticated = true; expect(store.alerts.size).toBe(0); diff --git a/app/src/components/auth/AuthPage.tsx b/app/src/components/auth/AuthPage.tsx index e7d04e82..34120b20 100644 --- a/app/src/components/auth/AuthPage.tsx +++ b/app/src/components/auth/AuthPage.tsx @@ -111,7 +111,7 @@ const AuthPage: React.FC = () => { try { await store.authStore.login(pass); } catch (err) { - setError(err.message); + setError(err instanceof Error ? err.message : String(err)); const errors = store.authStore.errors; setErrorDetailLit(errors.litDetail); setErrorDetailLnd(errors.lndDetail); diff --git a/app/src/store/store.ts b/app/src/store/store.ts index a68ba8ba..5e64d3d7 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -1,6 +1,6 @@ import { autorun, makeAutoObservable, runInAction } from 'mobx'; import { IS_DEV, IS_TEST, USE_SAMPLE_DATA } from 'config'; -import { createBrowserHistory, History } from 'history'; +import { createBrowserHistory, History } from '@remix-run/router'; import AppStorage from 'util/appStorage'; import CsvExporter from 'util/csv'; import { actionLog, Logger } from 'util/log'; @@ -217,7 +217,9 @@ export const createStore = (grpcClient?: GrpcClient, appStorage?: AppStorage) => const poolApi = new PoolApi(grpc); const litApi = new LitApi(grpc); const csv = new CsvExporter(); - const history = createBrowserHistory(); + // v5Compat is required for listeners to be notified of push() and replace() + // calls, not just browser back/forward navigation. + const history = createBrowserHistory({ v5Compat: true }); const store = new Store( lndApi, diff --git a/app/src/store/stores/authStore.ts b/app/src/store/stores/authStore.ts index 27bcc768..3a8135d6 100644 --- a/app/src/store/stores/authStore.ts +++ b/app/src/store/stores/authStore.ts @@ -126,7 +126,9 @@ export default class AuthStore { this.setCredentials(''); this._store.log.error('connection failure'); this.errors = { mainErr: '', litDetail: '', lndDetail: '' }; - throw new Error(await this.getErrMsg(error.message)); + throw new Error( + await this.getErrMsg(error instanceof Error ? error.message : String(error)), + ); } } diff --git a/app/src/store/stores/batchStore.ts b/app/src/store/stores/batchStore.ts index 1f5d96d7..655bdbd7 100644 --- a/app/src/store/stores/batchStore.ts +++ b/app/src/store/stores/batchStore.ts @@ -150,7 +150,7 @@ export default class BatchStore { this.loading = false; }); } catch (error) { - if (error.message !== 'batch snapshot not found') { + if (!(error instanceof Error) || error.message !== 'batch snapshot not found') { this._store.appView.handleError(error, `Unable to fetch batch with id ${prevId}`); } } @@ -171,7 +171,7 @@ export default class BatchStore { this._store.log.info('updated batchStore.markets', toJS(this.markets)); }); } catch (error) { - if (error.message === 'batch snapshot not found') return; + if (error instanceof Error && error.message === 'batch snapshot not found') return; this._store.appView.handleError(error, 'Unable to fetch the latest batch'); } } diff --git a/app/src/store/stores/routerStore.ts b/app/src/store/stores/routerStore.ts index 8945e937..dcff976c 100644 --- a/app/src/store/stores/routerStore.ts +++ b/app/src/store/stores/routerStore.ts @@ -1,5 +1,8 @@ import { makeAutoObservable, runInAction } from 'mobx'; -import { History, Location } from 'history'; +import { History, Location } from '@remix-run/router'; + +/** the listener callback accepted by the history object */ +type Listener = Parameters[0]; export default class RouterStore { /** the history object */ @@ -11,13 +14,31 @@ export default class RouterStore { constructor(history: History) { makeAutoObservable(this, { history: false }, { deep: false, autoBind: true }); - this.history = history; this.location = history.location; - history.listen(({ location }) => { + // The router's history implementation only accepts a single active + // listener, but both this store and the component need to + // observe navigation. Subscribe once here and fan the updates out to any + // additional listeners. + const listeners = new Set(); + history.listen(update => { runInAction(() => { - this.location = location; + this.location = update.location; }); + listeners.forEach(listener => listener(update)); + }); + + // Expose a history that hands out fan-out subscriptions instead of the + // single underlying one. A proxy is used so that the `location` and + // `action` getters continue to read through to the real history object. + this.history = new Proxy(history, { + get: (target, prop, receiver) => { + if (prop !== 'listen') return Reflect.get(target, prop, receiver); + return (listener: Listener) => { + listeners.add(listener); + return () => listeners.delete(listener); + }; + }, }); } diff --git a/app/src/store/views/appView.ts b/app/src/store/views/appView.ts index b4985d4b..e7534689 100644 --- a/app/src/store/views/appView.ts +++ b/app/src/store/views/appView.ts @@ -210,13 +210,13 @@ export default class AppView { } /** handle errors by showing a notification and/or the auth screen */ - handleError(error: Error, title?: string) { + handleError(error: unknown, title?: string) { if (error instanceof AuthenticationError) { // this will automatically redirect to the auth page this._store.authStore.authenticated = false; this.notify(l('authErrorMsg'), l('authErrorTitle')); } else { - this.notify(error.message, title); + this.notify(error instanceof Error ? error.message : String(error), title); } } } diff --git a/app/src/util/appStorage.ts b/app/src/util/appStorage.ts index 34aee850..973ce99f 100644 --- a/app/src/util/appStorage.ts +++ b/app/src/util/appStorage.ts @@ -96,7 +96,10 @@ export default class AppStorage { this.set(cacheKey, { expires, data }); log.info(`updated cache with ${keys.length} new ${cacheKey}`); } catch (error) { - log.error(`failed to fetch ${cacheKey} from the API`, error.message); + log.error( + `failed to fetch ${cacheKey} from the API`, + error instanceof Error ? error.message : String(error), + ); } } diff --git a/app/yarn.lock b/app/yarn.lock index e770fa11..744aca61 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -3177,6 +3177,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@remix-run/router@1.23.3": + version "1.23.3" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.3.tgz#957c098d4393d301a8aa7dccf3ef28ea5430e36a" + integrity sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -4545,16 +4550,6 @@ dependencies: "@types/node" "*" -"@types/history@4.7.6": - version "4.7.6" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356" - integrity sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w== - -"@types/history@^4.7.11": - version "4.7.11" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" - integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== - "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" @@ -4708,23 +4703,6 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^5.3.3": - version "5.3.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" - integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*": - version "5.1.18" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" - integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-virtualized@9.21.10": version "9.21.10" resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.10.tgz#cd072dc9c889291ace2c4c9de8e8c050da8738b7" @@ -9096,13 +9074,6 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -history@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" - integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== - dependencies: - "@babel/runtime" "^7.7.6" - hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -9238,10 +9209,10 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-middleware@2.0.9, http-proxy-middleware@^2.0.0: - version "2.0.9" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" - integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== +http-proxy-middleware@2.0.10, http-proxy-middleware@^2.0.0: + version "2.0.10" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz#b2df7b705203d7a8c269ac8450cf96b00c532f94" + integrity sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" @@ -12811,20 +12782,20 @@ react-remove-scroll@2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-router-dom@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" - integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== +react-router-dom@6.30.4: + version "6.30.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.4.tgz#f7167bf3da6c7d9132130ea985dd06def25e84d5" + integrity sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q== dependencies: - history "^5.2.0" - react-router "6.3.0" + "@remix-run/router" "1.23.3" + react-router "6.30.4" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== +react-router@6.30.4: + version "6.30.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.4.tgz#638f35176527bd243d96d81d35d33b757bad46c2" + integrity sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA== dependencies: - history "^5.2.0" + "@remix-run/router" "1.23.3" react-scripts@^5.0.1: version "5.0.1" @@ -14574,10 +14545,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@4.1.6: - version "4.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.6.tgz#1becd85d77567c3c741172339e93ce2e69932138" - integrity sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow== +typescript@4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== uglify-js@^3.1.4: version "3.15.5"