VYPR
Moderate severityNVD Advisory· Published Mar 9, 2024· Updated Feb 13, 2025

jose vulnerable to resource exhaustion via specifically crafted JWE with compressed plaintext

CVE-2024-28176

Description

jose is JavaScript module for JSON Object Signing and Encryption, providing support for JSON Web Tokens (JWT), JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), JSON Web Key Set (JWKS), and more. A vulnerability has been identified in the JSON Web Encryption (JWE) decryption interfaces, specifically related to the support for decompressing plaintext after its decryption. Under certain conditions it is possible to have the user's environment consume unreasonable amount of CPU time or memory during JWE Decryption operations. This issue has been patched in versions 2.0.7 and 4.15.5.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jose JWE decryption vulnerable to resource exhaustion via compressed plaintext; patched in v2.0.7 and v4.15.5.

Vulnerability

Overview

CVE-2024-28176 is a resource exhaustion vulnerability in the JSON Web Encryption (JWE) decryption interface of the jose JavaScript module. The root cause lies in the module's support for decompressing plaintext after decryption. According to RFC 8725, compression before encryption is discouraged, but jose allowed it. An attacker can craft a JWE token with a highly compressed payload such that the token size is small but the decompressed plaintext is extremely large, leading to excessive CPU or memory consumption during decryption [1][4].

Exploitation

Conditions

Exploitation requires the attacker to send a specially crafted JWE token to an application that uses jose's JWE decryption APIs and accepts tokens from untrusted sources. The attack is limited to Node.js environments, where zlib decompression is implemented natively; runtimes like Deno or Cloudflare Workers are not affected because they lack built-in compression support [4]. Token size limits may be bypassed since the compressed token appears small, and after decryption, the decompression inflates the payload, overwhelming system resources.

Impact

Successful exploitation results in denial-of-service conditions through unreasonable CPU time or memory consumption. The attacker does not need authentication if the application processes JWEs from untrusted sources. The impact is confined to resource exhaustion; no data breach or integrity compromise is reported [1][4].

Mitigation

