Skip to content

Commit ae46f1e

Browse files
authored
fix: filter large user-supplied config values from the cloud API (#32957)
1 parent 70a9525 commit ae46f1e

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

cli/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ _Released 11/18/2025 (PENDING)_
1616
- 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).
1717
- 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).
1818
- Fixed an issue where stack traces that are used to determine a test's invocation details are sometimes incorrect. Addressed in [#32699](https://github.com/cypress-io/cypress/pull/32699)
19+
- 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)
20+
1921

2022
**Misc:**
2123

packages/server/lib/cloud/api/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type { CreateInstanceRequestBody, CreateInstanceResponse } from './create
3737
import { transformError } from './axios_middleware/transform_error'
3838
import { DecryptionError } from './cloud_request_errors'
3939
import { isNonRetriableCertErrorCode } from '../network/non_retriable_cert_error_codes'
40+
import { filterRuntimeConfigForRecording } from '../../config'
4041

4142
const debug = debugModule('cypress:server:cloud:api')
4243
const debugProtocol = debugModule('cypress:server:protocol')
@@ -506,7 +507,7 @@ export default {
506507
},
507508

508509
postInstanceTests (options) {
509-
const { instanceId, runId, timeout, ...body } = options
510+
const { instanceId, runId, timeout, config, ...body } = options
510511

511512
return retryWithBackoff((attemptIndex) => {
512513
return rp.post({
@@ -519,7 +520,10 @@ export default {
519520
'x-cypress-run-id': runId,
520521
'x-cypress-request-attempt': attemptIndex,
521522
},
522-
body,
523+
body: {
524+
...body,
525+
config: filterRuntimeConfigForRecording(config ?? {}),
526+
},
523527
})
524528
.catch(RequestErrors.StatusCodeError, transformError)
525529
.catch(tagError)

packages/server/lib/config.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import _ from 'lodash'
2-
import type { ResolvedFromConfig } from '@packages/types'
32
import * as configUtils from '@packages/config'
43

54
export const setUrls = configUtils.setUrls
65

7-
export function getResolvedRuntimeConfig (config, runtimeConfig) {
8-
const resolvedRuntimeFields = _.mapValues(runtimeConfig, (v): ResolvedFromConfig => ({ value: v, from: 'runtime' }))
6+
// Strips out values that can be aribitrarily sized / are duplicated from config
7+
// payload sent for recording
8+
export function filterRuntimeConfigForRecording (config) {
9+
const { rawJson, devServer, env, resolved, ...configRest } = config
10+
const { webpackConfig, viteConfig, ...devServerRest } = devServer ?? {}
11+
const resultConfig = { ...configRest }
912

10-
return {
11-
...config,
12-
...runtimeConfig,
13-
resolved: { ...config.resolved, ...resolvedRuntimeFields },
13+
if (env) {
14+
resultConfig.env = _.mapValues(env ?? {}, (val, key) => `omitted: ${typeof val}`)
1415
}
16+
17+
if (devServer) {
18+
resultConfig.devServer = { ...devServerRest }
19+
if (typeof webpackConfig !== 'undefined') {
20+
resultConfig.devServer.webpackConfig = `omitted`
21+
}
22+
23+
if (typeof viteConfig !== 'undefined') {
24+
resultConfig.devServer.viteConfig = `omitted`
25+
}
26+
}
27+
28+
return resultConfig
1529
}

packages/server/lib/modes/record.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { getError } from '@packages/errors'
1515
import type { AllCypressErrorNames } from '@packages/errors'
1616
import { get as getErrors, warning as errorsWarning, throwErr } from '../errors'
1717
import * as capture from '../capture'
18-
import { getResolvedRuntimeConfig } from '../config'
1918
import * as env from '../util/env'
2019
import ciProvider from '../util/ci_provider'
2120
import { flattenSuiteIntoRunnables } from '../util/tests_utils'
@@ -754,7 +753,7 @@ const createRunAndRecordSpecs = (options: any = {}) => {
754753

755754
const r = flattenSuiteIntoRunnables(runnables)
756755
const runtimeConfig = runnables.runtimeConfig
757-
const resolvedRuntimeConfig = getResolvedRuntimeConfig(config, runtimeConfig)
756+
const resolvedRuntimeConfig = { ...config, ...runtimeConfig }
758757

759758
const tests = _.chain(r[0])
760759
.uniqBy('id')

packages/server/test/unit/cloud/api/api_spec.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require('../../../spec_helper')
88
const _ = require('lodash')
99
const os = require('os')
1010
const encryption = require('../../../../lib/cloud/encryption')
11+
const { filterRuntimeConfigForRecording } = require('../../../../lib/config')
1112

1213
const {
1314
agent,
@@ -987,7 +988,7 @@ describe('lib/cloud/api', () => {
987988
this.bodyProps = _.omit(this.props, 'instanceId', 'runId')
988989
})
989990

990-
it('POSTs /instances/:id/results', function () {
991+
it('POSTs /instances/:id/tests', function () {
991992
nock(API_BASEURL)
992993
.matchHeader('x-route-version', '1')
993994
.matchHeader('x-cypress-run-id', this.props.runId)
@@ -1000,6 +1001,59 @@ describe('lib/cloud/api', () => {
10001001
return api.postInstanceTests(this.props)
10011002
})
10021003

1004+
it('POSTs /instances/:id/tests strips arbitrarily large config values', function () {
1005+
this.props.config = {
1006+
projectId: 'abcd1234',
1007+
devServer: {
1008+
bundler: 'webpack',
1009+
framework: 'react',
1010+
webpackConfig: 'a'.repeat(10000),
1011+
viteConfig: 'a'.repeat(10000),
1012+
},
1013+
env: {
1014+
NUMERIC_VALUE: 1,
1015+
TRUTHY_VALUE: true,
1016+
SOME_REALLY_LONG_VALUE: 'a'.repeat(10000),
1017+
},
1018+
resolved: {
1019+
env: {
1020+
'NUMERIC_VALUE': { 'value': 1, 'from': 'env' },
1021+
'TRUTHY_VALUE': { 'value': true, 'from': 'env' },
1022+
'SOME_REALLY_LONG_VALUE': { 'value': 'a'.repeat(10000), 'from': 'env' },
1023+
},
1024+
},
1025+
}
1026+
1027+
this.props.config.rawJson = _.cloneDeep(this.props.config)
1028+
1029+
const expectedConfig = filterRuntimeConfigForRecording(this.props.config)
1030+
1031+
nock(API_BASEURL)
1032+
.matchHeader('x-route-version', '1')
1033+
.matchHeader('x-cypress-run-id', this.props.runId)
1034+
.matchHeader('x-cypress-request-attempt', '0')
1035+
.matchHeader('x-os-name', OS_PLATFORM)
1036+
.matchHeader('x-cypress-version', pkg.version)
1037+
.post('/instances/instance-id-123/tests', {
1038+
...this.bodyProps,
1039+
config: expectedConfig,
1040+
})
1041+
.reply(200)
1042+
1043+
expect(expectedConfig.projectId).to.eq('abcd1234')
1044+
expect(expectedConfig.env).to.eql({
1045+
NUMERIC_VALUE: `omitted: number`,
1046+
TRUTHY_VALUE: `omitted: boolean`,
1047+
SOME_REALLY_LONG_VALUE: `omitted: string`,
1048+
})
1049+
1050+
expect(expectedConfig.resolved).to.be.undefined
1051+
expect(expectedConfig.devServer.webpackConfig).to.equal('omitted')
1052+
expect(expectedConfig.devServer.viteConfig).to.equal('omitted')
1053+
1054+
return api.postInstanceTests(this.props)
1055+
})
1056+
10031057
it('PUT /instances/:id failure formatting', () => {
10041058
nock(API_BASEURL)
10051059
.matchHeader('x-route-version', '1')

system-tests/test/record_spec.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -602,16 +602,7 @@ describe('e2e record', () => {
602602
const requests = getRequests()
603603

604604
expect(requests[2].body.config.defaultCommandTimeout).eq(1111)
605-
expect(requests[2].body.config.resolved.defaultCommandTimeout).deep.eq({
606-
value: 1111,
607-
from: 'runtime',
608-
})
609-
610605
expect(requests[2].body.config.pageLoadTimeout).eq(3333)
611-
expect(requests[2].body.config.resolved.pageLoadTimeout).deep.eq({
612-
value: 3333,
613-
from: 'runtime',
614-
})
615606

616607
expect(requests[2].body.tests[0].config).deep.eq({
617608
defaultCommandTimeout: 1234,

0 commit comments

Comments
 (0)