diff --git a/.circleci/config.yml b/.circleci/config.yml index 276daf47c9..1dcabf00a5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -560,7 +560,7 @@ parameters: type: string dev_git_branch: # change to feature branch to test deployment description: "Name of github branch that will deploy to dev" - default: "al-ttahub-3196-new-tr-views" + default: "al-ttahub-3329-fix-event-view" type: string sandbox_git_branch: # change to feature branch to test deployment default: "kw-fix-duplicate-programs" diff --git a/frontend/src/pages/ViewTrainingReport/__tests__/index.js b/frontend/src/pages/ViewTrainingReport/__tests__/index.js index 2a6e603641..6401b241b4 100644 --- a/frontend/src/pages/ViewTrainingReport/__tests__/index.js +++ b/frontend/src/pages/ViewTrainingReport/__tests__/index.js @@ -73,7 +73,7 @@ const mockEvent = (data = {}) => ({ eventName: 'Health Webinar Series: Oral Health and Dental Care from a Regional and State Perspective', eventOrganizer: 'Regional PD Event (with National Centers)', 'Full Event Title': 'R03 Health Webinar Series: Oral Health and Dental Care from a Regional and State Perspective', - targetPopulations: ['None'], + targetPopulations: ['Tgt Pop 1'], 'Event Duration/# NC Days of Support': 'Series', }, updatedAt: '2023-06-27T13:46:29.884Z', @@ -251,7 +251,7 @@ describe('ViewTrainingReport', () => { expect(screen.getByText('Regional PD Event (with National Centers)')).toBeInTheDocument(); // target populations - expect(screen.getByText('None')).toBeInTheDocument(); + expect(screen.getByText('Tgt Pop 1')).toBeInTheDocument(); // session 1 expect(screen.getByText('Session 1')).toBeInTheDocument(); @@ -581,6 +581,166 @@ describe('ViewTrainingReport', () => { expect(await screen.findByText('USER 2')).toBeInTheDocument(); }); + it('displays the is ist visit field and the appropriate participants', async () => { + const e = mockEvent(); + e.sessionReports = [{ + ...e.sessionReports[0], + data: { + ...e.sessionReports[0].data, + isIstVisit: 'yes', + regionalOfficeTta: ['Ist Office 1', 'Ist Office 2'], + }, + }, + { + ...e.sessionReports[1], + data: { + ...e.sessionReports[1].data, + isIstVisit: 'no', + recipients: [{ label: 'Recipient 1' }, { label: 'Recipient 2' }], + participants: ['Participants 1', 'Participants 2'], + }, + }]; + fetchMock.getOnce('/api/events/id/1?readOnly=true', e); + + fetchMock.getOnce('/api/users/names?ids=1', ['USER 1']); + fetchMock.getOnce('/api/users/names?ids=2', ['USER 2']); + + act(() => { + renderTrainingReport(); + }); + + expect(await screen.findByRole('heading', { name: 'Training event report R03-PD-23-1037' })).toBeInTheDocument(); + + expect(screen.queryAllByText('IST visit').length).toBe(2); + expect(await screen.findByText('Regional Office/TTA')).toBeInTheDocument(); + expect(await screen.findByText('Yes')).toBeInTheDocument(); + expect(await screen.findByText(/ist office 1, ist office 2/i)).toBeInTheDocument(); + + expect(await screen.findByText('Recipient participants')).toBeInTheDocument(); + expect(await screen.findByText('No')).toBeInTheDocument(); + expect(await screen.findByText(/Recipient 1, Recipient 2/i)).toBeInTheDocument(); + + expect(await screen.findByText('Recipient participants')).toBeInTheDocument(); + expect(await screen.findByText(/Participants 1, Participants 2/i)).toBeInTheDocument(); + }); + + it('displays the delivery method field and the appropriate participants attending', async () => { + const e = mockEvent(); + e.sessionReports = [{ + ...e.sessionReports[0], + data: { + ...e.sessionReports[0].data, + deliveryMethod: 'in-person', + numberOfParticipants: 10, + }, + }, + { + ...e.sessionReports[1], + data: { + ...e.sessionReports[1].data, + deliveryMethod: 'hybrid', + numberOfParticipantsInPerson: 11, + numberOfParticipantsVirtually: 12, + }, + }]; + fetchMock.getOnce('/api/events/id/1?readOnly=true', e); + + fetchMock.getOnce('/api/users/names?ids=1', ['USER 1']); + fetchMock.getOnce('/api/users/names?ids=2', ['USER 2']); + + act(() => { + renderTrainingReport(); + }); + + expect(await screen.findByRole('heading', { name: 'Training event report R03-PD-23-1037' })).toBeInTheDocument(); + + expect(screen.queryAllByText('Delivery method').length).toBe(2); + expect(await screen.findByText('In-person')).toBeInTheDocument(); + expect(await screen.findByText('Number of participants')).toBeInTheDocument(); + expect(await screen.findByText('10')).toBeInTheDocument(); + + expect(await screen.findByText('Hybrid')).toBeInTheDocument(); + expect(await screen.findByText('Number of participants attending in person')).toBeInTheDocument(); + expect(await screen.findByText('11')).toBeInTheDocument(); + expect(await screen.findByText('Number of participants attending virtually')).toBeInTheDocument(); + expect(await screen.findByText('12')).toBeInTheDocument(); + }); + + it('display the correct value for Is IST visit if the value isIstVisit is not set and we have recipients', async () => { + const e = mockEvent(); + e.sessionReports = [{ + ...e.sessionReports[0], + data: { + ...e.sessionReports[0].data, + isIstVisit: null, + }, + }]; + + fetchMock.getOnce('/api/events/id/1?readOnly=true', e); + fetchMock.getOnce('/api/users/names?ids=1', ['USER 1']); + fetchMock.getOnce('/api/users/names?ids=2', ['USER 2']); + + act(() => { + renderTrainingReport(); + }); + + expect(await screen.findByRole('heading', { name: 'Training event report R03-PD-23-1037' })).toBeInTheDocument(); + expect(await screen.findByText('No')).toBeInTheDocument(); + + expect(screen.queryAllByText('IST visit').length).toBe(1); + }); + + it('display the correct value for Is IST visit if the value isIstVisit is not set and we have no recipients', async () => { + const e = mockEvent(); + e.sessionReports = [{ + ...e.sessionReports[0], + data: { + ...e.sessionReports[0].data, + isIstVisit: null, + recipients: [], + regionalOfficeTta: ['office 1', 'office 2'], + }, + }]; + + fetchMock.getOnce('/api/events/id/1?readOnly=true', e); + fetchMock.getOnce('/api/users/names?ids=1', ['USER 1']); + fetchMock.getOnce('/api/users/names?ids=2', ['USER 2']); + + act(() => { + renderTrainingReport(); + }); + + expect(await screen.findByRole('heading', { name: 'Training event report R03-PD-23-1037' })).toBeInTheDocument(); + expect(await screen.findByText('Yes')).toBeInTheDocument(); + + expect(screen.queryAllByText('IST visit').length).toBe(1); + expect(await screen.findByText(/office 1, office 2/i)).toBeInTheDocument(); + }); + + it('displays none for objectiveResources not set', async () => { + const e = mockEvent(); + e.sessionReports = [{ + ...e.sessionReports[0], + data: { + ...e.sessionReports[0].data, + objectiveResources: [{ value: '' }], + courses: [], + files: [], + }, + }]; + + fetchMock.getOnce('/api/events/id/1?readOnly=true', e); + fetchMock.getOnce('/api/users/names?ids=1', ['USER 1']); + fetchMock.getOnce('/api/users/names?ids=2', ['USER 2']); + + act(() => { + renderTrainingReport(); + }); + + expect(await screen.findByRole('heading', { name: 'Training event report R03-PD-23-1037' })).toBeInTheDocument(); + expect(await screen.queryAllByText('None').length).toBe(3); + }); + describe('formatOwnerName', () => { test('handles an error', () => { const result = formatOwnerName({ eventReportPilotNationalCenterUsers: 123 }); diff --git a/frontend/src/pages/ViewTrainingReport/index.js b/frontend/src/pages/ViewTrainingReport/index.js index acb9d9d60f..5e11df5bdb 100644 --- a/frontend/src/pages/ViewTrainingReport/index.js +++ b/frontend/src/pages/ViewTrainingReport/index.js @@ -214,15 +214,48 @@ export default function ViewTrainingReport({ match }) { 'Training type': event.data['Event Duration/# NC Days of Support'], Reasons: event.data.reasons, 'Target populations': event.data.targetPopulations, - }, - striped: true, - }, { - heading: 'Vision', - data: { Vision: event.data.vision, }, + striped: true, }] : []; + const isIstVisit = (session) => { + if (session.data.isIstVisit === 'yes' || (session.data.regionalOfficeTta && session.data.regionalOfficeTta.length > 0)) { + return true; + } + return false; + }; + + const generateIstOfficeOrRecipientProperties = (session) => { + if (isIstVisit(session)) { + return { + 'Regional Office/TTA': session.data.regionalOfficeTta.join(', '), + }; + } + + return { + Recipients: session.data.recipients ? session.data.recipients.map((r) => r.label).join(', ') : '', + 'Recipient participants': session.data.participants ? session.data.participants.join(', ') : [], + }; + }; + + const generateNumberOfParticipants = (session) => { + // In person or virtual. + if (session.data.deliveryMethod === 'in-person' || session.data.deliveryMethod === 'virtual') { + const numberOfParticipants = session.data.numberOfParticipants ? session.data.numberOfParticipants.toString() : ''; + return { + 'Number of participants': numberOfParticipants, + }; + } + // Hybrid. + const numberOfParticipantsInPerson = session.data.numberOfParticipantsInPerson ? session.data.numberOfParticipantsInPerson.toString() : ''; + const numberOfParticipantsVirtually = session.data.numberOfParticipantsVirtually ? session.data.numberOfParticipantsVirtually.toString() : ''; + return { + 'Number of participants attending in person': numberOfParticipantsInPerson, + 'Number of participants attending virtually': numberOfParticipantsVirtually, + }; + }; + const sessions = event && event.sessionReports ? event.sessionReports.map((session, index) => ( o.value) : [], - 'iPD Courses': session.data.courses ? session.data.courses.map((o) => o.name) : [], - 'Resource attachments': session.data.files ? session.data.files.map((f) => f.originalFileName) : [], + 'Resource links': session.data.objectiveResources && session.data.objectiveResources.filter((r) => r.value).length ? session.data.objectiveResources.map((o) => o.value) : 'None', + 'iPD Courses': session.data.courses && session.data.courses.length ? session.data.courses.map((o) => o.name) : 'None', + 'Resource attachments': session.data.files && session.data.files.length ? session.data.files.map((f) => f.originalFileName) : 'None', 'Support type': session.data.objectiveSupportType, }, }, { heading: 'Participants', striped: true, data: { - Recipients: session.data.recipients ? session.data.recipients.map((r) => r.label).join(', ') : '', - 'Recipient participants': session.data.participants ? session.data.participants.join(', ') : [], - 'Number of participants': String(( - session.data.numberOfParticipants || 0 - ) + ( - session.data.numberOfParticipantsVirtually || 0 - ) + ( - session.data.numberOfParticipantsInPerson || 0 - )), + 'IST visit': isIstVisit(session) ? 'Yes' : 'No', + ...generateIstOfficeOrRecipientProperties(session), 'Delivery method': capitalize(session.data.deliveryMethod || ''), + ...generateNumberOfParticipants(session), 'Language used': session.data.language ? session.data.language.join(', ') : [], 'TTA provided': session.data.ttaProvided, }, diff --git a/frontend/yarn-audit-known-issues b/frontend/yarn-audit-known-issues index afca62462a..697c6815af 100644 --- a/frontend/yarn-audit-known-issues +++ b/frontend/yarn-audit-known-issues @@ -3,4 +3,3 @@ {"type":"auditAdvisory","data":{"resolution":{"id":1097682,"path":"react-scripts>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-environment-jsdom>jsdom>tough-cookie","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"4.0.0","paths":["react-scripts>jest>@jest/core>jest-config>jest-environment-jsdom>jsdom>tough-cookie","react-scripts>jest>jest-cli>@jest/core>jest-config>jest-environment-jsdom>jsdom>tough-cookie","react-scripts>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-environment-jsdom>jsdom>tough-cookie"]}],"found_by":null,"deleted":null,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-26136\n- https://github.com/salesforce/tough-cookie/issues/282\n- https://github.com/salesforce/tough-cookie/commit/12d474791bb856004e858fdb1c47b7608d09cf6e\n- https://github.com/salesforce/tough-cookie/releases/tag/v4.1.3\n- https://security.snyk.io/vuln/SNYK-JS-TOUGHCOOKIE-5672873\n- https://lists.debian.org/debian-lts-announce/2023/07/msg00010.html\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3HUE6ZR5SL73KHL7XUPAOEL6SB7HUDT2\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6PVVPNSAGSDS63HQ74PJ7MZ3MU5IYNVZ\n- https://security.netapp.com/advisory/ntap-20240621-0006\n- https://github.com/advisories/GHSA-72xf-g2v4-qvf3","created":"2023-07-01T06:30:16.000Z","id":1097682,"npm_advisory_id":null,"overview":"Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in `rejectPublicSuffixes=false` mode. This issue arises from the manner in which the objects are initialized.","reported_by":null,"title":"tough-cookie Prototype Pollution vulnerability","metadata":null,"cves":["CVE-2023-26136"],"access":"public","severity":"moderate","module_name":"tough-cookie","vulnerable_versions":"<4.1.3","github_advisory_id":"GHSA-72xf-g2v4-qvf3","recommendation":"Upgrade to version 4.1.3 or later","patched_versions":">=4.1.3","updated":"2024-06-21T21:33:53.000Z","cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"},"cwe":["CWE-1321"],"url":"https://github.com/advisories/GHSA-72xf-g2v4-qvf3"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1099525,"path":"react-scripts>webpack-dev-server>express>serve-static>send","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"0.18.0","paths":["react-scripts>webpack-dev-server>express>serve-static>send"]}],"found_by":null,"deleted":null,"references":"- https://github.com/pillarjs/send/security/advisories/GHSA-m6fv-jmcg-4jfg\n- https://nvd.nist.gov/vuln/detail/CVE-2024-43799\n- https://github.com/pillarjs/send/commit/ae4f2989491b392ae2ef3b0015a019770ae65d35\n- https://github.com/advisories/GHSA-m6fv-jmcg-4jfg","created":"2024-09-10T19:42:41.000Z","id":1099525,"npm_advisory_id":null,"overview":"### Impact\n\npassing untrusted user input - even after sanitizing it - to `SendStream.redirect()` may execute untrusted code\n\n### Patches\n\nthis issue is patched in send 0.19.0\n\n### Workarounds\n\nusers are encouraged to upgrade to the patched version of express, but otherwise can workaround this issue by making sure any untrusted inputs are safe, ideally by validating them against an explicit allowlist\n\n### Details\n\nsuccessful exploitation of this vector requires the following:\n\n1. The attacker MUST control the input to response.redirect()\n1. express MUST NOT redirect before the template appears\n1. the browser MUST NOT complete redirection before:\n1. the user MUST click on the link in the template\n","reported_by":null,"title":"send vulnerable to template injection that can lead to XSS","metadata":null,"cves":["CVE-2024-43799"],"access":"public","severity":"moderate","module_name":"send","vulnerable_versions":"<0.19.0","github_advisory_id":"GHSA-m6fv-jmcg-4jfg","recommendation":"Upgrade to version 0.19.0 or later","patched_versions":">=0.19.0","updated":"2024-09-10T19:42:42.000Z","cvss":{"score":5,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"},"cwe":["CWE-79"],"url":"https://github.com/advisories/GHSA-m6fv-jmcg-4jfg"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1099597,"path":"react-admin>ra-ui-materialui>dompurify","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"2.4.4","paths":["react-admin>ra-ui-materialui>dompurify"]}],"found_by":null,"deleted":null,"references":"- https://github.com/cure53/DOMPurify/security/advisories/GHSA-mmhx-hmjr-r674\n- https://github.com/cure53/DOMPurify/commit/1e520262bf4c66b5efda49e2316d6d1246ca7b21\n- https://github.com/cure53/DOMPurify/commit/26e1d69ca7f769f5c558619d644d90dd8bf26ebc\n- https://nvd.nist.gov/vuln/detail/CVE-2024-45801\n- https://github.com/advisories/GHSA-mmhx-hmjr-r674","created":"2024-09-16T20:34:26.000Z","id":1099597,"npm_advisory_id":null,"overview":"It has been discovered that malicious HTML using special nesting techniques can bypass the depth checking added to DOMPurify in recent releases. It was also possible to use Prototype Pollution to weaken the depth check.\n\nThis renders dompurify unable to avoid XSS attack.\n\nFixed by https://github.com/cure53/DOMPurify/commit/1e520262bf4c66b5efda49e2316d6d1246ca7b21 (3.x branch) and https://github.com/cure53/DOMPurify/commit/26e1d69ca7f769f5c558619d644d90dd8bf26ebc (2.x branch).","reported_by":null,"title":"DOMPurify allows tampering by prototype pollution","metadata":null,"cves":["CVE-2024-45801"],"access":"public","severity":"high","module_name":"dompurify","vulnerable_versions":"<2.5.4","github_advisory_id":"GHSA-mmhx-hmjr-r674","recommendation":"Upgrade to version 2.5.4 or later","patched_versions":">=2.5.4","updated":"2024-09-16T22:37:33.000Z","cvss":{"score":7,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"},"cwe":["CWE-1321","CWE-1333"],"url":"https://github.com/advisories/GHSA-mmhx-hmjr-r674"}}} -{"type":"auditAdvisory","data":{"resolution":{"id":1099718,"path":"react-scripts>workbox-webpack-plugin>workbox-build>rollup","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"2.79.1","paths":["react-scripts>workbox-webpack-plugin>workbox-build>rollup"]}],"found_by":null,"deleted":null,"references":"- https://github.com/rollup/rollup/security/advisories/GHSA-gcx4-mw62-g8wm\n- https://nvd.nist.gov/vuln/detail/CVE-2024-47068\n- https://github.com/rollup/rollup/commit/2ef77c00ec2635d42697cff2c0567ccc8db34fb4\n- https://github.com/rollup/rollup/commit/e2552c9e955e0a61f70f508200ee9f752f85a541\n- https://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L157-L162\n- https://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L180-L185\n- https://github.com/advisories/GHSA-gcx4-mw62-g8wm","created":"2024-09-23T22:11:02.000Z","id":1099718,"npm_advisory_id":null,"overview":"### Summary\n\nA DOM Clobbering vulnerability was discovered in rollup when bundling scripts that use `import.meta.url` or with plugins that emit and reference asset files from code in `cjs`/`umd`/`iife` format. The DOM Clobbering gadget can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an `img` tag with an unsanitized `name` attribute) are present.\n\nIt's worth noting that similar issues in other popular bundlers like Webpack ([CVE-2024-43788](https://github.com/webpack/webpack/security/advisories/GHSA-4vvj-4cpr-p986)) have been reported, which might serve as a good reference.\n\n### Details\n\n#### Backgrounds\n\nDOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:\n\n[1] https://scnps.co/papers/sp23_domclob.pdf\n[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/\n\n#### Gadget found in `rollup`\n\nA DOM Clobbering vulnerability in `rollup` bundled scripts was identified, particularly when the scripts uses `import.meta` and set output in format of `cjs`/`umd`/`iife`. In such cases, `rollup` replaces meta property with the URL retrieved from `document.currentScript`.\n\nhttps://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L157-L162\n\nhttps://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L180-L185\n\nHowever, this implementation is vulnerable to a DOM Clobbering attack. The `document.currentScript` lookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the `src` attribute of the attacker-controlled element (e.g., an `img` tag ) is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.\n\n### PoC\n\nConsidering a website that contains the following `main.js` script, the devloper decides to use the `rollup` to bundle up the program: `rollup main.js --format cjs --file bundle.js`.\n\n```\nvar s = document.createElement('script')\ns.src = import.meta.url + 'extra.js'\ndocument.head.append(s)\n```\n\nThe output `bundle.js` is shown in the following code snippet.\n\n```\n'use strict';\n\nvar _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;\nvar s = document.createElement('script');\ns.src = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && False && _documentCurrentScript.src || new URL('bundle.js', document.baseURI).href)) + 'extra.js';\ndocument.head.append(s);\n```\n\nAdding the `rollup` bundled script, `bundle.js`, as part of the web page source code, the page could load the `extra.js` file from the attacker's domain, `attacker.controlled.server` due to the introduced gadget during bundling. The attacker only needs to insert an `img` tag with the name attribute set to `currentScript`. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.\n\n```\n\n\n\n rollup Example\n \n \n \n\n\n\n\n\n```\n\n### Impact\n\nThis vulnerability can result in cross-site scripting (XSS) attacks on websites that include rollup-bundled files (configured with an output format of `cjs`, `iife`, or `umd` and use `import.meta`) and allow users to inject certain scriptless HTML tags without properly sanitizing the `name` or `id` attributes.\n\n### Patch\n\nPatching the following two functions with type checking would be effective mitigations against DOM Clobbering attack.\n\n```\nconst getRelativeUrlFromDocument = (relativePath: string, umd = false) =>\n\tgetResolveUrl(\n\t\t`'${escapeId(relativePath)}', ${\n\t\t\tumd ? `typeof document === 'undefined' ? location.href : ` : ''\n\t\t}document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`\n\t);\n```\n\n```\nconst getUrlFromDocument = (chunkId: string, umd = false) =>\n\t`${\n\t\tumd ? `typeof document === 'undefined' ? location.href : ` : ''\n\t}(${DOCUMENT_CURRENT_SCRIPT} && ${DOCUMENT_CURRENT_SCRIPT}.tagName.toUpperCase() === 'SCRIPT' &&${DOCUMENT_CURRENT_SCRIPT}.src || new URL('${escapeId(\n\t\tchunkId\n\t)}', document.baseURI).href)`;\n```\n","reported_by":null,"title":"DOM Clobbering Gadget found in rollup bundled scripts that leads to XSS","metadata":null,"cves":["CVE-2024-47068"],"access":"public","severity":"high","module_name":"rollup","vulnerable_versions":"<3.29.5","github_advisory_id":"GHSA-gcx4-mw62-g8wm","recommendation":"Upgrade to version 3.29.5 or later","patched_versions":">=3.29.5","updated":"2024-09-23T22:11:05.000Z","cvss":{"score":6.4,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:H"},"cwe":["CWE-79"],"url":"https://github.com/advisories/GHSA-gcx4-mw62-g8wm"}}} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d2879d71e9..19f0a2f5b0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -10845,9 +10845,9 @@ rollup-plugin-terser@^7.0.0: terser "^5.0.0" rollup@^2.43.1: - version "2.79.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" - integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + version "2.79.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.2.tgz#f150e4a5db4b121a21a747d762f701e5e9f49090" + integrity sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ== optionalDependencies: fsevents "~2.3.2" diff --git a/package.json b/package.json index d84ca2a6bf..46684c55b4 100644 --- a/package.json +++ b/package.json @@ -265,7 +265,9 @@ "@babel/preset-env": "^7.11.5", "@babel/preset-typescript": "^7.18.6", "@cucumber/cucumber": "^7.0.0", + "@hapi/joi": "^17.1.1", "@playwright/test": "^1.28.0", + "@types/hapi__joi": "^17.1.14", "@types/jest": "^29.5.12", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.0.0", @@ -284,7 +286,6 @@ "jest": "26.6.0", "jest-cli": "26.4.2", "jest-junit": "^12.0.0", - "joi": "^17.9.1", "nodemon": "^2.0.4", "playwright": "^1.46.0", "puppeteer": "^13.1.1", @@ -361,7 +362,7 @@ "wait-for-expect": "^3.0.2", "winston": "^3.3.3", "ws": "^8.17.1", - "xml2json": "^0.12.0", + "xml2js": "^0.6.2", "yargs": "^17.3.1", "yayson": "^2.1.0" } diff --git a/src/lib/updateGrantsRecipients.js b/src/lib/updateGrantsRecipients.js index 254e882dd5..762bc1b976 100644 --- a/src/lib/updateGrantsRecipients.js +++ b/src/lib/updateGrantsRecipients.js @@ -1,5 +1,5 @@ import AdmZip from 'adm-zip'; -import { toJson } from 'xml2json'; +import xml2js from 'xml2js'; import axios from 'axios'; import { keyBy, mapValues } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; @@ -18,6 +18,12 @@ import { GRANT_PERSONNEL_ROLES } from '../constants'; const fs = require('mz/fs'); +const parser = new xml2js.Parser({ + explicitArray: false, + explicitCharkey: false, + trim: true, +}); + // TTAHUB-2126 TTAHUB-2334 // Update the specific attribute (e.g., state code) for each grant, // identified by its grant number, in the map below. @@ -215,8 +221,7 @@ export async function processFiles(hashSumHex) { ); const grantAgencyData = await fs.readFile('./temp/grant_agency.xml'); - const json = toJson(grantAgencyData); - const grantAgency = JSON.parse(json); + const grantAgency = await parser.parseStringPromise(grantAgencyData); // we are only interested in non-delegates const grantRecipients = grantAgency.grant_agencies.grant_agency.filter( (g) => g.grant_agency_number === '0', @@ -224,7 +229,7 @@ export async function processFiles(hashSumHex) { // process recipients aka agencies that are non-delegates const agencyData = await fs.readFile('./temp/agency.xml'); - const agency = JSON.parse(toJson(agencyData)); + const agency = await parser.parseStringPromise(agencyData); // filter out delegates by matching to the non-delegates; // filter out recipient 5 (TTAHUB-705) @@ -239,7 +244,7 @@ export async function processFiles(hashSumHex) { // process grants const grantData = await fs.readFile('./temp/grant_award.xml'); - const grant = JSON.parse(toJson(grantData)); + const grant = await parser.parseStringPromise(grantData); // temporary workaround for recipient 628 where it's name is coming in as DBA one. // This issue is pending with HSES as of 12/22/22. @@ -267,7 +272,7 @@ export async function processFiles(hashSumHex) { ); const programData = await fs.readFile('./temp/grant_program.xml'); - const programs = JSON.parse(toJson(programData)); + const programs = await parser.parseStringPromise(programData); const grantsForDb = grant.grant_awards.grant_award.map((g) => { let { @@ -373,7 +378,7 @@ export async function processFiles(hashSumHex) { // Load and Process grant replacement data. const grantReplacementsData = await fs.readFile('./temp/grant_award_replacement.xml'); - const grantReplacementsJson = JSON.parse(toJson(grantReplacementsData)); + const grantReplacementsJson = await parser.parseStringPromise(grantReplacementsData); const grantReplacements = grantReplacementsJson .grant_award_replacements.grant_award_replacement; diff --git a/src/models/hooks/activityReportGoal.js b/src/models/hooks/activityReportGoal.js index 5b22f0fe62..1552e1402f 100644 --- a/src/models/hooks/activityReportGoal.js +++ b/src/models/hooks/activityReportGoal.js @@ -1,3 +1,4 @@ +const { Op } = require('sequelize'); const { REPORT_STATUSES } = require('@ttahub/common'); const { GOAL_COLLABORATORS } = require('../../constants'); const { @@ -152,6 +153,46 @@ const destroyLinkedSimilarityGroups = async (sequelize, instance, options) => { }); }; +const updateOnARAndOnApprovedARForMergedGoals = async (sequelize, instance) => { + const changed = instance.changed(); + + // Check if both originalGoalId and goalId have been changed and originalGoalId is not null + if (Array.isArray(changed) + && changed.includes('originalGoalId') + && changed.includes('goalId') + && instance.originalGoalId !== null) { + const { goalId } = instance; + + // Check if the ActivityReport linked to this ActivityReportGoal has a + // calculatedStatus of 'approved' + const approvedActivityReports = await sequelize.models.ActivityReport.count({ + where: { + calculatedStatus: 'approved', + id: instance.activityReportId, // Use the activityReportId from the instance + }, + }); + + const onApprovedAR = approvedActivityReports > 0; + + // Update only if the current values differ + await sequelize.models.Goal.update( + { onAR: true, onApprovedAR }, + { + where: { + id: goalId, + [Op.or]: [ + // Update if onAR is not already true + { onAR: { [Op.ne]: true } }, + // Update if onApprovedAR differs + { onApprovedAR: { [Op.ne]: onApprovedAR } }, + ], + }, + individualHooks: true, // Ensure individual hooks are triggered + }, + ); + } +}; + const afterCreate = async (sequelize, instance, options) => { await processForEmbeddedResources(sequelize, instance, options); await autoPopulateLinker(sequelize, instance, options); @@ -177,6 +218,7 @@ const afterDestroy = async (sequelize, instance, options) => { const afterUpdate = async (sequelize, instance, options) => { await processForEmbeddedResources(sequelize, instance, options); await destroyLinkedSimilarityGroups(sequelize, instance, options); + await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance); }; export { @@ -185,6 +227,7 @@ export { recalculateOnAR, propagateDestroyToMetadata, destroyLinkedSimilarityGroups, + updateOnARAndOnApprovedARForMergedGoals, afterCreate, beforeDestroy, afterDestroy, diff --git a/src/models/hooks/activityReportGoal.test.js b/src/models/hooks/activityReportGoal.test.js index 882bf46506..f4db5f3778 100644 --- a/src/models/hooks/activityReportGoal.test.js +++ b/src/models/hooks/activityReportGoal.test.js @@ -1,5 +1,9 @@ +const { Op } = require('sequelize'); // Import Sequelize operators const { REPORT_STATUSES } = require('@ttahub/common'); -const { destroyLinkedSimilarityGroups } = require('./activityReportGoal'); +const { + destroyLinkedSimilarityGroups, + updateOnARAndOnApprovedARForMergedGoals, +} = require('./activityReportGoal'); describe('destroyLinkedSimilarityGroups', () => { afterEach(() => { @@ -205,3 +209,174 @@ describe('destroyLinkedSimilarityGroups', () => { expect(sequelize.models.GoalSimilarityGroup.destroy).not.toHaveBeenCalled(); }); }); + +describe('updateOnARAndOnApprovedARForMergedGoals', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const sequelize = { + models: { + Goal: { + update: jest.fn(), + }, + ActivityReport: { + count: jest.fn(), + }, + }, + }; + + it('should update onAR and onApprovedAR for merged goals when originalGoalId and goalId are changed', async () => { + const instance = { + goalId: 1, + originalGoalId: 2, + activityReportId: 1, + changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed + }; + + const options = { + transaction: 'mockTransaction', + }; + + // Mock the necessary Sequelize methods + // Simulate approved ActivityReport exists + sequelize.models.ActivityReport.count.mockResolvedValue(1); + + await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options); + + expect(sequelize.models.ActivityReport.count).toHaveBeenCalledWith({ + where: { + calculatedStatus: 'approved', + id: instance.activityReportId, + }, + }); + + expect(sequelize.models.Goal.update).toHaveBeenCalledWith( + { onAR: true, onApprovedAR: true }, + { + where: { + id: instance.goalId, + [Op.or]: [ + // Ensure onAR condition is in the where clause + { onAR: { [Op.ne]: true } }, + // Ensure onApprovedAR condition is in the where clause + { onApprovedAR: { [Op.ne]: true } }, + ], + }, + individualHooks: true, + }, + ); + }); + + it('should update onAR and onApprovedAR with false when there are no approved activity reports', async () => { + const instance = { + goalId: 1, + originalGoalId: 2, + activityReportId: 1, + changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed + }; + + const options = { + transaction: 'mockTransaction', + }; + + // Mock the necessary Sequelize methods + sequelize.models.ActivityReport.count.mockResolvedValue(0); // No approved ActivityReports + + await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options); + + expect(sequelize.models.ActivityReport.count).toHaveBeenCalledWith({ + where: { + calculatedStatus: 'approved', + id: instance.activityReportId, + }, + }); + + expect(sequelize.models.Goal.update).toHaveBeenCalledWith( + { onAR: true, onApprovedAR: false }, // onApprovedAR is false since no approved reports + { + where: { + id: instance.goalId, + [Op.or]: [ + // Ensure onAR condition is in the where clause + { onAR: { [Op.ne]: true } }, + // Ensure onApprovedAR condition is in the where clause + { onApprovedAR: { [Op.ne]: false } }, + ], + }, + individualHooks: true, + }, + ); + }); + + it('should not update if originalGoalId or goalId is not changed', async () => { + const instance = { + goalId: 1, + originalGoalId: 2, + activityReportId: 1, + changed: () => [], // Simulate no changes + }; + + const options = { + transaction: 'mockTransaction', + }; + + await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options); + + expect(sequelize.models.ActivityReport.count).not.toHaveBeenCalled(); + expect(sequelize.models.Goal.update).not.toHaveBeenCalled(); + }); + + it('should not update if originalGoalId is null', async () => { + const instance = { + goalId: 1, + originalGoalId: null, + activityReportId: 1, + changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed + }; + + const options = { + transaction: 'mockTransaction', + }; + + await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options); + + expect(sequelize.models.ActivityReport.count).not.toHaveBeenCalled(); + expect(sequelize.models.Goal.update).not.toHaveBeenCalled(); + }); + + it('should not update if onAR and onApprovedAR are already set to the correct values', async () => { + const instance = { + goalId: 1, + originalGoalId: 2, + activityReportId: 1, + changed: () => ['originalGoalId', 'goalId'], // Simulate that both columns have changed + }; + + const options = { + transaction: 'mockTransaction', + }; + + // Mock the necessary Sequelize methods + // Simulate approved ActivityReport exists + sequelize.models.ActivityReport.count.mockResolvedValue(1); + // Simulate that the update doesn't happen + sequelize.models.Goal.update.mockResolvedValue(0); + + await updateOnARAndOnApprovedARForMergedGoals(sequelize, instance, options); + + expect(sequelize.models.Goal.update).toHaveBeenCalledWith( + { onAR: true, onApprovedAR: true }, + { + where: { + id: instance.goalId, + [Op.or]: [ + { onAR: { [Op.ne]: true } }, // Check if onAR is already true + { onApprovedAR: { [Op.ne]: true } }, // Check if onApprovedAR is already true + ], + }, + individualHooks: true, + }, + ); + }); +}); diff --git a/tests/api/README.md b/tests/api/README.md index 0bde98c308..00c3a62376 100644 --- a/tests/api/README.md +++ b/tests/api/README.md @@ -10,7 +10,7 @@ There are a number of examples of writing tests in here, but the basic pattern i ```typescript import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test('get /endpoint', async ({ request }) => { diff --git a/tests/api/activityReport.spec.ts b/tests/api/activityReport.spec.ts index 8443916e2e..417f8b8a22 100644 --- a/tests/api/activityReport.spec.ts +++ b/tests/api/activityReport.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test.describe('get /activity-reports/goals', () => { diff --git a/tests/api/common.ts b/tests/api/common.ts index b04fa979c1..52ecda0eaa 100644 --- a/tests/api/common.ts +++ b/tests/api/common.ts @@ -1,5 +1,5 @@ import { APIResponse } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; export const root = `http://localhost:8080/api`; diff --git a/tests/api/externalapi.spec.ts b/tests/api/externalapi.spec.ts index 7ed4516a41..62283a3d30 100644 --- a/tests/api/externalapi.spec.ts +++ b/tests/api/externalapi.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test('get /v1/activity-reports/display/R01-AR-9999', async ({ request }) => { diff --git a/tests/api/goals.spec.ts b/tests/api/goals.spec.ts index 15df14ff11..9c3fbd56a0 100644 --- a/tests/api/goals.spec.ts +++ b/tests/api/goals.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test'; import { CLOSE_SUSPEND_REASONS } from '@ttahub/common'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; import { GOAL_STATUS, OBJECTIVE_STATUS } from '../../src/constants'; diff --git a/tests/api/recipient.spec.ts b/tests/api/recipient.spec.ts index a1f577506b..5e82d07009 100644 --- a/tests/api/recipient.spec.ts +++ b/tests/api/recipient.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; enum SortBy { diff --git a/tests/api/role.spec.ts b/tests/api/role.spec.ts index 10d0ad33c9..c562d13c97 100644 --- a/tests/api/role.spec.ts +++ b/tests/api/role.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test.describe('get /role', () => { diff --git a/tests/api/settings.spec.ts b/tests/api/settings.spec.ts index ff8cd065d2..44687e4b1f 100644 --- a/tests/api/settings.spec.ts +++ b/tests/api/settings.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test('get /settings', async ({ request }) => { diff --git a/tests/api/topics.spec.ts b/tests/api/topics.spec.ts index 784630ee87..bfbf1eb13a 100644 --- a/tests/api/topics.spec.ts +++ b/tests/api/topics.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test('get /topic', async ({ request }) => { diff --git a/tests/api/user.spec.ts b/tests/api/user.spec.ts index 6879f95ba5..df9e5e2db3 100644 --- a/tests/api/user.spec.ts +++ b/tests/api/user.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test('get /user', async ({ request }) => { diff --git a/tests/api/users.spec.ts b/tests/api/users.spec.ts index 5bece90aa7..dae9478f87 100644 --- a/tests/api/users.spec.ts +++ b/tests/api/users.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { root, validateSchema } from './common'; test.describe('get /users/collaborators', () => { diff --git a/tests/api/widgets.spec.ts b/tests/api/widgets.spec.ts index 1b659e6415..493488166b 100644 --- a/tests/api/widgets.spec.ts +++ b/tests/api/widgets.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import Joi from 'joi'; +import Joi from '@hapi/joi'; import { reseed } from '../utils/common'; import { root, validateSchema } from './common'; diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index 49bae093ea..3b73ad385d 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1,4 +1 @@ -{"type":"auditAdvisory","data":{"resolution":{"id":1096366,"path":"email-templates>preview-email>mailparser>nodemailer","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"6.7.3","paths":["email-templates>preview-email>mailparser>nodemailer"]}],"metadata":null,"vulnerable_versions":"<=6.9.8","module_name":"nodemailer","severity":"moderate","github_advisory_id":"GHSA-9h6g-pr28-7cqp","cves":[],"access":"public","patched_versions":">=6.9.9","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2024-02-01T17:58:50.000Z","recommendation":"Upgrade to version 6.9.9 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1096366,"references":"- https://github.com/nodemailer/nodemailer/security/advisories/GHSA-9h6g-pr28-7cqp\n- https://gist.github.com/francoatmega/890dd5053375333e40c6fdbcc8c58df6\n- https://gist.github.com/francoatmega/9aab042b0b24968d7b7039818e8b2698\n- https://github.com/nodemailer/nodemailer/commit/dd8f5e8a4ddc99992e31df76bcff9c590035cd4a\n- https://github.com/advisories/GHSA-9h6g-pr28-7cqp","created":"2024-01-31T22:42:54.000Z","reported_by":null,"title":"nodemailer ReDoS when trying to send a specially crafted email","npm_advisory_id":null,"overview":"### Summary\nA ReDoS vulnerability occurs when nodemailer tries to parse img files with the parameter `attachDataUrls` set, causing the stuck of event loop. \nAnother flaw was found when nodemailer tries to parse an attachments with a embedded file, causing the stuck of event loop. \n\n### Details\n\nRegex: /^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/\n\nPath: compile -> getAttachments -> _processDataUrl\n\nRegex: /(]* src\\s*=[\\s\"']*)(data:([^;]+);[^\"'>\\s]+)/\n\nPath: _convertDataImages\n\n### PoC\n\nhttps://gist.github.com/francoatmega/890dd5053375333e40c6fdbcc8c58df6\nhttps://gist.github.com/francoatmega/9aab042b0b24968d7b7039818e8b2698\n\n### Impact\n\nReDoS causes the event loop to stuck a specially crafted evil email can cause this problem.\n","url":"https://github.com/advisories/GHSA-9h6g-pr28-7cqp"}}} -{"type":"auditAdvisory","data":{"resolution":{"id":1096410,"path":"xml2json>hoek","dev":false,"bundled":false,"optional":false},"advisory":{"findings":[{"version":"4.2.1","paths":["xml2json>hoek"]},{"version":"5.0.4","paths":["xml2json>joi>hoek"]},{"version":"6.1.3","paths":["xml2json>joi>topo>hoek"]}],"metadata":null,"vulnerable_versions":"<=6.1.3","module_name":"hoek","severity":"high","github_advisory_id":"GHSA-c429-5p7v-vgjp","cves":["CVE-2020-36604"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":8.1,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2024-02-07T18:59:37.000Z","recommendation":"None","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1096410,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-36604\n- https://github.com/hapijs/hoek/issues/352\n- https://github.com/hapijs/hoek/commit/4d0804bc6135ad72afdc5e1ec002b935b2f5216a\n- https://github.com/hapijs/hoek/commit/948baf98634a5c206875b67d11368f133034fa90\n- https://github.com/advisories/GHSA-c429-5p7v-vgjp","created":"2022-09-25T00:00:27.000Z","reported_by":null,"title":"hoek subject to prototype pollution via the clone function.","npm_advisory_id":null,"overview":"hoek versions prior to 8.5.1, and 9.x prior to 9.0.3 are vulnerable to prototype pollution in the clone function. If an object with the __proto__ key is passed to clone() the key is converted to a prototype. This issue has been patched in version 9.0.3, and backported to 8.5.1. ","url":"https://github.com/advisories/GHSA-c429-5p7v-vgjp"}}} -{"type":"auditAdvisory","data":{"resolution":{"id":1096410,"path":"xml2json>joi>hoek","dev":false,"bundled":false,"optional":false},"advisory":{"findings":[{"version":"4.2.1","paths":["xml2json>hoek"]},{"version":"5.0.4","paths":["xml2json>joi>hoek"]},{"version":"6.1.3","paths":["xml2json>joi>topo>hoek"]}],"metadata":null,"vulnerable_versions":"<=6.1.3","module_name":"hoek","severity":"high","github_advisory_id":"GHSA-c429-5p7v-vgjp","cves":["CVE-2020-36604"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":8.1,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2024-02-07T18:59:37.000Z","recommendation":"None","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1096410,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-36604\n- https://github.com/hapijs/hoek/issues/352\n- https://github.com/hapijs/hoek/commit/4d0804bc6135ad72afdc5e1ec002b935b2f5216a\n- https://github.com/hapijs/hoek/commit/948baf98634a5c206875b67d11368f133034fa90\n- https://github.com/advisories/GHSA-c429-5p7v-vgjp","created":"2022-09-25T00:00:27.000Z","reported_by":null,"title":"hoek subject to prototype pollution via the clone function.","npm_advisory_id":null,"overview":"hoek versions prior to 8.5.1, and 9.x prior to 9.0.3 are vulnerable to prototype pollution in the clone function. If an object with the __proto__ key is passed to clone() the key is converted to a prototype. This issue has been patched in version 9.0.3, and backported to 8.5.1. ","url":"https://github.com/advisories/GHSA-c429-5p7v-vgjp"}}} -{"type":"auditAdvisory","data":{"resolution":{"id":1096410,"path":"xml2json>joi>topo>hoek","dev":false,"bundled":false,"optional":false},"advisory":{"findings":[{"version":"4.2.1","paths":["xml2json>hoek"]},{"version":"5.0.4","paths":["xml2json>joi>hoek"]},{"version":"6.1.3","paths":["xml2json>joi>topo>hoek"]}],"metadata":null,"vulnerable_versions":"<=6.1.3","module_name":"hoek","severity":"high","github_advisory_id":"GHSA-c429-5p7v-vgjp","cves":["CVE-2020-36604"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":8.1,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2024-02-07T18:59:37.000Z","recommendation":"None","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1096410,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-36604\n- https://github.com/hapijs/hoek/issues/352\n- https://github.com/hapijs/hoek/commit/4d0804bc6135ad72afdc5e1ec002b935b2f5216a\n- https://github.com/hapijs/hoek/commit/948baf98634a5c206875b67d11368f133034fa90\n- https://github.com/advisories/GHSA-c429-5p7v-vgjp","created":"2022-09-25T00:00:27.000Z","reported_by":null,"title":"hoek subject to prototype pollution via the clone function.","npm_advisory_id":null,"overview":"hoek versions prior to 8.5.1, and 9.x prior to 9.0.3 are vulnerable to prototype pollution in the clone function. If an object with the __proto__ key is passed to clone() the key is converted to a prototype. This issue has been patched in version 9.0.3, and backported to 8.5.1. ","url":"https://github.com/advisories/GHSA-c429-5p7v-vgjp"}}} +{"type":"auditAdvisory","data":{"resolution":{"id":1096366,"path":"email-templates>preview-email>mailparser>nodemailer","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"6.7.3","paths":["email-templates>preview-email>mailparser>nodemailer"]}],"found_by":null,"deleted":null,"references":"- https://github.com/nodemailer/nodemailer/security/advisories/GHSA-9h6g-pr28-7cqp\n- https://gist.github.com/francoatmega/890dd5053375333e40c6fdbcc8c58df6\n- https://gist.github.com/francoatmega/9aab042b0b24968d7b7039818e8b2698\n- https://github.com/nodemailer/nodemailer/commit/dd8f5e8a4ddc99992e31df76bcff9c590035cd4a\n- https://github.com/advisories/GHSA-9h6g-pr28-7cqp","created":"2024-01-31T22:42:54.000Z","id":1096366,"npm_advisory_id":null,"overview":"### Summary\nA ReDoS vulnerability occurs when nodemailer tries to parse img files with the parameter `attachDataUrls` set, causing the stuck of event loop. \nAnother flaw was found when nodemailer tries to parse an attachments with a embedded file, causing the stuck of event loop. \n\n### Details\n\nRegex: /^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/\n\nPath: compile -> getAttachments -> _processDataUrl\n\nRegex: /(]* src\\s*=[\\s\"']*)(data:([^;]+);[^\"'>\\s]+)/\n\nPath: _convertDataImages\n\n### PoC\n\nhttps://gist.github.com/francoatmega/890dd5053375333e40c6fdbcc8c58df6\nhttps://gist.github.com/francoatmega/9aab042b0b24968d7b7039818e8b2698\n\n### Impact\n\nReDoS causes the event loop to stuck a specially crafted evil email can cause this problem.\n","reported_by":null,"title":"nodemailer ReDoS when trying to send a specially crafted email","metadata":null,"cves":[],"access":"public","severity":"moderate","module_name":"nodemailer","vulnerable_versions":"<=6.9.8","github_advisory_id":"GHSA-9h6g-pr28-7cqp","recommendation":"Upgrade to version 6.9.9 or later","patched_versions":">=6.9.9","updated":"2024-02-01T17:58:50.000Z","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"cwe":["CWE-1333"],"url":"https://github.com/advisories/GHSA-9h6g-pr28-7cqp"}}} diff --git a/yarn.lock b/yarn.lock index 1f24f6d7b8..da90f5bb86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2001,6 +2001,13 @@ protobufjs "^7.2.4" yargs "^17.7.2" +"@hapi/address@^4.0.1": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d" + integrity sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/boom@^9.1.4": version "9.1.4" resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" @@ -2008,11 +2015,32 @@ dependencies: "@hapi/hoek" "9.x.x" +"@hapi/formula@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" + integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== + "@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== +"@hapi/joi@^17.1.1": + version "17.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.1.1.tgz#9cc8d7e2c2213d1e46708c6260184b447c661350" + integrity sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg== + dependencies: + "@hapi/address" "^4.0.1" + "@hapi/formula" "^2.0.0" + "@hapi/hoek" "^9.0.0" + "@hapi/pinpoint" "^2.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/pinpoint@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.1.tgz#32077e715655fc00ab8df74b6b416114287d6513" + integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q== + "@hapi/topo@^5.0.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" @@ -2661,23 +2689,6 @@ resolved "https://registry.yarnpkg.com/@servie/events/-/events-1.0.0.tgz#8258684b52d418ab7b86533e861186638ecc5dc1" integrity sha512-sBSO19KzdrJCM3gdx6eIxV8M9Gxfgg6iDQmH5TIAGaUu+X9VDdsINXJOnoiZ1Kx3TrHdH4bt5UVglkjsEGBcvw== -"@sideway/address@^4.1.3": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" - integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -3383,6 +3394,11 @@ dependencies: "@types/node" "*" +"@types/hapi__joi@^17.1.14": + version "17.1.14" + resolved "https://registry.yarnpkg.com/@types/hapi__joi/-/hapi__joi-17.1.14.tgz#937843b8a5d49fbfa8a87cc3527f6575e2140ca8" + integrity sha512-elV1VhwXUfA1sw59ij75HWyCH+3cA7xLbaOY9GQ+iQo/S+jSSf22LNZAmsXMdfV8DZwquCZaCT+F43Xf6/txrQ== + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" @@ -6782,7 +6798,7 @@ fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fast-xml-parser@4.2.5, fast-xml-parser@^4.4.1: +fast-xml-parser@4.2.5, fast-xml-parser@^4.4.1, fast-xml-parser@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz#2882b7d01a6825dfdf909638f2de0256351def37" integrity sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg== @@ -7380,9 +7396,9 @@ hoek@6.x.x: integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== hoek@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" - integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== + version "4.3.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.3.1.tgz#1d2ea831857e4ecdce7fd5ffe07acdf8eb368cca" + integrity sha512-v7E+yIjcHECn973i0xHm4kJkEpv3C8sbYS4344WXbzYqRyiDD7rjnnKo4hsJkejQBAFdRMUGNHySeSPKSH9Rqw== hoist-non-react-statics@^3.0.0: version "3.3.2" @@ -8645,17 +8661,6 @@ joi@^13.1.2: isemail "3.x.x" topo "3.x.x" -joi@^17.9.1: - version "17.9.1" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.1.tgz#74899b9fa3646904afa984a11df648eca66c9018" - integrity sha512-FariIi9j6QODKATGBrEX7HZcja8Bsh3rfdGYy/Sb65sGlZWK/QWesU1ghk7aJWDj95knjXlQfSmzFSPPkLVsfw== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.3" - "@sideway/formula" "^3.0.1" - "@sideway/pinpoint" "^2.0.0" - js-base64@^2.3.2: version "2.6.4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" @@ -9569,16 +9574,16 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.13.2: - version "2.16.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" - integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== - nan@^2.17.0, nan@^2.18.0: version "2.18.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +nan@^2.19.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + nanoid@^3.2.0, nanoid@^3.3.6: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -9686,12 +9691,12 @@ node-environment-flags@^1.0.5: semver "^5.7.0" node-expat@^2.3.18: - version "2.4.0" - resolved "https://registry.yarnpkg.com/node-expat/-/node-expat-2.4.0.tgz#0d51626808b5912cf990b24b41244cfea14ee96b" - integrity sha512-X8Y/Zk/izfNgfayeOeUGqze7KlaOwVJ9SDTjHUMKd0hu0aFTRpLlLCBwmx79cTPiQWD24I1YOafF+U+rTvEMfQ== + version "2.4.1" + resolved "https://registry.yarnpkg.com/node-expat/-/node-expat-2.4.1.tgz#1e0848084d46457195967766c29be0cdbb6e2698" + integrity sha512-uWgvQLgo883NKIL+66oJsK9ysKK3ej0YjVCPBZzO/7wMAuH68/Yb7+JwPWNaVq0yPaxrb48AoEXfYEc8gsmFbg== dependencies: bindings "^1.5.0" - nan "^2.13.2" + nan "^2.19.0" node-fetch-h2@^2.3.0: version "2.3.0" @@ -10826,20 +10831,20 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== -punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@2.x.x, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== punycode@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== -punycode@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== pupa@^2.1.1: version "2.1.1" @@ -11529,6 +11534,11 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +sax@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + sax@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" @@ -13416,6 +13426,13 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -13434,6 +13451,14 @@ xml2js@0.4.19, xml2js@^0.5.0: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml2js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + xml2json@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/xml2json/-/xml2json-0.12.0.tgz#b2ae450b267033b76d896f86e022fa7bff678572"