The issue is patched in jose versions 2.0.7 and 4.15.5. The fix introduces a maximum decompressed output limit of 250 kB, configurable via the inflateRawSyncLimit (v2.x) or inflateRaw decryption option (v4.x) [3][4]. Users of v5.x are unaffected as compression support was removed entirely. As a workaround, users unable to upgrade can reject tokens with a zip header indicating compression [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
josenpm
>= 3.0.0, < 4.15.54.15.5
jose-node-cjs-runtimenpm
< 4.15.54.15.5
jose-node-esm-runtimenpm
< 4.15.54.15.5
josenpm
< 2.0.72.0.7

Affected products

45

Patches

2
02a65794f787

fix: add a maxOutputLength option to zlib inflate

https://github.com/panva/joseFilip SkokanMar 7, 2024via ghsa
4 files changed · +28 2
  • docs/README.md+2 0 modified
    @@ -1494,6 +1494,8 @@ operation.
         Count) Header Parameter value. The PBKDF2 iteration count defines the algorithm's computational
         expense.
         **Default:** '10000'
    +  - `inflateRawSyncLimit`: `number` Limits compressed JWE plaintext output size.
    +    **Default:** '250000'
       - `complete`: `<boolean>` When true returns an object with the parsed headers, verified
         AAD, the content encryption key, the key that was used to unwrap or derive the content
         encryption key, and cleartext instead of only the cleartext.
    
  • lib/jwe/decrypt.js+2 2 modified
    @@ -52,7 +52,7 @@ const validateAlgorithms = (algorithms, option) => {
     /*
      * @public
      */
    -const jweDecrypt = (skipValidateHeaders, serialization, jwe, key, { crit = [], complete = false, keyManagementAlgorithms, contentEncryptionAlgorithms, maxPBES2Count = 10000 } = {}) => {
    +const jweDecrypt = (skipValidateHeaders, serialization, jwe, key, { crit = [], complete = false, keyManagementAlgorithms, contentEncryptionAlgorithms, maxPBES2Count = 10000, inflateRawSyncLimit = 250000 } = {}) => {
       key = getKey(key, true)
     
       keyManagementAlgorithms = validateAlgorithms(keyManagementAlgorithms, 'keyManagementAlgorithms')
    @@ -189,7 +189,7 @@ const jweDecrypt = (skipValidateHeaders, serialization, jwe, key, { crit = [], c
         let cleartext = decrypt(enc, cek, base64url.decodeToBuffer(ciphertext), { iv, tag, aad: adata })
     
         if (opts.zip) {
    -      cleartext = inflateRawSync(cleartext)
    +      cleartext = inflateRawSync(cleartext, { maxOutputLength: inflateRawSyncLimit })
         }
     
         if (complete) {
    
  • test/jwe/sanity.test.js+23 0 modified
    @@ -1,4 +1,5 @@
     const test = require('ava')
    +const crypto = require('crypto')
     
     const base64url = require('../../lib/help/base64url')
     const { JWKS, JWK: { generateSync }, JWE, errors } = require('../..')
    @@ -616,3 +617,25 @@ if (!('electron' in process.versions)) {
         }, { instanceOf: errors.JWEInvalid, message: 'JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds' })
       })
     }
    +
    +test('Compressed JWE output length limit', t => {
    +  const k = generateSync('oct', 256)
    +  {
    +    const jwe = JWE.encrypt(crypto.randomBytes(250000), k, { alg: 'dir', enc: 'A128CBC-HS256', zip: 'DEF' })
    +    t.notThrows(() => {
    +      JWE.decrypt(jwe, k)
    +    })
    +  }
    +  {
    +    const jwe = JWE.encrypt(crypto.randomBytes(250000 + 1), k, { alg: 'dir', enc: 'A128CBC-HS256', zip: 'DEF' })
    +    t.throws(() => {
    +      JWE.decrypt(jwe, k)
    +    })
    +  }
    +  {
    +    const jwe = JWE.encrypt(crypto.randomBytes(1001), k, { alg: 'dir', enc: 'A128CBC-HS256', zip: 'DEF' })
    +    t.throws(() => {
    +      JWE.decrypt(jwe, k, { inflateRawSyncLimit: 1000 })
    +    })
    +  }
    +})
    
  • types/index.d.ts+1 0 modified
    @@ -402,6 +402,7 @@ export namespace JWE {
         contentEncryptionAlgorithms?: string[];
         keyManagementAlgorithms?: string[];
         maxPBES2Count?: number;
    +    inflateRawSyncLimit?: number;
       }
     
       interface completeDecrypt {
    
1b91d88d2f82

fix: add a maxOutputLength option to zlib inflate

https://github.com/panva/joseFilip SkokanMar 7, 2024via ghsa
4 files changed · +121 1
  • docs/classes/util_errors.JWEDecompressionFailed.md+82 0 added
    @@ -0,0 +1,82 @@
    +# Class: JWEDecompressionFailed
    +
    +## [💗 Help the project](https://github.com/sponsors/panva)
    +
    +Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).
    +
    +---
    +
    +An error subclass thrown when a JWE ciphertext decompression fails.
    +
    +**`Example`**
    +
    +Checking thrown error is this one using a stable error code
    +
    +```js
    +if (err.code === 'ERR_JWE_DECOMPRESSION_FAILED') {
    +  // ...
    +}
    +```
    +
    +**`Example`**
    +
    +Checking thrown error is this one using `instanceof`
    +
    +```js
    +if (err instanceof jose.errors.JWEDecompressionFailed) {
    +  // ...
    +}
    +```
    +
    +## Table of contents
    +
    +### Constructors
    +
    +- [constructor](util_errors.JWEDecompressionFailed.md#constructor)
    +
    +### Properties
    +
    +- [code](util_errors.JWEDecompressionFailed.md#code)
    +- [message](util_errors.JWEDecompressionFailed.md#message)
    +
    +### Accessors
    +
    +- [code](util_errors.JWEDecompressionFailed.md#code-1)
    +
    +## Constructors
    +
    +### constructor
    +
    +• **new JWEDecompressionFailed**(`message?`)
    +
    +#### Parameters
    +
    +| Name | Type |
    +| :------ | :------ |
    +| `message?` | `string` |
    +
    +## Properties
    +
    +### code
    +
    +• **code**: `string` = `'ERR_JWE_DECOMPRESSION_FAILED'`
    +
    +A unique error code for the particular error subclass.
    +
    +___
    +
    +### message
    +
    +• **message**: `string` = `'decompression operation failed'`
    +
    +## Accessors
    +
    +### code
    +
    +• `Static` `get` **code**(): ``"ERR_JWE_DECOMPRESSION_FAILED"``
    +
    +A unique error code for the particular error subclass.
    +
    +#### Returns
    +
    +``"ERR_JWE_DECOMPRESSION_FAILED"``
    
  • docs/modules/util_errors.md+1 0 modified
    @@ -13,6 +13,7 @@ Support from the community to continue maintaining and improving this module is
     - [JOSEAlgNotAllowed](../classes/util_errors.JOSEAlgNotAllowed.md)
     - [JOSEError](../classes/util_errors.JOSEError.md)
     - [JOSENotSupported](../classes/util_errors.JOSENotSupported.md)
    +- [JWEDecompressionFailed](../classes/util_errors.JWEDecompressionFailed.md)
     - [JWEDecryptionFailed](../classes/util_errors.JWEDecryptionFailed.md)
     - [JWEInvalid](../classes/util_errors.JWEInvalid.md)
     - [JWKInvalid](../classes/util_errors.JWKInvalid.md)
    
  • src/runtime/node/zlib.ts+2 1 modified
    @@ -6,5 +6,6 @@ import type { InflateFunction, DeflateFunction } from '../../types.d'
     const inflateRaw = promisify(inflateRawCb)
     const deflateRaw = promisify(deflateRawCb)
     
    -export const inflate: InflateFunction = (input: Uint8Array) => inflateRaw(input)
    +export const inflate: InflateFunction = (input: Uint8Array) =>
    +  inflateRaw(input, { maxOutputLength: 250_000 })
     export const deflate: DeflateFunction = (input: Uint8Array) => deflateRaw(input)
    
  • test/jwe/flattened.decrypt.test.mjs+36 0 modified
    @@ -1,5 +1,7 @@
     import test from 'ava'
     import * as crypto from 'crypto'
    +import { promisify } from 'node:util'
    +import { inflateRaw as inflateRawCb } from 'node:zlib'
     
     const { FlattenedEncrypt, flattenedDecrypt } = await import('#dist')
     
    @@ -228,3 +230,37 @@ test('decrypt PBES2 p2c limit', async (t) => {
         code: 'ERR_JWE_INVALID',
       })
     })
    +
    +test('decrypt inflate output length limit', async (t) => {
    +  {
    +    const jwe = await new FlattenedEncrypt(new Uint8Array(250000))
    +      .setProtectedHeader({ alg: 'dir', enc: 'A128CBC-HS256', zip: 'DEF' })
    +      .encrypt(new Uint8Array(32))
    +
    +    await flattenedDecrypt(jwe, new Uint8Array(32))
    +  }
    +
    +  {
    +    const jwe = await new FlattenedEncrypt(new Uint8Array(250000 + 1))
    +      .setProtectedHeader({ alg: 'dir', enc: 'A128CBC-HS256', zip: 'DEF' })
    +      .encrypt(new Uint8Array(32))
    +
    +    await t.throwsAsync(flattenedDecrypt(jwe, new Uint8Array(32)), {
    +      message: 'decompression operation failed',
    +      code: 'ERR_JWE_DECOMPRESSION_FAILED',
    +    })
    +  }
    +
    +  {
    +    const jwe = await new FlattenedEncrypt(new Uint8Array(1000 + 1))
    +      .setProtectedHeader({ alg: 'dir', enc: 'A128CBC-HS256', zip: 'DEF' })
    +      .encrypt(new Uint8Array(32))
    +
    +    const inflateRawPromise = promisify(inflateRawCb)
    +    const inflateRaw = async (buffer) => inflateRawPromise(buffer, { maxOutputLength: 1000 })
    +
    +    await t.throwsAsync(flattenedDecrypt(jwe, new Uint8Array(32), { inflateRaw }), {
    +      code: 'ERR_BUFFER_TOO_LARGE',
    +    })
    +  }
    +})
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

15

News mentions

0

No linked articles in our index yet.