mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-25 01:49:06 -05:00
fix: pass inflateRaw override to decryptResponse for large cy.prompt payloads (#33619)
* fix: pass inflateRaw override to decryptResponse for large cy.prompt payloads The jose library caps decompressed JWE payloads at ~250KB by default. Larger cy.prompt /plan responses (which carry cached selectors and chains) inflated past that limit and produced DecryptionError: decryption operation failed. Match the 5MB ceiling the services-side @packages/encryption decrypt already configures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Update CHANGELOG for version 15.14.1 Added changelog entry for version 15.14.1 with bugfix details. * Update CHANGELOG.md * Apply suggestion from @ryanthemanuel --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
<!-- See ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
|
||||
|
||||
## 15.14.1
|
||||
|
||||
_Released 04/30/2026 (PENDING)_
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
- Increased the limit for decrypted payloads to support large `cy.prompt` requests and responses. Fixed in [#33619](https://github.com/cypress-io/cypress/pull/33619).
|
||||
|
||||
## 15.14.0
|
||||
|
||||
_Released Apr 16, 2026_
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import crypto, { BinaryLike } from 'crypto'
|
||||
import { TextEncoder, promisify } from 'util'
|
||||
import { generalDecrypt, GeneralJWE } from 'jose'
|
||||
import { DecryptOptions, generalDecrypt, GeneralJWE } from 'jose'
|
||||
import base64Url from 'base64url'
|
||||
import type { CypressRequestOptions } from './api'
|
||||
import { deflateRaw as deflateRawCb } from 'zlib'
|
||||
import { deflateRaw as deflateRawCb, inflateRaw as inflateRawCb } from 'zlib'
|
||||
import fs from 'fs'
|
||||
|
||||
const deflateRaw = promisify(deflateRawCb)
|
||||
const inflateRaw = promisify(inflateRawCb)
|
||||
|
||||
// The `jose` library caps decompressed JWE payloads at ~250KB by default, which
|
||||
// is too small for larger cy.prompt `/plan` responses (these carry cached
|
||||
// selectors/chains that can inflate past that threshold). Override with a 5MB
|
||||
// ceiling so decryption matches the server-side inflate limit configured in
|
||||
// `@packages/encryption` in cypress-services.
|
||||
const DECRYPT_OPTIONS: DecryptOptions = {
|
||||
inflateRaw: (data) => inflateRaw(data, { maxOutputLength: 1024 * 1024 * 5 }),
|
||||
}
|
||||
|
||||
const CY_TEST = `LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFyZ0pGb2FuMFROTUxWSUNvZWF2WQpJVWtqNWJaM2QxTlVJdVU4WjM1b2hzcUFGWHY1eGhRRzd5MWdkeTR1SVZOSFU0a1RmUERBZEk1MnRWcGE5UG1yCm5OSDhsZE5kMjRwemVFNm1CeE91MlVDQ1d3VEY5eS9BUGZqNjVkczJSSTMwR09oZm95Q1pyQndxRU1zdWJ1MTUKVVNqbFBUcEFoWlFZN2Y2bHA4TTZMYU55SUhLMzYyMDgvZFp6aWs4Q1NLWmwya0E0TUN4eWxhUElWNVVWZG0rRQpkdjJhdm1Hcy9vQzVUV1VRNzlVSmJyMDllem1oZWExcS81VnpvajRvODlKSkNnelFhcllvL1QrNVlreWJ0Z2hkCjN3NnNPSjBCQ2NrUUV5MGpXVWJWUnhSa084VGJsckxXbC9Rd0Ryd1EvRERQaEZaSGhIbCtCL3JRTldsYTFXdEoKclFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t`
|
||||
const CY_STAGING = `LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxZE1OazZYVkFhV3VlT0lXZ3V2aQpDTlhPRGVtMHRINmo0NnFTWUhJZFcyU1N5NHU0ZFpGd1VHQldlZjBEbDVmeGhZa1BFczBxUDJHUnlUZnY5YjNXCk9xWEJFQmFyNXMyYzJSMGd1RzdqNGtidTlZZklRQWpWejZndUtQMTIzd3VKSjFmcEU5T3pXWlZmUi9pQWl4b1gKTi82aEFhSHNMT1RlNXROdTVESzNOQnUxa3VJTTExcDZScEo5bGgvbWVFK3JObzRZVWUvZ2Jzam1mZmJiODRGeQpqWk1sQW9YSnYxU3lKK2phdTNMa3JkdzMybWYrczMyd2NLUnNpbmp1STgrZndDT2lEb2xnZW9NdEhta2tXVS8xCnJvUnVEcGd6d2FZemxLbUhRODNWQTlOTUhvNmMwVU40MzlBMnVtRFQ4ek94SjJjQUY0U0RiWTV3RnBnd3cvUVgKd1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t`
|
||||
@@ -111,7 +121,7 @@ export async function encryptRequest (params: Pick<CypressRequestOptions, 'body'
|
||||
* decrypts the response payload, which is assumed to be JSON
|
||||
*/
|
||||
export async function decryptResponse (jwe: GeneralJWE, encryptionKey: crypto.KeyObject): Promise<any> {
|
||||
const result = await generalDecrypt(jwe, encryptionKey)
|
||||
const result = await generalDecrypt(jwe, encryptionKey, DECRYPT_OPTIONS)
|
||||
const plaintext = Buffer.from(result.plaintext).toString('utf8')
|
||||
|
||||
return JSON.parse(plaintext)
|
||||
|
||||
@@ -66,6 +66,44 @@ describe('encryption', () => {
|
||||
expect(roundtripResponse).to.eql(RESPONSE_BODY)
|
||||
})
|
||||
|
||||
// Regression: jose's default inflateRaw caps decompressed payloads at ~250KB,
|
||||
// which caused large cy.prompt /plan responses to fail with
|
||||
// "DecryptionError: decryption operation failed". decryptResponse must pass
|
||||
// DecryptOptions with a higher maxOutputLength to match the server-side limit.
|
||||
it('decrypts response payloads larger than the jose default inflate limit', async () => {
|
||||
const { jwe, secretKey } = await encryption.encryptRequest({
|
||||
encrypt: true,
|
||||
body: TEST_BODY,
|
||||
}, { publicKey })
|
||||
|
||||
const unwrappedKey = crypto.privateDecrypt(privateKey, Buffer.from(jwe.recipients[0].encrypted_key, 'base64'))
|
||||
const unwrappedSecretKey = crypto.createSecretKey(unwrappedKey)
|
||||
|
||||
// Build a payload whose uncompressed JSON is larger than jose's ~250KB
|
||||
// default but still well within our 5MB ceiling. Use random bytes per entry
|
||||
// so the DEFLATE layer can't trivially compress it away.
|
||||
const LARGE_RESPONSE = {
|
||||
items: Array.from({ length: 800 }, (_, i) => ({
|
||||
id: i,
|
||||
xpath: `//body/div[${i}]`,
|
||||
innerText: crypto.randomBytes(256).toString('hex'),
|
||||
})),
|
||||
}
|
||||
|
||||
expect(JSON.stringify(LARGE_RESPONSE).length).to.be.greaterThan(400 * 1024)
|
||||
|
||||
const enc = new jose.GeneralEncrypt(
|
||||
Buffer.from(JSON.stringify(LARGE_RESPONSE)),
|
||||
)
|
||||
|
||||
enc.setProtectedHeader({ alg: 'A256GCMKW', enc: 'A256GCM', zip: 'DEF' }).addRecipient(unwrappedSecretKey)
|
||||
|
||||
const jweResponse = await enc.encrypt()
|
||||
const roundtripResponse = await encryption.decryptResponse(jweResponse, secretKey)
|
||||
|
||||
expect(roundtripResponse).to.eql(LARGE_RESPONSE)
|
||||
})
|
||||
|
||||
describe('verifySignatureFromFile', () => {
|
||||
it('verifies a valid signature from a file', async () => {
|
||||
const filePath = path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'encryption', 'index.js')
|
||||
|
||||
Reference in New Issue
Block a user