mirror of
https://github.com/cypress-io/cypress.git
synced 2025-12-31 11:39:48 -06:00
* dependency: update Electron to 34 * setup workflows to run against binary branch and on all tests * changelog entry * node version did bump minorly * Update base-internal image to match new node version * fix typo * changelog updates * bumping to newest version just released today - hopefully solves glibc error * fix cy in cy * remove extra register_ts_node require * updated lockfile * upgrade better-sqlite3 * changelog * update electron in top level package.json * ts issue, update to use binary workflow for e35, update ancillary deps * update gh issue templates * bump missed image names and engines field * node 22 * snapgen? * ts issue, log errors even if err.stderr/stdout is null * more logging * defer http-proxy common.js due to regexp issue in v8 13.4.* - 13.8.91 * update images for node 22.15.1, use bullseye instead of buster for bettersqlite * use bullseye image for glibc2.31 build of bettersqlite * use electron-36 publish binary branch * node-abi update, set http-proxy deferred in darwin * update .node-version * attempt to patch http-proxy to immediately defer http-proxy/lib/http-proxy/common.js * empty commit [run ci] * better patch? * changelog * changelog * Updates v8 snapshots to fix windows build (#31918) * use node 22 in the v8 snapshot update workflow * index on windows-v8-snapshots:a013464197use node 22 in the v8 snapshot update workflow * index on windows-v8-snapshots:a013464197use node 22 in the v8 snapshot update workflow * index on windows-v8-snapshots:a013464197use node 22 in the v8 snapshot update workflow * run workflows on windows/mac --------- Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> * update protocol system test snapshots (#31925) * use snapshot to verify the error message on invalid json (#31926) * chore: account for all node: internal stacks when trying to calculate the code frame. Accounts additionally for node:diagnostics_channel (#31935) * Fixes electron 36 integrity checks (#31956) * update the fs.readFileSync integrity check expectation * maybe this fn is missing from the expected stack? * more debug, change the stack up a little * actual fn name is traceSync * logging * logging * remove logging from integrity check * maybe circle api changed? * correct params * inspect stack frames for differences * have to manually serialize the stack frames * change expectation * update expected global keys * additional allow list * update key allow list * increase zipfile size limit on non-windows builds * revert logging changes * Update scripts/binary/binary-integrity-check-source.js * increase timeout to 120s for darwin fsevents/native module test (#31975) * print out stdout for darwin test * try and fix test * update readme re: browsers-internal images, ensure module_api_spec binary test uses correct electron version * Update .circleci/workflows.yml Co-authored-by: Bill Glesias <bglesias@gmail.com> * Update .circleci/workflows.yml Co-authored-by: Bill Glesias <bglesias@gmail.com> * trigger 15.0.0 binary pipeline rather than electron-36 specific one * Update cli/CHANGELOG.md Co-authored-by: Bill Glesias <bglesias@gmail.com> * Update cli/CHANGELOG.md --------- Co-authored-by: Jennifer Shehane <jennifer@cypress.io> Co-authored-by: Ryan Manuel <ryanm@cypress.io> Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com> Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Bill Glesias <bglesias@gmail.com>
219 lines
8.0 KiB
JavaScript
219 lines
8.0 KiB
JavaScript
const OrigError = Error
|
|
const originalCaptureStackTrace = Error.captureStackTrace
|
|
const originalToString = Function.prototype.toString
|
|
const originalCallFn = Function.call
|
|
const originalFilter = Array.prototype.filter
|
|
const originalStartsWith = String.prototype.startsWith
|
|
|
|
const integrityErrorMessage = `
|
|
We detected an issue with the integrity of the Cypress binary. It may have been modified and cannot run. We recommend re-installing the Cypress binary with:
|
|
|
|
\`cypress cache clear && cypress install\`
|
|
`
|
|
|
|
const stackIntegrityCheck = function stackIntegrityCheck (options) {
|
|
const originalStackTraceLimit = OrigError.stackTraceLimit
|
|
const originalPrepareStackTrace = OrigError.prepareStackTrace
|
|
|
|
OrigError.stackTraceLimit = Infinity
|
|
|
|
OrigError.prepareStackTrace = function (_, stack) {
|
|
return stack
|
|
}
|
|
|
|
const tempError = new OrigError
|
|
|
|
originalCaptureStackTrace(tempError, arguments.callee)
|
|
const stack = originalFilter.call(tempError.stack, (frame) => !originalStartsWith.call(frame.getFileName(), 'node:internal') && !originalStartsWith.call(frame.getFileName(), 'node:electron'))
|
|
|
|
OrigError.prepareStackTrace = originalPrepareStackTrace
|
|
OrigError.stackTraceLimit = originalStackTraceLimit
|
|
|
|
if (stack.length !== options.stackToMatch.length) {
|
|
console.error(`Integrity check failed with expected stack length ${options.stackToMatch.length} but got ${stack.length}`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
|
|
for (let index = 0; index < options.stackToMatch.length; index++) {
|
|
const { functionName: expectedFunctionName, fileName: expectedFileName, line: expectedLineNumber, column: expectedColumnNumber } = options.stackToMatch[index]
|
|
const actualFunctionName = stack[index].getFunctionName()
|
|
const actualFileName = stack[index].getFileName()
|
|
const actualColumnNumber = stack[index].getColumnNumber()
|
|
const actualLineNumber = stack[index].getLineNumber()
|
|
|
|
if (expectedFunctionName && actualFunctionName !== expectedFunctionName) {
|
|
console.error(`Integrity check failed with expected function name ${expectedFunctionName} but got ${actualFunctionName}`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
|
|
if (expectedFileName && actualFileName !== expectedFileName) {
|
|
console.error(`Integrity check failed with expected file name ${expectedFileName} but got ${actualFileName}`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
|
|
if (expectedLineNumber && actualLineNumber !== expectedLineNumber) {
|
|
console.error(`Integrity check failed with expected line number ${expectedLineNumber} but got ${actualLineNumber}`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
|
|
if (expectedColumnNumber && actualColumnNumber !== expectedColumnNumber) {
|
|
console.error(`Integrity check failed with expected column number ${expectedColumnNumber} but got ${actualColumnNumber}`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateStartsWith () {
|
|
if (originalStartsWith.call !== originalCallFn) {
|
|
console.error(`Integrity check failed for startsWith.call`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
function validateFilter () {
|
|
if (originalFilter.call !== originalCallFn) {
|
|
console.error(`Integrity check failed for filter.call`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
function validateToString () {
|
|
if (originalToString.call !== originalCallFn) {
|
|
console.error(`Integrity check failed for toString.call`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
function validateElectron (electron) {
|
|
// Hard coded function as this is electron code and there's not an easy way to get the function string at package time. If this fails on an updated version of electron, we'll need to update this.
|
|
if (originalToString.call(electron.app.getAppPath) !== 'function getAppPath() { [native code] }') {
|
|
console.error(`Integrity check failed for toString.call(electron.app.getAppPath)`, originalToString.call(electron.app.getAppPath))
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
function validateFs (fs) {
|
|
// Hard coded function as this is electron code and there's not an easy way to get the function string at package time. If this fails on an updated version of electron, we'll need to update this.
|
|
if (originalToString.call(fs.readFileSync) !== `function(t,e){const r=splitPath(t);return r.isAsar?readFileFromArchiveSync(r,e):R.apply(this,arguments)}`) {
|
|
console.error(`Integrity check failed for toString.call(fs.readFileSync)`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
function validateCrypto (crypto) {
|
|
if (originalToString.call(crypto.createHmac) !== `CRYPTO_CREATE_HMAC_TO_STRING`) {
|
|
console.error(`Integrity check failed for toString.call(crypto.createHmac)`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
|
|
if (originalToString.call(crypto.Hmac.prototype.update) !== `CRYPTO_HMAC_UPDATE_TO_STRING`) {
|
|
console.error(`Integrity check failed for toString.call(crypto.Hmac.prototype.update)`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
|
|
if (originalToString.call(crypto.Hmac.prototype.digest) !== `CRYPTO_HMAC_DIGEST_TO_STRING`) {
|
|
console.error(`Integrity check failed for toString.call(crypto.Hmac.prototype.digest)`)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
function validateFile ({ filePath, crypto, fs, expectedHash, errorMessage }) {
|
|
const hash = crypto.createHmac('md5', 'HMAC_SECRET').update(fs.readFileSync(filePath, 'utf8')).digest('hex')
|
|
|
|
if (hash !== expectedHash) {
|
|
console.error(errorMessage)
|
|
throw new Error(integrityErrorMessage)
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
function integrityCheck (options) {
|
|
const require = options.require
|
|
const electron = require('electron')
|
|
const fs = require('fs')
|
|
const crypto = require('crypto')
|
|
|
|
// 1. Validate that the native functions we are using haven't been tampered with
|
|
validateStartsWith()
|
|
validateFilter()
|
|
validateToString()
|
|
validateElectron(electron)
|
|
validateFs(fs)
|
|
validateCrypto(crypto)
|
|
|
|
const appPath = electron.app.getAppPath()
|
|
|
|
// 2. Validate that the stack trace is what we expect
|
|
stackIntegrityCheck({ stackToMatch:
|
|
[
|
|
{
|
|
functionName: 'integrityCheck',
|
|
fileName: '<embedded>',
|
|
},
|
|
{
|
|
fileName: '<embedded>',
|
|
},
|
|
{
|
|
functionName: 'snapshotRequire',
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'packages', 'server', 'index.jsc'].join(PATH_SEP),
|
|
},
|
|
{
|
|
functionName: 'runWithSnapshot',
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'packages', 'server', 'index.jsc'].join(PATH_SEP),
|
|
},
|
|
{
|
|
functionName: 'hookRequire',
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'packages', 'server', 'index.jsc'].join(PATH_SEP),
|
|
},
|
|
{
|
|
functionName: 'startCypress',
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'packages', 'server', 'index.jsc'].join(PATH_SEP),
|
|
},
|
|
{
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'packages', 'server', 'index.jsc'].join(PATH_SEP),
|
|
},
|
|
{
|
|
functionName: 'v',
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'index.js'].join(PATH_SEP),
|
|
line: 1,
|
|
column: 2573,
|
|
},
|
|
{
|
|
// eslint-disable-next-line no-undef
|
|
fileName: [appPath, 'index.js'].join(PATH_SEP),
|
|
line: 1,
|
|
column: 2764,
|
|
},
|
|
{
|
|
functionName: 'traceSync',
|
|
fileName: 'node:diagnostics_channel',
|
|
},
|
|
],
|
|
})
|
|
|
|
// 3. Validate the three pieces of the entry point: the main index file, the bundled jsc file, and the bytenode node module
|
|
validateFile({
|
|
// eslint-disable-next-line no-undef
|
|
filePath: [appPath, 'index.js'].join(PATH_SEP),
|
|
crypto,
|
|
fs,
|
|
expectedHash: 'MAIN_INDEX_HASH',
|
|
errorMessage: 'Integrity check failed for main index.js file',
|
|
})
|
|
|
|
validateFile({
|
|
// eslint-disable-next-line no-undef
|
|
filePath: [appPath, 'packages', 'server', 'index.jsc'].join(PATH_SEP),
|
|
crypto,
|
|
fs,
|
|
expectedHash: 'INDEX_JSC_HASH',
|
|
errorMessage: 'Integrity check failed for main server index.jsc file',
|
|
})
|
|
}
|