Repository: cypress-io/cypress
File: packages/server/lib/cloud/studio/StudioLifecycleManager.ts (TypeScript)

Code before:

      let hashLoadingPromise = StudioLifecycleManager.hashLoadingMap.get(studioHash)

      if (!hashLoadingPromise) {
        hashLoadingPromise = ensureStudioBundle({
          studioUrl: studioSession.studioUrl,
          studioPath,
          projectId,
        })

        StudioLifecycleManager.hashLoadingMap.set(studioHash, hashLoadingPromise)
      }

      manifest = await hashLoadingPromise
    } else {
      studioPath = process.env.CYPRESS_LOCAL_STUDIO_PATH
      studioHash = 'local'
      manifest = {}
    }

    telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.ENSURE_STUDIO_BUNDLE_END)

    const serverFilePath = path.join(studioPath, 'server', 'index.js')

    const script = await readFile(serverFilePath, 'utf8')
    const expectedHash = manifest[path.join('server', 'index.js')]
    const actualHash = crypto.createHash('sha256').update(script).digest('hex')

    if (!process.env.CYPRESS_LOCAL_STUDIO_PATH && actualHash !== expectedHash) {
      throw new Error('Invalid hash for studio server script')
    }

    const studioManager = new StudioManager()

    telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.STUDIO_MANAGER_SETUP_START)

    const { cloudUrl, cloudHeaders } = await getCloudMetadata(cloudDataSource)

    await studioManager.setup({
      script,
      studioPath,
      studioHash,
      projectSlug: projectId,
      cloudApi: {
        cloudUrl,
        cloudHeaders,
        CloudRequest,
        isRetryableError,
        asyncRetry,
      },
      shouldEnableStudio: this.cloudStudioRequested,

Review comment (suggestion):
Consider adding a check to ensure the manifest contains a valid hash for 'server/index.js' before proceeding with hash verification to avoid using an undefined expected hash.

Code after:

      let hashLoadingPromise = StudioLifecycleManager.hashLoadingMap.get(studioHash)

      if (!hashLoadingPromise) {
        hashLoadingPromise = ensureStudioBundle({
          studioUrl: studioSession.studioUrl,
          studioPath,
          projectId,
        })

        StudioLifecycleManager.hashLoadingMap.set(studioHash, hashLoadingPromise)
      }

      manifest = await hashLoadingPromise
    } else {
      studioPath = process.env.CYPRESS_LOCAL_STUDIO_PATH
      studioHash = 'local'
      manifest = {}
    }

    telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.ENSURE_STUDIO_BUNDLE_END)

    const serverFilePath = path.join(studioPath, 'server', 'index.js')

    const script = await readFile(serverFilePath, 'utf8')

    if (!process.env.CYPRESS_LOCAL_STUDIO_PATH) {
      const expectedHash = manifest['server/index.js']
      const actualHash = crypto.createHash('sha256').update(script).digest('hex')

      if (!expectedHash) {
        throw new Error('Expected hash for studio server script not found in manifest')
      }

      if (actualHash !== expectedHash) {
        throw new Error('Invalid hash for studio server script')
      }
    }

    const studioManager = new StudioManager()

    telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.STUDIO_MANAGER_SETUP_START)

    const { cloudUrl, cloudHeaders } = await getCloudMetadata(cloudDataSource)

    await studioManager.setup({
      script,
      studioPath,
      studioHash,
      projectSlug: projectId,
      cloudApi: {