From 24dc326a17fa474dda2bad92964c31571a46789b Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 17 Nov 2025 22:28:06 -0500 Subject: [PATCH 1/7] fix: filter large user-supplied config values from the cloud API --- packages/server/lib/cloud/api/index.ts | 8 +++- packages/server/lib/config.ts | 30 ++++++++++++++ .../server/test/unit/cloud/api/api_spec.js | 41 ++++++++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/packages/server/lib/cloud/api/index.ts b/packages/server/lib/cloud/api/index.ts index 41389431c99..a46fbdd81ad 100644 --- a/packages/server/lib/cloud/api/index.ts +++ b/packages/server/lib/cloud/api/index.ts @@ -37,6 +37,7 @@ import type { CreateInstanceRequestBody, CreateInstanceResponse } from './create import { transformError } from './axios_middleware/transform_error' import { DecryptionError } from './cloud_request_errors' import { isNonRetriableCertErrorCode } from '../network/non_retriable_cert_error_codes' +import { filterRuntimeConfigForRecoding } from '../../config' const debug = debugModule('cypress:server:cloud:api') const debugProtocol = debugModule('cypress:server:protocol') @@ -506,7 +507,7 @@ export default { }, postInstanceTests (options) { - const { instanceId, runId, timeout, ...body } = options + const { instanceId, runId, timeout, config, ...body } = options return retryWithBackoff((attemptIndex) => { return rp.post({ @@ -519,7 +520,10 @@ export default { 'x-cypress-run-id': runId, 'x-cypress-request-attempt': attemptIndex, }, - body, + body: { + ...body, + config: filterRuntimeConfigForRecoding(config), + }, }) .catch(RequestErrors.StatusCodeError, transformError) .catch(tagError) diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index 5d22ab1d689..226299585de 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -13,3 +13,33 @@ export function getResolvedRuntimeConfig (config, runtimeConfig) { resolved: { ...config.resolved, ...resolvedRuntimeFields }, } } + +// Strips out values that can be aribitrarily sized from config payload sent for recording +export function filterRuntimeConfigForRecoding (config) { + const { rawJson, devServer, env, resolved = {}, ...configRest } = config + const { webpackConfig, viteConfig, ...devServerRest } = devServer ?? {} + const resultConfig = { ...configRest } + + resultConfig.env = _.mapValues(env ?? {}, (val, key) => `omitted: ${typeof val}`) + + if (devServer) { + resultConfig.devServer = { ...devServerRest } + if (typeof webpackConfig !== 'undefined') { + resultConfig.devServer.webpackConfig = `omitted` + } + + if (typeof viteConfig !== 'undefined') { + resultConfig.devServer.viteConfig = `omitted` + } + } + + resultConfig.resolved = { + ...resolved, + env: _.mapValues(resolved.env ?? {}, (val, key) => ({ + ...val, + value: `omitted: ${typeof val.value}`, + })), + } + + return resultConfig +} diff --git a/packages/server/test/unit/cloud/api/api_spec.js b/packages/server/test/unit/cloud/api/api_spec.js index 83691b3df1a..ab13b958bdb 100644 --- a/packages/server/test/unit/cloud/api/api_spec.js +++ b/packages/server/test/unit/cloud/api/api_spec.js @@ -8,6 +8,7 @@ require('../../../spec_helper') const _ = require('lodash') const os = require('os') const encryption = require('../../../../lib/cloud/encryption') +const { filterRuntimeConfigForRecoding } = require('../../../../lib/config') const { agent, @@ -987,7 +988,7 @@ describe('lib/cloud/api', () => { this.bodyProps = _.omit(this.props, 'instanceId', 'runId') }) - it('POSTs /instances/:id/results', function () { + it('POSTs /instances/:id/tests', function () { nock(API_BASEURL) .matchHeader('x-route-version', '1') .matchHeader('x-cypress-run-id', this.props.runId) @@ -1000,6 +1001,44 @@ describe('lib/cloud/api', () => { return api.postInstanceTests(this.props) }) + it('POSTs /instances/:id/tests strips arbitrarily large config values', function () { + this.props.config = { + devServer: { + bundler: 'webpack', + framework: 'react', + webpackConfig: 'a'.repeat(10000), + }, + env: { + NUMERIC_VALUE: 1, + TRUTHY_VALUE: true, + SOME_REALLY_LONG_VALUE: 'a'.repeat(10000), + }, + resolved: { + env: { + 'NUMERIC_VALUE': { 'value': 1, 'from': 'env' }, + 'TRUTHY_VALUE': { 'value': true, 'from': 'env' }, + 'SOME_REALLY_LONG_VALUE': { 'value': 'a'.repeat(10000), 'from': 'env' }, + }, + }, + } + + this.props.config.rawJson = _.cloneDeep(this.props.config) + + nock(API_BASEURL) + .matchHeader('x-route-version', '1') + .matchHeader('x-cypress-run-id', this.props.runId) + .matchHeader('x-cypress-request-attempt', '0') + .matchHeader('x-os-name', OS_PLATFORM) + .matchHeader('x-cypress-version', pkg.version) + .post('/instances/instance-id-123/tests', { + ...this.bodyProps, + config: filterRuntimeConfigForRecoding(this.props.config), + }) + .reply(200) + + return api.postInstanceTests(this.props) + }) + it('PUT /instances/:id failure formatting', () => { nock(API_BASEURL) .matchHeader('x-route-version', '1') From 7c1c09badddbeee7d6f69844a134d74d4b66e231 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 17 Nov 2025 22:57:16 -0500 Subject: [PATCH 2/7] fix test failures --- packages/server/lib/cloud/api/index.ts | 2 +- packages/server/lib/config.ts | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/server/lib/cloud/api/index.ts b/packages/server/lib/cloud/api/index.ts index a46fbdd81ad..082f900f922 100644 --- a/packages/server/lib/cloud/api/index.ts +++ b/packages/server/lib/cloud/api/index.ts @@ -522,7 +522,7 @@ export default { }, body: { ...body, - config: filterRuntimeConfigForRecoding(config), + config: filterRuntimeConfigForRecoding(config ?? {}), }, }) .catch(RequestErrors.StatusCodeError, transformError) diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index 226299585de..9d899903662 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -16,11 +16,13 @@ export function getResolvedRuntimeConfig (config, runtimeConfig) { // Strips out values that can be aribitrarily sized from config payload sent for recording export function filterRuntimeConfigForRecoding (config) { - const { rawJson, devServer, env, resolved = {}, ...configRest } = config + const { rawJson, devServer, env, ...configRest } = config const { webpackConfig, viteConfig, ...devServerRest } = devServer ?? {} const resultConfig = { ...configRest } - resultConfig.env = _.mapValues(env ?? {}, (val, key) => `omitted: ${typeof val}`) + if (env) { + resultConfig.env = _.mapValues(env ?? {}, (val, key) => `omitted: ${typeof val}`) + } if (devServer) { resultConfig.devServer = { ...devServerRest } @@ -33,12 +35,14 @@ export function filterRuntimeConfigForRecoding (config) { } } - resultConfig.resolved = { - ...resolved, - env: _.mapValues(resolved.env ?? {}, (val, key) => ({ - ...val, - value: `omitted: ${typeof val.value}`, - })), + if (resultConfig.resolved?.env) { + resultConfig.resolved = { + ...resultConfig.resolved, + env: _.mapValues(resultConfig.resolved.env ?? {}, (val, key) => ({ + ...val, + value: `omitted: ${typeof val.value}`, + })), + } } return resultConfig From 451428842a211d0c4e6fd645aecc13117fbb5cdd Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 18 Nov 2025 10:15:39 -0500 Subject: [PATCH 3/7] add changelog entry, retain truthy values --- cli/CHANGELOG.md | 1 + packages/server/lib/config.ts | 4 ++-- packages/server/test/unit/cloud/api/api_spec.js | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 96466cc47ec..ba747d1c53a 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -11,6 +11,7 @@ _Released 11/18/2025 (PENDING)_ - Fixed an issue where [`cy.wrap()`](https://docs.cypress.io/api/commands/wrap) would cause infinite recursion and freeze the Cypress App when called with objects containing circular references. Fixes [#24715](https://github.com/cypress-io/cypress/issues/24715). Addressed in [#32917](https://github.com/cypress-io/cypress/pull/32917). - Fixed an issue where top changes on test retries could cause attempt numbers to show up more than one time in the reporter and cause attempts to be lost in Test Replay. Addressed in [#32888](https://github.com/cypress-io/cypress/pull/32888). +- Fixed an issue where larger than expected config values were causing issues in certain cases when recording to the Cypress Cloud. Addressed in [#32957](https://github.com/cypress-io/cypress/pull/32957) **Misc:** diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index 9d899903662..a6c1db792d9 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -21,7 +21,7 @@ export function filterRuntimeConfigForRecoding (config) { const resultConfig = { ...configRest } if (env) { - resultConfig.env = _.mapValues(env ?? {}, (val, key) => `omitted: ${typeof val}`) + resultConfig.env = _.mapValues(env ?? {}, (val, key) => typeof val === 'boolean' ? val : `omitted: ${typeof val}`) } if (devServer) { @@ -40,7 +40,7 @@ export function filterRuntimeConfigForRecoding (config) { ...resultConfig.resolved, env: _.mapValues(resultConfig.resolved.env ?? {}, (val, key) => ({ ...val, - value: `omitted: ${typeof val.value}`, + value: typeof val === 'boolean' ? val : `omitted: ${typeof val}`, })), } } diff --git a/packages/server/test/unit/cloud/api/api_spec.js b/packages/server/test/unit/cloud/api/api_spec.js index ab13b958bdb..1829ab9ea56 100644 --- a/packages/server/test/unit/cloud/api/api_spec.js +++ b/packages/server/test/unit/cloud/api/api_spec.js @@ -1024,6 +1024,8 @@ describe('lib/cloud/api', () => { this.props.config.rawJson = _.cloneDeep(this.props.config) + const expectedConfig = filterRuntimeConfigForRecoding(this.props.config) + nock(API_BASEURL) .matchHeader('x-route-version', '1') .matchHeader('x-cypress-run-id', this.props.runId) @@ -1032,10 +1034,12 @@ describe('lib/cloud/api', () => { .matchHeader('x-cypress-version', pkg.version) .post('/instances/instance-id-123/tests', { ...this.bodyProps, - config: filterRuntimeConfigForRecoding(this.props.config), + config: expectedConfig, }) .reply(200) + expect(expectedConfig.env.TRUTHY_VALUE).to.equal(true) + return api.postInstanceTests(this.props) }) From 186b513b4931930cb7da7381a79b71cd7542c224 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 18 Nov 2025 13:23:50 -0500 Subject: [PATCH 4/7] address PR review --- packages/server/lib/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index a6c1db792d9..fbb76209c35 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -40,7 +40,7 @@ export function filterRuntimeConfigForRecoding (config) { ...resultConfig.resolved, env: _.mapValues(resultConfig.resolved.env ?? {}, (val, key) => ({ ...val, - value: typeof val === 'boolean' ? val : `omitted: ${typeof val}`, + value: typeof val.value === 'boolean' ? val.value : `omitted: ${typeof val.value}`, })), } } From fe031c793d0b325407b9a011124f1c03c1066723 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 18 Nov 2025 14:52:24 -0500 Subject: [PATCH 5/7] fix typo [ci skip] --- packages/server/lib/cloud/api/index.ts | 4 ++-- packages/server/lib/config.ts | 2 +- packages/server/test/unit/cloud/api/api_spec.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/server/lib/cloud/api/index.ts b/packages/server/lib/cloud/api/index.ts index 082f900f922..90c0c2b246b 100644 --- a/packages/server/lib/cloud/api/index.ts +++ b/packages/server/lib/cloud/api/index.ts @@ -37,7 +37,7 @@ import type { CreateInstanceRequestBody, CreateInstanceResponse } from './create import { transformError } from './axios_middleware/transform_error' import { DecryptionError } from './cloud_request_errors' import { isNonRetriableCertErrorCode } from '../network/non_retriable_cert_error_codes' -import { filterRuntimeConfigForRecoding } from '../../config' +import { filterRuntimeConfigForRecording } from '../../config' const debug = debugModule('cypress:server:cloud:api') const debugProtocol = debugModule('cypress:server:protocol') @@ -522,7 +522,7 @@ export default { }, body: { ...body, - config: filterRuntimeConfigForRecoding(config ?? {}), + config: filterRuntimeConfigForRecording(config ?? {}), }, }) .catch(RequestErrors.StatusCodeError, transformError) diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index fbb76209c35..69c28784bd8 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -15,7 +15,7 @@ export function getResolvedRuntimeConfig (config, runtimeConfig) { } // Strips out values that can be aribitrarily sized from config payload sent for recording -export function filterRuntimeConfigForRecoding (config) { +export function filterRuntimeConfigForRecording (config) { const { rawJson, devServer, env, ...configRest } = config const { webpackConfig, viteConfig, ...devServerRest } = devServer ?? {} const resultConfig = { ...configRest } diff --git a/packages/server/test/unit/cloud/api/api_spec.js b/packages/server/test/unit/cloud/api/api_spec.js index 1829ab9ea56..4810a819600 100644 --- a/packages/server/test/unit/cloud/api/api_spec.js +++ b/packages/server/test/unit/cloud/api/api_spec.js @@ -8,7 +8,7 @@ require('../../../spec_helper') const _ = require('lodash') const os = require('os') const encryption = require('../../../../lib/cloud/encryption') -const { filterRuntimeConfigForRecoding } = require('../../../../lib/config') +const { filterRuntimeConfigForRecording } = require('../../../../lib/config') const { agent, @@ -1024,7 +1024,7 @@ describe('lib/cloud/api', () => { this.props.config.rawJson = _.cloneDeep(this.props.config) - const expectedConfig = filterRuntimeConfigForRecoding(this.props.config) + const expectedConfig = filterRuntimeConfigForRecording(this.props.config) nock(API_BASEURL) .matchHeader('x-route-version', '1') From a10648c2b64487db887167b2faaf320c56a27f9d Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 18 Nov 2025 16:10:34 -0500 Subject: [PATCH 6/7] update PR feedback --- packages/server/lib/config.ts | 28 +++---------------- packages/server/lib/modes/record.ts | 3 +- .../server/test/unit/cloud/api/api_spec.js | 13 ++++++++- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index 69c28784bd8..d4e3bd1c3c0 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -1,27 +1,17 @@ import _ from 'lodash' -import type { ResolvedFromConfig } from '@packages/types' import * as configUtils from '@packages/config' export const setUrls = configUtils.setUrls -export function getResolvedRuntimeConfig (config, runtimeConfig) { - const resolvedRuntimeFields = _.mapValues(runtimeConfig, (v): ResolvedFromConfig => ({ value: v, from: 'runtime' })) - - return { - ...config, - ...runtimeConfig, - resolved: { ...config.resolved, ...resolvedRuntimeFields }, - } -} - -// Strips out values that can be aribitrarily sized from config payload sent for recording +// Strips out values that can be aribitrarily sized / are duplicated from config +// payload sent for recording export function filterRuntimeConfigForRecording (config) { - const { rawJson, devServer, env, ...configRest } = config + const { rawJson, devServer, env, resolved, ...configRest } = config const { webpackConfig, viteConfig, ...devServerRest } = devServer ?? {} const resultConfig = { ...configRest } if (env) { - resultConfig.env = _.mapValues(env ?? {}, (val, key) => typeof val === 'boolean' ? val : `omitted: ${typeof val}`) + resultConfig.env = _.mapValues(env ?? {}, (val, key) => `omitted: ${typeof val}`) } if (devServer) { @@ -35,15 +25,5 @@ export function filterRuntimeConfigForRecording (config) { } } - if (resultConfig.resolved?.env) { - resultConfig.resolved = { - ...resultConfig.resolved, - env: _.mapValues(resultConfig.resolved.env ?? {}, (val, key) => ({ - ...val, - value: typeof val.value === 'boolean' ? val.value : `omitted: ${typeof val.value}`, - })), - } - } - return resultConfig } diff --git a/packages/server/lib/modes/record.ts b/packages/server/lib/modes/record.ts index f7cbd3bed4e..7fa24070c07 100644 --- a/packages/server/lib/modes/record.ts +++ b/packages/server/lib/modes/record.ts @@ -15,7 +15,6 @@ import { getError } from '@packages/errors' import type { AllCypressErrorNames } from '@packages/errors' import { get as getErrors, warning as errorsWarning, throwErr } from '../errors' import * as capture from '../capture' -import { getResolvedRuntimeConfig } from '../config' import * as env from '../util/env' import ciProvider from '../util/ci_provider' import { flattenSuiteIntoRunnables } from '../util/tests_utils' @@ -754,7 +753,7 @@ const createRunAndRecordSpecs = (options: any = {}) => { const r = flattenSuiteIntoRunnables(runnables) const runtimeConfig = runnables.runtimeConfig - const resolvedRuntimeConfig = getResolvedRuntimeConfig(config, runtimeConfig) + const resolvedRuntimeConfig = { ...config, ...runtimeConfig } const tests = _.chain(r[0]) .uniqBy('id') diff --git a/packages/server/test/unit/cloud/api/api_spec.js b/packages/server/test/unit/cloud/api/api_spec.js index 4810a819600..bdbf7593ca5 100644 --- a/packages/server/test/unit/cloud/api/api_spec.js +++ b/packages/server/test/unit/cloud/api/api_spec.js @@ -1003,10 +1003,12 @@ describe('lib/cloud/api', () => { it('POSTs /instances/:id/tests strips arbitrarily large config values', function () { this.props.config = { + projectId: 'abcd1234', devServer: { bundler: 'webpack', framework: 'react', webpackConfig: 'a'.repeat(10000), + viteConfig: 'a'.repeat(10000), }, env: { NUMERIC_VALUE: 1, @@ -1038,7 +1040,16 @@ describe('lib/cloud/api', () => { }) .reply(200) - expect(expectedConfig.env.TRUTHY_VALUE).to.equal(true) + expect(expectedConfig.projectId).to.eq('abcd1234') + expect(expectedConfig.env).to.eql({ + NUMERIC_VALUE: `omitted: number`, + TRUTHY_VALUE: `omitted: boolean`, + SOME_REALLY_LONG_VALUE: `omitted: string`, + }) + + expect(expectedConfig.resolved).to.be.undefined + expect(expectedConfig.devServer.webpackConfig).to.equal('omitted') + expect(expectedConfig.devServer.viteConfig).to.equal('omitted') return api.postInstanceTests(this.props) }) From dd67d03f1a3a5a8add96255c598d7532e7d11615 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 18 Nov 2025 17:09:14 -0500 Subject: [PATCH 7/7] fix tests after dropping resolved --- system-tests/test/record_spec.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/system-tests/test/record_spec.js b/system-tests/test/record_spec.js index 0df5f6828e1..8294f0cd7ea 100644 --- a/system-tests/test/record_spec.js +++ b/system-tests/test/record_spec.js @@ -602,16 +602,7 @@ describe('e2e record', () => { const requests = getRequests() expect(requests[2].body.config.defaultCommandTimeout).eq(1111) - expect(requests[2].body.config.resolved.defaultCommandTimeout).deep.eq({ - value: 1111, - from: 'runtime', - }) - expect(requests[2].body.config.pageLoadTimeout).eq(3333) - expect(requests[2].body.config.resolved.pageLoadTimeout).deep.eq({ - value: 3333, - from: 'runtime', - }) expect(requests[2].body.tests[0].config).deep.eq({ defaultCommandTimeout: 1234,