diff --git a/packages/proxy/lib/http/util/regex-rewriter.ts b/packages/proxy/lib/http/util/regex-rewriter.ts index a2d643d5a24c..f1740266ff7a 100644 --- a/packages/proxy/lib/http/util/regex-rewriter.ts +++ b/packages/proxy/lib/http/util/regex-rewriter.ts @@ -16,23 +16,21 @@ const topOrParentLocationOrFramesRe = /([^\da-zA-Z\(\)])?(\btop\b|\bparent\b)([. const jiraTopWindowGetterRe = /(!function\s*\((\w{1})\)\s*{\s*return\s*\w{1}\s*(?:={2,})\s*\w{1}\.parent)(\s*}\(\w{1}\))/g const jiraTopWindowGetterUnMinifiedRe = /(function\s*\w{1,}\s*\((\w{1})\)\s*{\s*return\s*\w{1}\s*(?:={2,})\s*\w{1}\.parent)(\s*;\s*})/g +/** + * Matches the word integrity if being set on an object, such as foo.integrity. This MUST be preceded by a valid hash to match. This is replaced with + * foo['cypress-stripped-integrity'] + */ +const javaScriptIntegrityReplacementRe = new RegExp(`[\\.](${STRIPPED_INTEGRITY_TAG}|integrity)((\\s?=\\s?)(?:"|')sha(?:256|384|512)-.*?(?:"|'))`, 'g') +/** + * Does a negative lookback to see a variable is being declared, such as var let or const (the nst is back end of const since lookbacks need a fixed width). This can then + * be preceded by any optional character that isn't a period, space, single or double quote. This MUST be preceded by a valid hash to match. A space preceding the integrity tag can still be matched, + * but the match only starts at the word integrity, and not the character preceding it. In these cases, we always replace the word integrity with cypress-stripped-integrity. + * The match for cypress-stripped-integrity is if we are replacing in the stripStream, and the replaced text is rematched no essentially complete a no op + */ +const htmlIntegrityReplacementRe = new RegExp(`(?:(? { - if (!isHtml) { - // only replace integrity if a trailing period (.) or string exists inside JS/Other resources - return new RegExp(`(${STRIPPED_INTEGRITY_TAG}|[\\.|"|']integrity)((\\s?=\\s?|["|'], )(?:"|')sha(?:256|384|512)-.*?(?:"|'))`, 'g') - } - - return new RegExp(`(${STRIPPED_INTEGRITY_TAG}|integrity)((=|["|'], )(?:"|')sha(?:256|384|512)-.*?(?:"|'))`, 'g') -} - -const returnReplacedIntegrityExpression = (isHtml = true) => { - return isHtml ? `${STRIPPED_INTEGRITY_TAG}$2` : `['${STRIPPED_INTEGRITY_TAG}']$2` -} - -export function strip (html: string, { modifyObstructiveThirdPartyCode, isHtml = true }: Partial = { +export function strip (html: string, { modifyObstructiveThirdPartyCode }: Partial = { modifyObstructiveThirdPartyCode: false, - isHtml: true, }) { let rewrittenHTML = html .replace(modifyObstructiveThirdPartyCode ? topOrParentExpandedEqualityBeforeRe : topOrParentEqualityBeforeRe, '$1self') @@ -42,15 +40,15 @@ export function strip (html: string, { modifyObstructiveThirdPartyCode, isHtml = .replace(jiraTopWindowGetterUnMinifiedRe, '$1 || $2.parent.__Cypress__$3') if (modifyObstructiveThirdPartyCode) { - rewrittenHTML = rewrittenHTML.replace(buildIntegrityReplacementRe(isHtml), returnReplacedIntegrityExpression(isHtml)) + rewrittenHTML = rewrittenHTML.replace(javaScriptIntegrityReplacementRe, `['${STRIPPED_INTEGRITY_TAG}']$2`) + rewrittenHTML = rewrittenHTML.replace(htmlIntegrityReplacementRe, `${STRIPPED_INTEGRITY_TAG}$3`) } return rewrittenHTML } -export function stripStream ({ modifyObstructiveThirdPartyCode, isHtml = true }: Partial = { +export function stripStream ({ modifyObstructiveThirdPartyCode }: Partial = { modifyObstructiveThirdPartyCode: false, - isHtml: true, }) { return pumpify( utf8Stream(), @@ -62,7 +60,8 @@ export function stripStream ({ modifyObstructiveThirdPartyCode, isHtml = true }: jiraTopWindowGetterRe, jiraTopWindowGetterUnMinifiedRe, ...(modifyObstructiveThirdPartyCode ? [ - buildIntegrityReplacementRe(isHtml), + javaScriptIntegrityReplacementRe, + htmlIntegrityReplacementRe, ] : []), ], [ @@ -72,7 +71,8 @@ export function stripStream ({ modifyObstructiveThirdPartyCode, isHtml = true }: '$1 || $2.parent.__Cypress__$3', '$1 || $2.parent.__Cypress__$3', ...(modifyObstructiveThirdPartyCode ? [ - returnReplacedIntegrityExpression(isHtml), + `['${STRIPPED_INTEGRITY_TAG}']$2`, + `${STRIPPED_INTEGRITY_TAG}$3`, ] : []), ], ), diff --git a/packages/proxy/test/unit/http/util/regex-rewriter.spec.ts b/packages/proxy/test/unit/http/util/regex-rewriter.spec.ts index faf08c7b9127..4a79f7379293 100644 --- a/packages/proxy/test/unit/http/util/regex-rewriter.spec.ts +++ b/packages/proxy/test/unit/http/util/regex-rewriter.spec.ts @@ -265,7 +265,7 @@ const originalWithModifyObstructiveThirdPartyCode = `\ dynamicIntegrityScript.src = 'integrity.js' dynamicIntegrityScript.setAttribute('crossorigin', "anonymous") dynamicIntegrityScript.setAttribute('data-script-type', 'dynamic') - dynamicIntegrityScript.setAttribute('integrity', "sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C" + dynamicIntegrityScript.setAttribute('integrity', "sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C") document.querySelector('head').appendChild(dynamicIntegrityScript) @@ -279,6 +279,17 @@ const originalWithModifyObstructiveThirdPartyCode = `\ dynamicIntegrityScript.setAttribute('integrity', "sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C") document.querySelector('head').appendChild(dynamicIntegrityScript) + \ ` @@ -380,7 +391,7 @@ const expectedWithModifyObstructiveThirdPartyCode = `\ dynamicIntegrityScript.src = 'integrity.js' dynamicIntegrityScript.setAttribute('crossorigin', "anonymous") dynamicIntegrityScript.setAttribute('data-script-type', 'dynamic') - dynamicIntegrityScript.setAttribute('cypress-stripped-integrity', "sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C" + dynamicIntegrityScript.setAttribute('cypress-stripped-integrity', "sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C") document.querySelector('head').appendChild(dynamicIntegrityScript) @@ -394,49 +405,33 @@ const expectedWithModifyObstructiveThirdPartyCode = `\ dynamicIntegrityScript.setAttribute('cypress-stripped-integrity', "sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C") document.querySelector('head').appendChild(dynamicIntegrityScript) + \ ` -const originalScriptWithModifyObstructiveThirdPartyCode = `\ -(function(){var d=document,po=d.createElement('script');po.type='text/javascript';po.async=true;po.src='https://www.foobar.com/foobar.js';po.crossOrigin='anonymous';po.integrity='sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C';var e=d.querySelector('script[nonce]'),n=e&&(e['nonce']||e.getAttribute('nonce'));if(n){po.setAttribute('nonce',n);}var s=d.getElementsByTagName('script')[0];s.parentNode.insertBefore(po, s);})(); -var integrity = 'sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C' -foo.integrity = 'foo-bar' -foo.integrity = 'sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C' -var integrity='sha256-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C' -foo.integrity='foo-bar' -foo.integrity='sha256-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C'\ -` - -const expectedScriptWithModifyObstructiveThirdPartyCode = `\ -(function(){var d=document,po=d.createElement('script');po.type='text/javascript';po.async=true;po.src='https://www.foobar.com/foobar.js';po.crossOrigin='anonymous';po['cypress-stripped-integrity']='sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C';var e=d.querySelector('script[nonce]'),n=e&&(e['nonce']||e.getAttribute('nonce'));if(n){po.setAttribute('nonce',n);}var s=d.getElementsByTagName('script')[0];s.parentNode.insertBefore(po, s);})(); -var integrity = 'sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C' -foo.integrity = 'foo-bar' -foo['cypress-stripped-integrity'] = 'sha384-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C' -var integrity='sha256-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C' -foo.integrity='foo-bar' -foo['cypress-stripped-integrity']='sha256-XiV6bRRw9OEpsWSumtD1J7rElgTrNQro4MY/O4IYjhH+YGCf1dHaNGZ3A2kzYi/C'\ -` - describe('http/util/regex-rewriter', () => { context('.strip', () => { it('replaces obstructive code', () => { expect(regexRewriter.strip(original)).to.eq(expected) }) - it('replaces additional obstructive code with the "modifyObstructiveThirdPartyCode" set (html)', () => { + it('replaces additional obstructive code with the "modifyObstructiveThirdPartyCode" set', () => { expect(regexRewriter.strip(originalWithModifyObstructiveThirdPartyCode, { modifyObstructiveThirdPartyCode: true, })).to.eq(expectedWithModifyObstructiveThirdPartyCode) }) - it('replaces additional obstructive code with the "modifyObstructiveThirdPartyCode" set (javascript)', () => { - expect(regexRewriter.strip(originalScriptWithModifyObstructiveThirdPartyCode, { - modifyObstructiveThirdPartyCode: true, - isHtml: false, - })).to.eq(expectedScriptWithModifyObstructiveThirdPartyCode) - }) - it('replaces jira window getter', () => { const jira = `\ for (; !function (n) { @@ -636,12 +631,11 @@ while (!isTopMostWindow(parentOf) && satisfiesSameOrigin(parentOf.parent)) { replacer.end() }) - it('replaces additional obstructive code with the "modifyObstructiveThirdPartyCode" set (html)', (done) => { + it('replaces additional obstructive code with the "modifyObstructiveThirdPartyCode" set', (done) => { const haystacks = originalWithModifyObstructiveThirdPartyCode.split('\n') const replacer = regexRewriter.stripStream({ modifyObstructiveThirdPartyCode: true, - isHtml: true, }) replacer.pipe(concatStream({ encoding: 'string' }, (str) => { @@ -662,32 +656,5 @@ while (!isTopMostWindow(parentOf) && satisfiesSameOrigin(parentOf.parent)) { replacer.end() }) - - it('replaces additional obstructive code with the "modifyObstructiveThirdPartyCode" set (js)', (done) => { - const haystacks = originalScriptWithModifyObstructiveThirdPartyCode.split('\n') - - const replacer = regexRewriter.stripStream({ - modifyObstructiveThirdPartyCode: true, - isHtml: false, - }) - - replacer.pipe(concatStream({ encoding: 'string' }, (str) => { - const string = str.toString().trim() - - try { - expect(string).to.eq(expectedScriptWithModifyObstructiveThirdPartyCode) - - done() - } catch (err) { - done(err) - } - })) - - haystacks.forEach((haystack) => { - replacer.write(`${haystack}\n`) - }) - - replacer.end() - }) }) })