Skip to content

Commit 3b80385

Browse files
fix: ensure that all calls to api.cypress.io and cloud.cypress.io properly set rejectUnauthorized (#32629)
* fix: ensure that all calls to api.cypress.io and cloud.cypress.io properly set rejectUnauthorized * fix: ensure that all calls to api.cypress.io and cloud.cypress.io properly set rejectUnauthorized * Fix SSL certificate verification for cloud requests Fixed SSL certificate verification for Cypress cloud requests. * fix: ensure that all calls to api.cypress.io and cloud.cypress.io properly set rejectUnauthorized * Update packages/data-context/src/sources/UtilDataSource.ts * fix tests * fix tests
1 parent a4b7dcd commit 3b80385

File tree

14 files changed

+187
-49
lines changed

14 files changed

+187
-49
lines changed

cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ _Released 10/07/2025 (PENDING)_
1111

1212
- Fixed a regression introduced in [`15.0.0`](https://docs.cypress.io/guides/references/changelog#15-0-0) where `dbus` connection error messages appear in docker containers when launching Cypress. Fixes [#32290](https://github.com/cypress-io/cypress/issues/32290).
1313
- Fixed code frames in `cy.origin` so that failed commands will show the correct line/column within the corresponding spec file. Addressed in [#32597](https://github.com/cypress-io/cypress/pull/32597).
14+
- Fixed Cypress cloud requests so that they properly verify SSL certificates. Addressed in [#32629](https://github.com/cypress-io/cypress/pull/32629).
1415

1516
**Misc:**
1617

packages/data-context/src/sources/UtilDataSource.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { DataContext } from '../DataContext'
33
import { isDependencyInstalled, isDependencyInstalledByName } from '@packages/scaffold-config'
44

55
// Require rather than import since data-context is stricter than network and there are a fair amount of errors in agent.
6-
const { agent } = require('@packages/network')
6+
const { strictAgent } = require('@packages/network')
77

88
/**
99
* this.ctx.util....
@@ -17,7 +17,7 @@ export class UtilDataSource {
1717
fetch (input: RequestInfo | URL, init?: RequestInit) {
1818
// @ts-ignore agent isn't a part of cross-fetch's API since it's not a part of the browser's fetch but it is a part of node-fetch
1919
// which is what will be used here
20-
return fetch(input, { agent, ...init })
20+
return fetch(input, { ...init, agent: strictAgent })
2121
}
2222

2323
isDependencyInstalled (dependency: Cypress.CypressComponentDependency, projectPath: string) {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Jest globals are available without import in this environment
2+
import fetch from 'cross-fetch'
3+
import { createTestDataContext } from '../helper'
4+
import { UtilDataSource } from '../../../src/sources/UtilDataSource'
5+
import { DataContext } from '../../../src'
6+
import { strictAgent } from '@packages/network'
7+
8+
// Mock cross-fetch
9+
jest.mock('cross-fetch')
10+
const mockedFetch = jest.mocked(fetch)
11+
12+
describe('UtilDataSource', () => {
13+
let ctx: DataContext
14+
let utilDataSource: UtilDataSource
15+
16+
beforeEach(() => {
17+
ctx = createTestDataContext('open')
18+
utilDataSource = new UtilDataSource(ctx)
19+
20+
// Reset all mocks
21+
jest.clearAllMocks()
22+
})
23+
24+
afterEach(() => {
25+
ctx.destroy()
26+
})
27+
28+
describe('#fetch', () => {
29+
it('calls fetch with strictAgent and provided options', async () => {
30+
const mockResponse = {
31+
ok: true,
32+
status: 200,
33+
json: jest.fn().mockResolvedValue({ data: 'test' }),
34+
} as any
35+
36+
mockedFetch.mockResolvedValue(mockResponse)
37+
38+
const url = 'https://example.com/api'
39+
const init = { method: 'POST', body: 'test' }
40+
41+
const result = await utilDataSource.fetch(url, init)
42+
43+
expect(mockedFetch).toHaveBeenCalledWith(url, {
44+
agent: strictAgent,
45+
method: 'POST',
46+
body: 'test',
47+
})
48+
49+
expect(result).toBe(mockResponse)
50+
})
51+
52+
it('calls fetch with only strictAgent when no init options provided', async () => {
53+
const mockResponse = {
54+
ok: true,
55+
status: 200,
56+
json: jest.fn().mockResolvedValue({ data: 'test' }),
57+
} as any
58+
59+
mockedFetch.mockResolvedValue(mockResponse)
60+
61+
const url = 'https://example.com/api'
62+
63+
const result = await utilDataSource.fetch(url)
64+
65+
expect(mockedFetch).toHaveBeenCalledWith(url, {
66+
agent: expect.any(Object), // strictAgent
67+
})
68+
69+
expect(result).toBe(mockResponse)
70+
})
71+
72+
it('merges init options with agent configuration', async () => {
73+
const mockResponse = {
74+
ok: true,
75+
status: 200,
76+
} as any
77+
78+
mockedFetch.mockResolvedValue(mockResponse)
79+
80+
const url = 'https://example.com/api'
81+
const init = {
82+
method: 'PUT',
83+
headers: { 'Content-Type': 'application/json' },
84+
body: JSON.stringify({ test: 'data' }),
85+
}
86+
87+
await utilDataSource.fetch(url, init)
88+
89+
expect(mockedFetch).toHaveBeenCalledWith(url, {
90+
agent: strictAgent,
91+
method: 'PUT',
92+
headers: { 'Content-Type': 'application/json' },
93+
body: JSON.stringify({ test: 'data' }),
94+
})
95+
})
96+
})
97+
})

packages/network/lib/agent.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,18 @@ class HttpsAgent extends https.Agent {
465465
}
466466
}
467467

468+
// NODE_TLS_REJECT_UNAUTHORIZED is set to '0' in Cypress to cover
469+
// all traffic to the user's app and `agent` honors this by default.
470+
// Calls to the Cloud should use the `strictAgent` or `api/index`'s
471+
// request promise implementation instead as they override
472+
// this functionality to actually reject in unauthorized situations.
468473
const agent = new CombinedAgent()
469474

475+
// This agent always rejects unauthorized certificates.
476+
const strictAgent = new CombinedAgent({}, {
477+
rejectUnauthorized: true,
478+
})
479+
470480
export default agent
481+
482+
export { strictAgent }

packages/network/lib/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import agent from './agent'
1+
import agent, { strictAgent } from './agent'
22
import * as blocked from './blocked'
33
import * as connect from './connect'
44
import * as cors from './cors'
@@ -14,6 +14,7 @@ export {
1414
httpUtils,
1515
uri,
1616
clientCertificates,
17+
strictAgent,
1718
}
1819

1920
export { allowDestroy } from './allow-destroy'

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import os from 'os'
55
import followRedirects from 'follow-redirects'
66
import axios, { AxiosInstance } from 'axios'
77
import pkg from '@packages/root'
8-
import agent from '@packages/network/lib/agent'
8+
import { strictAgent } from '@packages/network/lib/agent'
99

1010
import app_config from '../../../config/app.json'
1111
import { installErrorTransform } from './axios_middleware/transform_error'
@@ -40,8 +40,8 @@ export const createCloudRequest = (options: CreateCloudRequestOptions = {}): Axi
4040

4141
const instance = axios.create({
4242
baseURL,
43-
httpAgent: agent,
44-
httpsAgent: agent,
43+
httpAgent: strictAgent,
44+
httpsAgent: strictAgent,
4545
headers: {
4646
'x-os-name': os.platform(),
4747
'x-cypress-version': pkg.version,

packages/server/lib/cloud/api/studio/get_studio_bundle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { asyncRetry, linearDelay } from '../../../util/async_retry'
22
import { isRetryableError } from '../../network/is_retryable_error'
33
import fetch from 'cross-fetch'
44
import os from 'os'
5-
import { agent } from '@packages/network'
5+
import { strictAgent } from '@packages/network'
66
import { PUBLIC_KEY_VERSION } from '../../constants'
77
import { createWriteStream } from 'fs'
88
import { verifySignatureFromFile } from '../../encryption'
@@ -24,7 +24,7 @@ export const getStudioBundle = async ({ studioUrl, bundlePath }: { studioUrl: st
2424
try {
2525
const response = await fetch(studioUrl, {
2626
// @ts-expect-error - this is supported
27-
agent,
27+
agent: strictAgent,
2828
method: 'GET',
2929
headers: {
3030
'x-route-version': '1',

packages/server/lib/cloud/api/studio/post_studio_session.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { asyncRetry, linearDelay } from '../../../util/async_retry'
22
import { isRetryableError } from '../../network/is_retryable_error'
33
import fetch from 'cross-fetch'
44
import os from 'os'
5-
import { agent } from '@packages/network'
5+
import { strictAgent } from '@packages/network'
66

77
const pkg = require('@packages/root')
88
const routes = require('../../routes') as typeof import('../../routes')
@@ -17,7 +17,7 @@ export const postStudioSession = async ({ projectId }: PostStudioSessionOptions)
1717
return await (asyncRetry(async () => {
1818
const response = await fetch(routes.apiRoutes.studioSession(), {
1919
// @ts-expect-error - this is supported
20-
agent,
20+
agent: strictAgent,
2121
method: 'POST',
2222
headers: {
2323
'Content-Type': 'application/json',

packages/server/lib/cloud/network/put_fetch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import crossFetch from 'cross-fetch'
22
import { SystemError } from './system_error'
33
import { HttpError } from './http_error'
44
import { ParseError } from './parse_error'
5-
import { agent } from '@packages/network'
5+
import { strictAgent } from '@packages/network'
66
import Debug from 'debug'
77

88
const debug = Debug('cypress-verbose:server:cloud:api:put')
@@ -37,7 +37,7 @@ export async function putFetch<
3737
// types based on that rather than on node-fetch which it
3838
// actually uses under the hood. node-fetch supports `agent`.
3939
// @ts-expect-error
40-
agent,
40+
agent: strictAgent,
4141
})
4242

4343
if (response.status >= 400) {

packages/server/lib/cloud/protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Debug from 'debug'
66
import fs from 'fs-extra'
77
import os from 'os'
88
import path from 'path'
9-
import { agent } from '@packages/network'
9+
import { strictAgent } from '@packages/network'
1010
import pkg from '@packages/root'
1111
import env from '../util/env'
1212
import { putProtocolArtifact } from './api/put_protocol_artifact'
@@ -416,7 +416,7 @@ export class ProtocolManager implements ProtocolManagerShape {
416416

417417
await fetch(routes.apiRoutes.captureProtocolErrors() as string, {
418418
// @ts-expect-error - this is supported
419-
agent,
419+
agent: strictAgent,
420420
method: 'POST',
421421
body,
422422
headers: {

0 commit comments

Comments
 (0)