Search

Find a vulnerability

Search criteria ⓘ Use this form to refine search results.
Full-text search supports keyword queries with ranking and filtering.
You can combine vendor, product, and sources to narrow results.
Enable “Apply ordering” to sort by date instead of relevance.

    344 vulnerabilities

    CVE-2026-93471 (GCVE-0-2026-93471)

    Vulnerability from cvelistv5 – Published: 2026-10-22 06:59 – Updated: 2026-10-22 06:59
    VLAI
    Title
    Unbounded SAML message decompression in esaml allows memory exhaustion
    Summary
    Improper Handling of Highly Compressed Data (Data Amplification) in esaml_binding in dropbox esaml allows an unauthenticated attacker to exhaust server memory and crash the Erlang VM via a crafted compressed SAMLResponse. esaml_binding:decode_response/2 base64-decodes the attacker-supplied SAMLResponse, inflates it with zlib:unzip/1 with no bound on the output, then converts the result to a charlist with binary_to_list/1 before parsing. This runs on the SAML consume (ACS) and single-logout endpoints, which accept unauthenticated requests by design, and it runs before any signature check. A small deflate stream reaches a compression ratio near 1000 to 1, so a request body of tens of kilobytes inflates to hundreds of megabytes and then to several gigabytes of list heap, so a few requests exhaust memory and take the node down. This issue affects esaml: from 0.1.0 onward.
    CWE
    • CWE-409 - Improper Handling of Highly Compressed Data (Data Amplification)
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    handnot2 esaml Affected: deeb84378d5c82153b470e99d24f77c38afca14a , < * (git)
        cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    arekinath esaml Affected: deeb84378d5c82153b470e99d24f77c38afca14a , < * (git)
        cpe:2.3:a:arekinath:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    dropbox esaml Affected: 0.1.0 , < * (semver)
        cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    dropbox esaml Affected: deeb84378d5c82153b470e99d24f77c38afca14a , < * (git)
        cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "cna": {
          "affected": [
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "handnot2/esaml",
              "packageURL": "pkg:github/handnot2/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/handnot2/esaml",
              "vendor": "handnot2",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "deeb84378d5c82153b470e99d24f77c38afca14a",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:arekinath:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "arekinath/esaml",
              "packageURL": "pkg:github/arekinath/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/arekinath/esaml",
              "vendor": "arekinath",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "deeb84378d5c82153b470e99d24f77c38afca14a",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "esaml",
              "packageURL": "pkg:hex/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/dropbox/esaml",
              "vendor": "dropbox",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "dropbox/esaml",
              "packageURL": "pkg:github/dropbox/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/dropbox/esaml",
              "vendor": "dropbox",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "deeb84378d5c82153b470e99d24f77c38afca14a",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "2.0.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "reporter",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-06T10:25:40.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Handling of Highly Compressed Data (Data Amplification) in \u003ccode\u003eesaml_binding\u003c/code\u003e in dropbox esaml allows an unauthenticated attacker to exhaust server memory and crash the Erlang VM via a crafted compressed \u003ccode\u003eSAMLResponse\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e base64-decodes the attacker-supplied \u003ccode\u003eSAMLResponse\u003c/code\u003e, inflates it with \u003ccode\u003ezlib:unzip/1\u003c/code\u003e with no bound on the output, then converts the result to a charlist with \u003ccode\u003ebinary_to_list/1\u003c/code\u003e before parsing. This runs on the SAML consume (ACS) and single-logout endpoints, which accept unauthenticated requests by design, and it runs before any signature check. A small deflate stream reaches a compression ratio near 1000 to 1, so a request body of tens of kilobytes inflates to hundreds of megabytes and then to several gigabytes of list heap, so a few requests exhaust memory and take the node down.\u003c/p\u003e\n\u003cp\u003eThis issue affects esaml: from 0.1.0 onward.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Handling of Highly Compressed Data (Data Amplification) in `esaml_binding` in dropbox esaml allows an unauthenticated attacker to exhaust server memory and crash the Erlang VM via a crafted compressed `SAMLResponse`.\n\n`esaml_binding:decode_response/2` base64-decodes the attacker-supplied `SAMLResponse`, inflates it with `zlib:unzip/1` with no bound on the output, then converts the result to a charlist with `binary_to_list/1` before parsing. This runs on the SAML consume (ACS) and single-logout endpoints, which accept unauthenticated requests by design, and it runs before any signature check. A small deflate stream reaches a compression ratio near 1000 to 1, so a request body of tens of kilobytes inflates to hundreds of megabytes and then to several gigabytes of list heap, so a few requests exhaust memory and take the node down.\n\nThis issue affects esaml: from 0.1.0 onward."
                }
              ],
              "value": "Improper Handling of Highly Compressed Data (Data Amplification) in esaml_binding in dropbox esaml allows an unauthenticated attacker to exhaust server memory and crash the Erlang VM via a crafted compressed SAMLResponse.\n\nesaml_binding:decode_response/2 base64-decodes the attacker-supplied SAMLResponse, inflates it with zlib:unzip/1 with no bound on the output, then converts the result to a charlist with binary_to_list/1 before parsing. This runs on the SAML consume (ACS) and single-logout endpoints, which accept unauthenticated requests by design, and it runs before any signature check. A small deflate stream reaches a compression ratio near 1000 to 1, so a request body of tens of kilobytes inflates to hundreds of megabytes and then to several gigabytes of list heap, so a few requests exhaust memory and take the node down.\n\nThis issue affects esaml: from 0.1.0 onward."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-231",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn unauthenticated attacker reaches a SAML consume or single-logout endpoint and posts a small compressed \u003ccode\u003eSAMLResponse\u003c/code\u003e that inflates to gigabytes, exhausting memory. A few requests crash the Erlang VM and deny service to every user of the service provider.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An unauthenticated attacker reaches a SAML consume or single-logout endpoint and posts a small compressed `SAMLResponse` that inflates to gigabytes, exhausting memory. A few requests crash the Erlang VM and deny service to every user of the service provider."
                    }
                  ],
                  "value": "An unauthenticated attacker reaches a SAML consume or single-logout endpoint and posts a small compressed SAMLResponse that inflates to gigabytes, exhausting memory. A few requests crash the Erlang VM and deny service to every user of the service provider."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "NONE",
                "attackVector": "NETWORK",
                "baseScore": 8.7,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-409",
                  "description": "CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-22T06:59:24.029Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "EEF CNA record for CVE-2026-93471",
              "tags": [
                "related",
                "third-party-advisory"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-93471.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-93471",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-93471"
            },
            {
              "name": "Introducing commit deeb843 in handnot2/esaml",
              "tags": [
                "related"
              ],
              "url": "https://github.com/handnot2/esaml/commit/deeb84378d5c82153b470e99d24f77c38afca14a"
            },
            {
              "name": "Introducing commit deeb843 in arekinath/esaml",
              "tags": [
                "related"
              ],
              "url": "https://github.com/arekinath/esaml/commit/deeb84378d5c82153b470e99d24f77c38afca14a"
            },
            {
              "name": "Introducing commit deeb843 in dropbox/esaml",
              "tags": [
                "related",
                "broken-link"
              ],
              "url": "https://github.com/dropbox/esaml/commit/deeb84378d5c82153b470e99d24f77c38afca14a"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Unbounded SAML message decompression in esaml allows memory exhaustion",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eBuild a \u003ccode\u003eSAMLResponse\u003c/code\u003e as the base64 of a raw deflate stream of a long run of identical bytes (for example 60 MB of zero bytes), which encodes to roughly 79 KB on the wire.\u003c/li\u003e\n\u003cli\u003ePost it to the service provider\u0027s SAML consume (ACS) endpoint as the \u003ccode\u003eSAMLResponse\u003c/code\u003e form field, with \u003ccode\u003eSAMLEncoding\u003c/code\u003e set to the DEFLATE URI, or omitted to take the default clause.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e inflates the stream and converts it to a charlist before any signature check. A single request spikes the Erlang VM by several gigabytes. Repeat to exhaust memory and crash the node.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Build a `SAMLResponse` as the base64 of a raw deflate stream of a long run of identical bytes (for example 60 MB of zero bytes), which encodes to roughly 79 KB on the wire.\n2. Post it to the service provider\u0027s SAML consume (ACS) endpoint as the `SAMLResponse` form field, with `SAMLEncoding` set to the DEFLATE URI, or omitted to take the default clause.\n3. `esaml_binding:decode_response/2` inflates the stream and converts it to a charlist before any signature check. A single request spikes the Erlang VM by several gigabytes. Repeat to exhaust memory and crash the node."
                }
              ],
              "value": "* Build a SAMLResponse as the base64 of a raw deflate stream of a long run of identical bytes (for example 60 MB of zero bytes), which encodes to roughly 79 KB on the wire.\n* Post it to the service provider\u0027s SAML consume (ACS) endpoint as the SAMLResponse form field, with SAMLEncoding set to the DEFLATE URI, or omitted to take the default clause.\n* esaml_binding:decode_response/2 inflates the stream and converts it to a charlist before any signature check. A single request spikes the Erlang VM by several gigabytes. Repeat to exhaust memory and crash the node."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Input.\u003c/strong\u003e The SAML consume (ACS) and single-logout endpoints read the request body and pass the \u003ccode\u003eSAMLResponse\u003c/code\u003e form field to \u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e. The attacker\u0027s \u003ccode\u003eSAMLEncoding\u003c/code\u003e field selects the clause. Both clauses inflate: the explicit DEFLATE clause, and the default clause, which tries \u003ccode\u003ezlib:unzip/1\u003c/code\u003e inside a \u003ccode\u003ecatch\u003c/code\u003e whenever the body is not plain XML.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unbounded inflate.\u003c/strong\u003e \u003ccode\u003edecode_response/2\u003c/code\u003e evaluates \u003ccode\u003ebinary_to_list(zlib:unzip(base64:decode(SAMLResponse)))\u003c/code\u003e. \u003ccode\u003ezlib:unzip/1\u003c/code\u003e inflates the whole stream in one call with no cap on output size, and \u003ccode\u003ebinary_to_list/1\u003c/code\u003e then expands the inflated binary to a charlist, which costs about 16 bytes of heap per byte on a 64-bit VM.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Exhaustion before any check.\u003c/strong\u003e All of this happens before \u003ccode\u003exmerl_scan\u003c/code\u003e parses the document and before \u003ccode\u003eesaml_sp\u003c/code\u003e verifies any signature. A deflate stream of a long run of identical bytes reaches a ratio near 1000 to 1, so a request body of tens of kilobytes produces hundreds of megabytes of binary and then several gigabytes of list heap. A few concurrent requests exhaust memory and crash the node.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Input.** The SAML consume (ACS) and single-logout endpoints read the request body and pass the `SAMLResponse` form field to `esaml_binding:decode_response/2`. The attacker\u0027s `SAMLEncoding` field selects the clause. Both clauses inflate: the explicit DEFLATE clause, and the default clause, which tries `zlib:unzip/1` inside a `catch` whenever the body is not plain XML.\n\n**2. Unbounded inflate.** `decode_response/2` evaluates `binary_to_list(zlib:unzip(base64:decode(SAMLResponse)))`. `zlib:unzip/1` inflates the whole stream in one call with no cap on output size, and `binary_to_list/1` then expands the inflated binary to a charlist, which costs about 16 bytes of heap per byte on a 64-bit VM.\n\n**3. Exhaustion before any check.** All of this happens before `xmerl_scan` parses the document and before `esaml_sp` verifies any signature. A deflate stream of a long run of identical bytes reaches a ratio near 1000 to 1, so a request body of tens of kilobytes produces hundreds of megabytes of binary and then several gigabytes of list heap. A few concurrent requests exhaust memory and crash the node."
                }
              ],
              "value": "1. Input. The SAML consume (ACS) and single-logout endpoints read the request body and pass the SAMLResponse form field to esaml_binding:decode_response/2. The attacker\u0027s SAMLEncoding field selects the clause. Both clauses inflate: the explicit DEFLATE clause, and the default clause, which tries zlib:unzip/1 inside a catch whenever the body is not plain XML.\n\n2. Unbounded inflate. decode_response/2 evaluates binary_to_list(zlib:unzip(base64:decode(SAMLResponse))). zlib:unzip/1 inflates the whole stream in one call with no cap on output size, and binary_to_list/1 then expands the inflated binary to a charlist, which costs about 16 bytes of heap per byte on a 64-bit VM.\n\n3. Exhaustion before any check. All of this happens before xmerl_scan parses the document and before esaml_sp verifies any signature. A deflate stream of a long run of identical bytes reaches a ratio near 1000 to 1, so a request body of tens of kilobytes produces hundreds of megabytes of binary and then several gigabytes of list heap. A few concurrent requests exhaust memory and crash the node."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-93471",
        "datePublished": "2026-10-22T06:59:24.029Z",
        "dateReserved": "2026-10-06T08:45:01.518Z",
        "dateUpdated": "2026-10-22T06:59:24.029Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-86696 (GCVE-0-2026-86696)

    Vulnerability from cvelistv5 – Published: 2026-10-22 06:59 – Updated: 2026-10-22 06:59
    VLAI
    Title
    Unbounded atom creation from unsigned LogoutResponse status codes in esaml allows atom table exhaustion
    Summary
    Allocation of Resources Without Limits or Throttling vulnerability in esaml in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via unsigned LogoutResponse messages. esaml:status_code_map/1 in src/esaml.erl maps any StatusCode value it does not recognize to an atom with list_to_atom/1 on the last colon-separated segment of the URN. esaml_sp:validate_logout_response/2 verifies a signature only when the message carries one, so an unsigned LogoutResponse posted to the single-logout endpoint reaches esaml:decode_logout_response/1 and mints one permanent atom per distinct status value. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so an attacker who posts that many distinct values aborts the node. This issue affects esaml: from 0.1.0 onward.
    CWE
    • CWE-770 - Allocation of Resources Without Limits or Throttling
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    handnot2 esaml Affected: be692b56245071899757afa7ef47e43593d3de96 , < * (git)
        cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    arekinath esaml Affected: be692b56245071899757afa7ef47e43593d3de96 , < * (git)
        cpe:2.3:a:arekinath:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    dropbox esaml Affected: 0.1.0 , < * (semver)
        cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    dropbox esaml Affected: be692b56245071899757afa7ef47e43593d3de96 , < * (git)
        cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "cna": {
          "affected": [
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml",
                "esaml_sp",
                "esaml_cowboy"
              ],
              "packageName": "handnot2/esaml",
              "packageURL": "pkg:github/handnot2/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml.erl",
                "src/esaml_sp.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml:status_code_map/1"
                },
                {
                  "name": "esaml:decode_logout_response/1"
                },
                {
                  "name": "esaml_sp:validate_logout_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/handnot2/esaml",
              "vendor": "handnot2",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "be692b56245071899757afa7ef47e43593d3de96",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:arekinath:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml",
                "esaml_sp",
                "esaml_cowboy"
              ],
              "packageName": "arekinath/esaml",
              "packageURL": "pkg:github/arekinath/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml.erl",
                "src/esaml_sp.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml:status_code_map/1"
                },
                {
                  "name": "esaml:decode_logout_response/1"
                },
                {
                  "name": "esaml_sp:validate_logout_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/arekinath/esaml",
              "vendor": "arekinath",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "be692b56245071899757afa7ef47e43593d3de96",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml",
                "esaml_sp",
                "esaml_cowboy"
              ],
              "packageName": "esaml",
              "packageURL": "pkg:hex/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml.erl",
                "src/esaml_sp.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml:status_code_map/1"
                },
                {
                  "name": "esaml:decode_logout_response/1"
                },
                {
                  "name": "esaml_sp:validate_logout_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/dropbox/esaml",
              "vendor": "dropbox",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml",
                "esaml_sp",
                "esaml_cowboy"
              ],
              "packageName": "dropbox/esaml",
              "packageURL": "pkg:github/dropbox/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml.erl",
                "src/esaml_sp.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml:status_code_map/1"
                },
                {
                  "name": "esaml:decode_logout_response/1"
                },
                {
                  "name": "esaml_sp:validate_logout_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/dropbox/esaml",
              "vendor": "dropbox",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "be692b56245071899757afa7ef47e43593d3de96",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReachable only where the service provider exposes a single-logout endpoint that hands incoming \u003ccode\u003eLogoutResponse\u003c/code\u003e messages to \u003ccode\u003eesaml_sp:validate_logout_response/2\u003c/code\u003e, directly or through \u003ccode\u003eesaml_cowboy:validate_logout/2\u003c/code\u003e. The consume (ACS) path verifies signatures before decoding and does not reach the status code mapping.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reachable only where the service provider exposes a single-logout endpoint that hands incoming `LogoutResponse` messages to `esaml_sp:validate_logout_response/2`, directly or through `esaml_cowboy:validate_logout/2`. The consume (ACS) path verifies signatures before decoding and does not reach the status code mapping."
                }
              ],
              "value": "Reachable only where the service provider exposes a single-logout endpoint that hands incoming LogoutResponse messages to esaml_sp:validate_logout_response/2, directly or through esaml_cowboy:validate_logout/2. The consume (ACS) path verifies signatures before decoding and does not reach the status code mapping."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "2.0.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-08T14:39:37.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAllocation of Resources Without Limits or Throttling vulnerability in \u003ccode\u003eesaml\u003c/code\u003e in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via unsigned \u003ccode\u003eLogoutResponse\u003c/code\u003e messages.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eesaml:status_code_map/1\u003c/code\u003e in \u003ccode\u003esrc/esaml.erl\u003c/code\u003e maps any \u003ccode\u003eStatusCode\u003c/code\u003e value it does not recognize to an atom with \u003ccode\u003elist_to_atom/1\u003c/code\u003e on the last colon-separated segment of the URN. \u003ccode\u003eesaml_sp:validate_logout_response/2\u003c/code\u003e verifies a signature only when the message carries one, so an unsigned \u003ccode\u003eLogoutResponse\u003c/code\u003e posted to the single-logout endpoint reaches \u003ccode\u003eesaml:decode_logout_response/1\u003c/code\u003e and mints one permanent atom per distinct status value. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so an attacker who posts that many distinct values aborts the node.\u003c/p\u003e\n\u003cp\u003eThis issue affects esaml: from 0.1.0 onward.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Allocation of Resources Without Limits or Throttling vulnerability in `esaml` in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via unsigned `LogoutResponse` messages.\n\n`esaml:status_code_map/1` in `src/esaml.erl` maps any `StatusCode` value it does not recognize to an atom with `list_to_atom/1` on the last colon-separated segment of the URN. `esaml_sp:validate_logout_response/2` verifies a signature only when the message carries one, so an unsigned `LogoutResponse` posted to the single-logout endpoint reaches `esaml:decode_logout_response/1` and mints one permanent atom per distinct status value. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so an attacker who posts that many distinct values aborts the node.\n\nThis issue affects esaml: from 0.1.0 onward."
                }
              ],
              "value": "Allocation of Resources Without Limits or Throttling vulnerability in esaml in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via unsigned LogoutResponse messages.\n\nesaml:status_code_map/1 in src/esaml.erl maps any StatusCode value it does not recognize to an atom with list_to_atom/1 on the last colon-separated segment of the URN. esaml_sp:validate_logout_response/2 verifies a signature only when the message carries one, so an unsigned LogoutResponse posted to the single-logout endpoint reaches esaml:decode_logout_response/1 and mints one permanent atom per distinct status value. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so an attacker who posts that many distinct values aborts the node.\n\nThis issue affects esaml: from 0.1.0 onward."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-125",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn unauthenticated attacker posts unsigned \u003ccode\u003eLogoutResponse\u003c/code\u003e messages with distinct status code URNs to a service provider\u0027s single-logout endpoint. Each message permanently consumes one atom table entry, and about one million of them abort the Erlang VM, denying service to every user of the service provider.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An unauthenticated attacker posts unsigned `LogoutResponse` messages with distinct status code URNs to a service provider\u0027s single-logout endpoint. Each message permanently consumes one atom table entry, and about one million of them abort the Erlang VM, denying service to every user of the service provider."
                    }
                  ],
                  "value": "An unauthenticated attacker posts unsigned LogoutResponse messages with distinct status code URNs to a service provider\u0027s single-logout endpoint. Each message permanently consumes one atom table entry, and about one million of them abort the Erlang VM, denying service to every user of the service provider."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 8.2,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-770",
                  "description": "CWE-770 Allocation of Resources Without Limits or Throttling",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-22T06:59:07.680Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "EEF CNA record for CVE-2026-86696",
              "tags": [
                "related",
                "third-party-advisory"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-86696.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-86696",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-86696"
            },
            {
              "name": "Introducing commit be692b5 in handnot2/esaml",
              "tags": [
                "related"
              ],
              "url": "https://github.com/handnot2/esaml/commit/be692b56245071899757afa7ef47e43593d3de96"
            },
            {
              "name": "Introducing commit be692b5 in arekinath/esaml",
              "tags": [
                "related"
              ],
              "url": "https://github.com/arekinath/esaml/commit/be692b56245071899757afa7ef47e43593d3de96"
            },
            {
              "name": "Introducing commit be692b5 in dropbox/esaml",
              "tags": [
                "related",
                "broken-link"
              ],
              "url": "https://github.com/dropbox/esaml/commit/be692b56245071899757afa7ef47e43593d3de96"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Unbounded atom creation from unsigned LogoutResponse status codes in esaml allows atom table exhaustion",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eBuild an unsigned \u003ccode\u003esamlp:LogoutResponse\u003c/code\u003e document with \u003ccode\u003eVersion=\"2.0\"\u003c/code\u003e, any \u003ccode\u003eIssueInstant\u003c/code\u003e, and a \u003ccode\u003esamlp:Status/samlp:StatusCode\u003c/code\u003e whose \u003ccode\u003eValue\u003c/code\u003e is a fresh URN such as \u003ccode\u003eurn:x:\u0026lt;unique token\u0026gt;\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eDeflate and base64-encode it, and post it to the service provider\u0027s single-logout endpoint as the \u003ccode\u003eSAMLResponse\u003c/code\u003e form field with \u003ccode\u003eSAMLEncoding\u003c/code\u003e set to the DEFLATE URI.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eesaml_sp:validate_logout_response/2\u003c/code\u003e skips signature verification because none is present, \u003ccode\u003eesaml:decode_logout_response/1\u003c/code\u003e maps the status through \u003ccode\u003estatus_code_map/1\u003c/code\u003e, and the atom table grows by one entry per distinct token. The endpoint answers with an error, but the atom is never reclaimed. Repeat with fresh tokens until the VM aborts.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Build an unsigned `samlp:LogoutResponse` document with `Version=\"2.0\"`, any `IssueInstant`, and a `samlp:Status/samlp:StatusCode` whose `Value` is a fresh URN such as `urn:x:\u003cunique token\u003e`.\n2. Deflate and base64-encode it, and post it to the service provider\u0027s single-logout endpoint as the `SAMLResponse` form field with `SAMLEncoding` set to the DEFLATE URI.\n3. `esaml_sp:validate_logout_response/2` skips signature verification because none is present, `esaml:decode_logout_response/1` maps the status through `status_code_map/1`, and the atom table grows by one entry per distinct token. The endpoint answers with an error, but the atom is never reclaimed. Repeat with fresh tokens until the VM aborts."
                }
              ],
              "value": "* Build an unsigned samlp:LogoutResponse document with Version=\"2.0\", any IssueInstant, and a samlp:Status/samlp:StatusCode whose Value is a fresh URN such as urn:x:\u003cunique token\u003e.\n* Deflate and base64-encode it, and post it to the service provider\u0027s single-logout endpoint as the SAMLResponse form field with SAMLEncoding set to the DEFLATE URI.\n* esaml_sp:validate_logout_response/2 skips signature verification because none is present, esaml:decode_logout_response/1 maps the status through status_code_map/1, and the atom table grows by one entry per distinct token. The endpoint answers with an error, but the atom is never reclaimed. Repeat with fresh tokens until the VM aborts."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Input.\u003c/strong\u003e The single-logout endpoint in \u003ccode\u003eesaml_cowboy:validate_logout/2\u003c/code\u003e reads the request body, decodes the \u003ccode\u003eSAMLResponse\u003c/code\u003e form field with \u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e, and passes a \u003ccode\u003esamlp:LogoutResponse\u003c/code\u003e document to \u003ccode\u003eesaml_sp:validate_logout_response/2\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. No signature required.\u003c/strong\u003e \u003ccode\u003evalidate_logout_response/2\u003c/code\u003e looks for a \u003ccode\u003eds:Signature\u003c/code\u003e element and verifies it only when one is present. An unsigned document passes through unchanged to \u003ccode\u003eesaml:decode_logout_response/1\u003c/code\u003e, which requires only the \u003ccode\u003eVersion\u003c/code\u003e and \u003ccode\u003eIssueInstant\u003c/code\u003e attributes before mapping the \u003ccode\u003eStatusCode\u003c/code\u003e value.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Interning.\u003c/strong\u003e \u003ccode\u003eesaml:status_code_map/1\u003c/code\u003e matches six known SAML status URNs to fixed atoms. Any other value starting with \u003ccode\u003eurn:\u003c/code\u003e falls through to a clause that interns the last colon-separated segment with \u003ccode\u003elist_to_atom/1\u003c/code\u003e. The attacker chooses that segment, so each request with a fresh value mints one permanent atom. The VM aborts when the atom table reaches its limit of about one million entries.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Input.** The single-logout endpoint in `esaml_cowboy:validate_logout/2` reads the request body, decodes the `SAMLResponse` form field with `esaml_binding:decode_response/2`, and passes a `samlp:LogoutResponse` document to `esaml_sp:validate_logout_response/2`.\n\n**2. No signature required.** `validate_logout_response/2` looks for a `ds:Signature` element and verifies it only when one is present. An unsigned document passes through unchanged to `esaml:decode_logout_response/1`, which requires only the `Version` and `IssueInstant` attributes before mapping the `StatusCode` value.\n\n**3. Interning.** `esaml:status_code_map/1` matches six known SAML status URNs to fixed atoms. Any other value starting with `urn:` falls through to a clause that interns the last colon-separated segment with `list_to_atom/1`. The attacker chooses that segment, so each request with a fresh value mints one permanent atom. The VM aborts when the atom table reaches its limit of about one million entries."
                }
              ],
              "value": "1. Input. The single-logout endpoint in esaml_cowboy:validate_logout/2 reads the request body, decodes the SAMLResponse form field with esaml_binding:decode_response/2, and passes a samlp:LogoutResponse document to esaml_sp:validate_logout_response/2.\n\n2. No signature required. validate_logout_response/2 looks for a ds:Signature element and verifies it only when one is present. An unsigned document passes through unchanged to esaml:decode_logout_response/1, which requires only the Version and IssueInstant attributes before mapping the StatusCode value.\n\n3. Interning. esaml:status_code_map/1 matches six known SAML status URNs to fixed atoms. Any other value starting with urn: falls through to a clause that interns the last colon-separated segment with list_to_atom/1. The attacker chooses that segment, so each request with a fresh value mints one permanent atom. The VM aborts when the atom table reaches its limit of about one million entries."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-86696",
        "datePublished": "2026-10-22T06:59:07.680Z",
        "dateReserved": "2026-10-08T13:45:02.138Z",
        "dateUpdated": "2026-10-22T06:59:07.680Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105270 (GCVE-0-2026-105270)

    Vulnerability from cvelistv5 – Published: 2026-10-22 06:58 – Updated: 2026-10-22 06:58
    VLAI
    Title
    Unbounded atom creation while parsing SAML messages in esaml allows atom table exhaustion
    Summary
    Allocation of Resources Without Limits or Throttling vulnerability in esaml_binding in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via a crafted SAMLResponse. esaml_binding:decode_response/2 parses the decoded SAMLResponse with xmerl_scan:string/2, which interns every element name, attribute name, and namespace prefix it reads as a permanent atom. The SAML consume (ACS) and single-logout endpoints call it on unauthenticated requests before any signature check, so one well-formed document with thousands of distinct names mints thousands of atoms while staying inside the 128 KB request body limit. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so a few dozen requests abort the node. This issue affects esaml: from 0.1.0 onward.
    CWE
    • CWE-770 - Allocation of Resources Without Limits or Throttling
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    arekinath esaml Affected: deeb84378d5c82153b470e99d24f77c38afca14a , < * (git)
        cpe:2.3:a:arekinath:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    handnot2 esaml Affected: deeb84378d5c82153b470e99d24f77c38afca14a , < * (git)
        cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    dropbox esaml Affected: 0.1.0 , < * (semver)
        cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    dropbox esaml Affected: deeb84378d5c82153b470e99d24f77c38afca14a , < * (git)
        cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "cna": {
          "affected": [
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:arekinath:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "arekinath/esaml",
              "packageURL": "pkg:github/arekinath/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/arekinath/esaml",
              "vendor": "arekinath",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "deeb84378d5c82153b470e99d24f77c38afca14a",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "handnot2/esaml",
              "packageURL": "pkg:github/handnot2/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/handnot2/esaml",
              "vendor": "handnot2",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "deeb84378d5c82153b470e99d24f77c38afca14a",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "esaml",
              "packageURL": "pkg:hex/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/dropbox/esaml",
              "vendor": "dropbox",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "esaml_binding",
                "esaml_cowboy"
              ],
              "packageName": "dropbox/esaml",
              "packageURL": "pkg:github/dropbox/esaml",
              "product": "esaml",
              "programFiles": [
                "src/esaml_binding.erl",
                "src/esaml_cowboy.erl"
              ],
              "programRoutines": [
                {
                  "name": "esaml_binding:decode_response/2"
                },
                {
                  "name": "esaml_cowboy:validate_assertion/2"
                },
                {
                  "name": "esaml_cowboy:validate_logout/2"
                }
              ],
              "repo": "https://github.com/dropbox/esaml",
              "vendor": "dropbox",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "deeb84378d5c82153b470e99d24f77c38afca14a",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:handnot2:esaml:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "2.0.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:dropbox:esaml:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-08T14:44:30.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAllocation of Resources Without Limits or Throttling vulnerability in \u003ccode\u003eesaml_binding\u003c/code\u003e in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via a crafted \u003ccode\u003eSAMLResponse\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e parses the decoded \u003ccode\u003eSAMLResponse\u003c/code\u003e with \u003ccode\u003exmerl_scan:string/2\u003c/code\u003e, which interns every element name, attribute name, and namespace prefix it reads as a permanent atom. The SAML consume (ACS) and single-logout endpoints call it on unauthenticated requests before any signature check, so one well-formed document with thousands of distinct names mints thousands of atoms while staying inside the 128 KB request body limit. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so a few dozen requests abort the node.\u003c/p\u003e\n\u003cp\u003eThis issue affects esaml: from 0.1.0 onward.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Allocation of Resources Without Limits or Throttling vulnerability in `esaml_binding` in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via a crafted `SAMLResponse`.\n\n`esaml_binding:decode_response/2` parses the decoded `SAMLResponse` with `xmerl_scan:string/2`, which interns every element name, attribute name, and namespace prefix it reads as a permanent atom. The SAML consume (ACS) and single-logout endpoints call it on unauthenticated requests before any signature check, so one well-formed document with thousands of distinct names mints thousands of atoms while staying inside the 128 KB request body limit. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so a few dozen requests abort the node.\n\nThis issue affects esaml: from 0.1.0 onward."
                }
              ],
              "value": "Allocation of Resources Without Limits or Throttling vulnerability in esaml_binding in dropbox esaml allows an unauthenticated attacker to exhaust the Erlang VM atom table and crash the node via a crafted SAMLResponse.\n\nesaml_binding:decode_response/2 parses the decoded SAMLResponse with xmerl_scan:string/2, which interns every element name, attribute name, and namespace prefix it reads as a permanent atom. The SAML consume (ACS) and single-logout endpoints call it on unauthenticated requests before any signature check, so one well-formed document with thousands of distinct names mints thousands of atoms while staying inside the 128 KB request body limit. Atoms are never reclaimed and the VM caps the atom table at about one million entries, so a few dozen requests abort the node.\n\nThis issue affects esaml: from 0.1.0 onward."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn unauthenticated attacker posts a small SAML document with thousands of distinct element and attribute names to a service provider\u0027s consume or single-logout endpoint. Each request permanently consumes thousands of atom table entries, and a few dozen requests abort the Erlang VM, denying service to every user of the service provider.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An unauthenticated attacker posts a small SAML document with thousands of distinct element and attribute names to a service provider\u0027s consume or single-logout endpoint. Each request permanently consumes thousands of atom table entries, and a few dozen requests abort the Erlang VM, denying service to every user of the service provider."
                    }
                  ],
                  "value": "An unauthenticated attacker posts a small SAML document with thousands of distinct element and attribute names to a service provider\u0027s consume or single-logout endpoint. Each request permanently consumes thousands of atom table entries, and a few dozen requests abort the Erlang VM, denying service to every user of the service provider."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "NONE",
                "attackVector": "NETWORK",
                "baseScore": 8.7,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-770",
                  "description": "CWE-770 Allocation of Resources Without Limits or Throttling",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-22T06:58:43.034Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "EEF CNA record for CVE-2026-105270",
              "tags": [
                "related",
                "third-party-advisory"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105270.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105270",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105270"
            },
            {
              "name": "Introducing commit deeb843 in arekinath/esaml",
              "tags": [
                "related"
              ],
              "url": "https://github.com/arekinath/esaml/commit/deeb84378d5c82153b470e99d24f77c38afca14a"
            },
            {
              "name": "Introducing commit deeb843 in handnot2/esaml",
              "tags": [
                "related"
              ],
              "url": "https://github.com/handnot2/esaml/commit/deeb84378d5c82153b470e99d24f77c38afca14a"
            },
            {
              "name": "Introducing commit deeb843 in dropbox/esaml",
              "tags": [
                "related",
                "broken-link"
              ],
              "url": "https://github.com/dropbox/esaml/commit/deeb84378d5c82153b470e99d24f77c38afca14a"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Unbounded atom creation while parsing SAML messages in esaml allows atom table exhaustion",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eBuild a well-formed XML document whose root contains several thousand empty elements with distinct names, each carrying a distinctly named attribute, for example \u003ccode\u003e\u0026lt;p0 a0=\"x\"/\u0026gt;\u003c/code\u003e through \u003ccode\u003e\u0026lt;p4999 a4999=\"x\"/\u0026gt;\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eDeflate and base64-encode it, and post it to the service provider\u0027s SAML consume (ACS) or single-logout endpoint as the \u003ccode\u003eSAMLResponse\u003c/code\u003e form field with \u003ccode\u003eSAMLEncoding\u003c/code\u003e set to the DEFLATE URI. A document with 5,000 elements and 5,000 attributes encodes to about 33 KB.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e parses it with \u003ccode\u003exmerl_scan:string/2\u003c/code\u003e before any signature check, and the atom table grows by 10,000 entries that are never reclaimed. Repeat with fresh names until the VM aborts.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Build a well-formed XML document whose root contains several thousand empty elements with distinct names, each carrying a distinctly named attribute, for example `\u003cp0 a0=\"x\"/\u003e` through `\u003cp4999 a4999=\"x\"/\u003e`.\n2. Deflate and base64-encode it, and post it to the service provider\u0027s SAML consume (ACS) or single-logout endpoint as the `SAMLResponse` form field with `SAMLEncoding` set to the DEFLATE URI. A document with 5,000 elements and 5,000 attributes encodes to about 33 KB.\n3. `esaml_binding:decode_response/2` parses it with `xmerl_scan:string/2` before any signature check, and the atom table grows by 10,000 entries that are never reclaimed. Repeat with fresh names until the VM aborts."
                }
              ],
              "value": "* Build a well-formed XML document whose root contains several thousand empty elements with distinct names, each carrying a distinctly named attribute, for example \u003cp0 a0=\"x\"/\u003e through \u003cp4999 a4999=\"x\"/\u003e.\n* Deflate and base64-encode it, and post it to the service provider\u0027s SAML consume (ACS) or single-logout endpoint as the SAMLResponse form field with SAMLEncoding set to the DEFLATE URI. A document with 5,000 elements and 5,000 attributes encodes to about 33 KB.\n* esaml_binding:decode_response/2 parses it with xmerl_scan:string/2 before any signature check, and the atom table grows by 10,000 entries that are never reclaimed. Repeat with fresh names until the VM aborts."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Input.\u003c/strong\u003e The SAML consume (ACS) and single-logout endpoints in \u003ccode\u003eesaml_cowboy\u003c/code\u003e read the request body, capped at 128 KB, and pass the \u003ccode\u003eSAMLResponse\u003c/code\u003e form field to \u003ccode\u003eesaml_binding:decode_response/2\u003c/code\u003e. Both the DEFLATE clause and the default clause end in \u003ccode\u003exmerl_scan:string/2\u003c/code\u003e with \u003ccode\u003enamespace_conformant\u003c/code\u003e set.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Interning.\u003c/strong\u003e \u003ccode\u003exmerl_scan\u003c/code\u003e converts every element name, attribute name, and namespace prefix it tokenizes into an atom with \u003ccode\u003elist_to_atom/1\u003c/code\u003e, and the same happens for each namespace local name in \u003ccode\u003eexpanded_name/4\u003c/code\u003e. A well-formed document with many distinct names therefore mints one permanent atom per name. Repetitive names deflate well, so a document with ten thousand distinct names fits in a few tens of kilobytes on the wire.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Exhaustion before any check.\u003c/strong\u003e Signature verification and assertion validation run on the parsed document afterward, so the atoms are minted whether or not the document is a valid or signed SAML message. Atoms are never garbage collected and the VM aborts when the atom table reaches its limit of about one million entries, so a few dozen requests take the node down.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Input.** The SAML consume (ACS) and single-logout endpoints in `esaml_cowboy` read the request body, capped at 128 KB, and pass the `SAMLResponse` form field to `esaml_binding:decode_response/2`. Both the DEFLATE clause and the default clause end in `xmerl_scan:string/2` with `namespace_conformant` set.\n\n**2. Interning.** `xmerl_scan` converts every element name, attribute name, and namespace prefix it tokenizes into an atom with `list_to_atom/1`, and the same happens for each namespace local name in `expanded_name/4`. A well-formed document with many distinct names therefore mints one permanent atom per name. Repetitive names deflate well, so a document with ten thousand distinct names fits in a few tens of kilobytes on the wire.\n\n**3. Exhaustion before any check.** Signature verification and assertion validation run on the parsed document afterward, so the atoms are minted whether or not the document is a valid or signed SAML message. Atoms are never garbage collected and the VM aborts when the atom table reaches its limit of about one million entries, so a few dozen requests take the node down."
                }
              ],
              "value": "1. Input. The SAML consume (ACS) and single-logout endpoints in esaml_cowboy read the request body, capped at 128 KB, and pass the SAMLResponse form field to esaml_binding:decode_response/2. Both the DEFLATE clause and the default clause end in xmerl_scan:string/2 with namespace_conformant set.\n\n2. Interning. xmerl_scan converts every element name, attribute name, and namespace prefix it tokenizes into an atom with list_to_atom/1, and the same happens for each namespace local name in expanded_name/4. A well-formed document with many distinct names therefore mints one permanent atom per name. Repetitive names deflate well, so a document with ten thousand distinct names fits in a few tens of kilobytes on the wire.\n\n3. Exhaustion before any check. Signature verification and assertion validation run on the parsed document afterward, so the atoms are minted whether or not the document is a valid or signed SAML message. Atoms are never garbage collected and the VM aborts when the atom table reaches its limit of about one million entries, so a few dozen requests take the node down."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105270",
        "datePublished": "2026-10-22T06:58:43.034Z",
        "dateReserved": "2026-10-07T22:15:01.882Z",
        "dateUpdated": "2026-10-22T06:58:43.034Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105660 (GCVE-0-2026-105660)

    Vulnerability from cvelistv5 – Published: 2026-10-15 18:37 – Updated: 2026-10-16 20:02
    VLAI
    Title
    Unvalidated HTTP/1 request-target authority lets a client choose conn.host and conn.port in Bandit
    Summary
    Improper Validation of Syntactic Correctness of Input vulnerability in mtrudel bandit allows an unauthenticated remote attacker to set conn.host and conn.port to values outside the RFC 3986 host grammar via the authority of an HTTP/1 absolute-form or authority-form request target. Bandit validates the Host header and answers 400 when it is malformed, but when the request target carries its own authority that authority takes precedence, as RFC 9112 requires, and Bandit.Pipeline.determine_host_and_port!/4 in lib/bandit/pipeline.ex passes it to the application unchecked. A request line such as GET http://good.example@evil.example/x HTTP/1.1 with a valid Host header yields a conn.host of good.example@evil.example, and the same path admits NUL, DEL and non-ASCII bytes, invalid IP literals and ports outside 1..65535. The HTTP/1.0 clause needs no Host header at all, and CONNECT places the unparsed port string in conn.port. Applications that build absolute URLs from conn.host, such as through Plug.Conn.request_url/1 or Phoenix URL helpers, emit links whose authority the attacker chose. Message framing is unaffected, and no CR or LF can reach conn.host. This issue affects bandit: from 0.6.0 before 1.13.0.
    SSVC
    Exploitation: poc Automatable: yes Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 20:02 UTC
    CWE
    • CWE-1286 - Improper Validation of Syntactic Correctness of Input
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    mtrudel bandit Affected: 0.6.0 , < 1.13.0 (semver)
        cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
    Create a notification for this product.
    mtrudel bandit Affected: 0e3edda0edf6322e9164b827c6d3ddecd080ff2e , < 6ea80210137b3f69caf65f67398934259dc365d0 (git)
        cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105660",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "yes"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T20:02:09.521673Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T20:02:38.784Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-v53v-9hvg-37jf"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Bandit.Pipeline\u0027",
                "\u0027Elixir.Bandit.HTTPTransport.Bandit.HTTP1.Socket\u0027"
              ],
              "packageName": "bandit",
              "packageURL": "pkg:hex/bandit",
              "product": "bandit",
              "programFiles": [
                "lib/bandit/pipeline.ex",
                "lib/bandit/http1/socket.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Bandit.Pipeline\u0027:determine_host_and_port!/4"
                },
                {
                  "name": "\u0027Elixir.Bandit.Pipeline\u0027:build_conn!/6"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTPTransport.Bandit.HTTP1.Socket\u0027:resolve_request_target!/2"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTPTransport.Bandit.HTTP1.Socket\u0027:validate_request_line!/2"
                }
              ],
              "repo": "https://github.com/mtrudel/bandit",
              "vendor": "mtrudel",
              "versions": [
                {
                  "lessThan": "1.13.0",
                  "status": "affected",
                  "version": "0.6.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Bandit.Pipeline\u0027",
                "\u0027Elixir.Bandit.HTTPTransport.Bandit.HTTP1.Socket\u0027"
              ],
              "packageName": "mtrudel/bandit",
              "packageURL": "pkg:github/mtrudel/bandit",
              "product": "bandit",
              "programFiles": [
                "lib/bandit/pipeline.ex",
                "lib/bandit/http1/socket.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Bandit.Pipeline\u0027:determine_host_and_port!/4"
                },
                {
                  "name": "\u0027Elixir.Bandit.Pipeline\u0027:build_conn!/6"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTPTransport.Bandit.HTTP1.Socket\u0027:resolve_request_target!/2"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTPTransport.Bandit.HTTP1.Socket\u0027:validate_request_line!/2"
                }
              ],
              "repo": "https://github.com/mtrudel/bandit",
              "vendor": "mtrudel",
              "versions": [
                {
                  "lessThan": "6ea80210137b3f69caf65f67398934259dc365d0",
                  "status": "affected",
                  "version": "0e3edda0edf6322e9164b827c6d3ddecd080ff2e",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.13.0",
                      "versionStartIncluding": "0.6.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Mat Trudel"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            },
            {
              "lang": "en",
              "type": "finder",
              "value": "Eurico Nicacio"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Eurico Nicacio"
            }
          ],
          "dateAssigned": "2026-10-14T21:42:16.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Validation of Syntactic Correctness of Input vulnerability in mtrudel bandit allows an unauthenticated remote attacker to set \u003ccode\u003econn.host\u003c/code\u003e and \u003ccode\u003econn.port\u003c/code\u003e to values outside the RFC 3986 host grammar via the authority of an HTTP/1 absolute-form or authority-form request target. Bandit validates the \u003ccode\u003eHost\u003c/code\u003e header and answers 400 when it is malformed, but when the request target carries its own authority that authority takes precedence, as RFC 9112 requires, and \u003ccode\u003eBandit.Pipeline.determine_host_and_port!/4\u003c/code\u003e in \u003ccode\u003elib/bandit/pipeline.ex\u003c/code\u003e passes it to the application unchecked. A request line such as \u003ccode\u003eGET http://good.example@evil.example/x HTTP/1.1\u003c/code\u003e with a valid \u003ccode\u003eHost\u003c/code\u003e header yields a \u003ccode\u003econn.host\u003c/code\u003e of \u003ccode\u003egood.example@evil.example\u003c/code\u003e, and the same path admits NUL, DEL and non-ASCII bytes, invalid IP literals and ports outside 1..65535. The HTTP/1.0 clause needs no \u003ccode\u003eHost\u003c/code\u003e header at all, and \u003ccode\u003eCONNECT\u003c/code\u003e places the unparsed port string in \u003ccode\u003econn.port\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eApplications that build absolute URLs from \u003ccode\u003econn.host\u003c/code\u003e, such as through \u003ccode\u003ePlug.Conn.request_url/1\u003c/code\u003e or Phoenix URL helpers, emit links whose authority the attacker chose. Message framing is unaffected, and no CR or LF can reach \u003ccode\u003econn.host\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThis issue affects bandit: from 0.6.0 before 1.13.0.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Validation of Syntactic Correctness of Input vulnerability in mtrudel bandit allows an unauthenticated remote attacker to set `conn.host` and `conn.port` to values outside the RFC 3986 host grammar via the authority of an HTTP/1 absolute-form or authority-form request target. Bandit validates the `Host` header and answers 400 when it is malformed, but when the request target carries its own authority that authority takes precedence, as RFC 9112 requires, and `Bandit.Pipeline.determine_host_and_port!/4` in `lib/bandit/pipeline.ex` passes it to the application unchecked. A request line such as `GET http://good.example@evil.example/x HTTP/1.1` with a valid `Host` header yields a `conn.host` of `good.example@evil.example`, and the same path admits NUL, DEL and non-ASCII bytes, invalid IP literals and ports outside 1..65535. The HTTP/1.0 clause needs no `Host` header at all, and `CONNECT` places the unparsed port string in `conn.port`.\n\nApplications that build absolute URLs from `conn.host`, such as through `Plug.Conn.request_url/1` or Phoenix URL helpers, emit links whose authority the attacker chose. Message framing is unaffected, and no CR or LF can reach `conn.host`.\n\nThis issue affects bandit: from 0.6.0 before 1.13.0."
                }
              ],
              "value": "Improper Validation of Syntactic Correctness of Input vulnerability in mtrudel bandit allows an unauthenticated remote attacker to set conn.host and conn.port to values outside the RFC 3986 host grammar via the authority of an HTTP/1 absolute-form or authority-form request target. Bandit validates the Host header and answers 400 when it is malformed, but when the request target carries its own authority that authority takes precedence, as RFC 9112 requires, and Bandit.Pipeline.determine_host_and_port!/4 in lib/bandit/pipeline.ex passes it to the application unchecked. A request line such as GET http://good.example@evil.example/x HTTP/1.1 with a valid Host header yields a conn.host of good.example@evil.example, and the same path admits NUL, DEL and non-ASCII bytes, invalid IP literals and ports outside 1..65535. The HTTP/1.0 clause needs no Host header at all, and CONNECT places the unparsed port string in conn.port.\n\nApplications that build absolute URLs from conn.host, such as through Plug.Conn.request_url/1 or Phoenix URL helpers, emit links whose authority the attacker chose. Message framing is unaffected, and no CR or LF can reach conn.host.\n\nThis issue affects bandit: from 0.6.0 before 1.13.0."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-220",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn unauthenticated client can make the application see any host string and any port number as the request\u0027s authority, bypassing the validation applied to the \u003ccode\u003eHost\u003c/code\u003e header. An application that builds absolute URLs from \u003ccode\u003econn.host\u003c/code\u003e, such as password-reset or verification links, emits links whose host the attacker chose.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An unauthenticated client can make the application see any host string and any port number as the request\u0027s authority, bypassing the validation applied to the `Host` header. An application that builds absolute URLs from `conn.host`, such as password-reset or verification links, emits links whose host the attacker chose."
                    }
                  ],
                  "value": "An unauthenticated client can make the application see any host string and any port number as the request\u0027s authority, bypassing the validation applied to the Host header. An application that builds absolute URLs from conn.host, such as password-reset or verification links, emits links whose host the attacker chose."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "NONE",
                "attackVector": "NETWORK",
                "baseScore": 6.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-1286",
                  "description": "CWE-1286 Improper Validation of Syntactic Correctness of Input",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-15T18:37:02.485Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-v53v-9hvg-37jf"
            },
            {
              "name": "EEF CNA record for CVE-2026-105660",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105660.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105660",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105660"
            },
            {
              "name": "Introducing commit 0e3edda in mtrudel/bandit",
              "tags": [
                "related"
              ],
              "url": "https://github.com/mtrudel/bandit/commit/0e3edda0edf6322e9164b827c6d3ddecd080ff2e"
            },
            {
              "name": "Fix commit 6ea8021 in mtrudel/bandit",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/mtrudel/bandit/commit/6ea80210137b3f69caf65f67398934259dc365d0"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Unvalidated HTTP/1 request-target authority lets a client choose conn.host and conn.port in Bandit",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAdd a plug ahead of any code that reads \u003ccode\u003econn.host\u003c/code\u003e or \u003ccode\u003econn.port\u003c/code\u003e that compares \u003ccode\u003econn.host\u003c/code\u003e against an allow-list of the hostnames the application serves, checks that \u003ccode\u003econn.port\u003c/code\u003e is an integer, and halts the request with a 400 response otherwise. Do not derive absolute URLs from \u003ccode\u003econn.host\u003c/code\u003e until that check has passed.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Add a plug ahead of any code that reads `conn.host` or `conn.port` that compares `conn.host` against an allow-list of the hostnames the application serves, checks that `conn.port` is an integer, and halts the request with a 400 response otherwise. Do not derive absolute URLs from `conn.host` until that check has passed."
                }
              ],
              "value": "Add a plug ahead of any code that reads conn.host or conn.port that compares conn.host against an allow-list of the hostnames the application serves, checks that conn.port is an integer, and halts the request with a 400 response otherwise. Do not derive absolute URLs from conn.host until that check has passed."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eStart Bandit over plaintext HTTP with a plug that returns \u003ccode\u003econn.host\u003c/code\u003e and \u003ccode\u003econn.port\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eOpen a raw TCP connection to the listener and send \u003ccode\u003eGET http://good.example@evil.example/x HTTP/1.1\u003c/code\u003e followed by \u003ccode\u003eHost: good.example\u003c/code\u003e and an empty line. The response is 200 and \u003ccode\u003econn.host\u003c/code\u003e is \u003ccode\u003egood.example@evil.example\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRepeat with \u003ccode\u003eGET http://ex\\x00am.com/x HTTP/1.1\u003c/code\u003e (a NUL byte in the authority) or \u003ccode\u003eGET http://ex.com:99999/x HTTP/1.1\u003c/code\u003e. Both are accepted, with the NUL byte in \u003ccode\u003econn.host\u003c/code\u003e and 99999 in \u003ccode\u003econn.port\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend the same bytes in the \u003ccode\u003eHost\u003c/code\u003e header of an origin-form request instead. Bandit answers 400 for the NUL byte and the out-of-range port on 1.12.5, and for every payload once the \u003ccode\u003eHost\u003c/code\u003e grammar tightening on main is released.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003eGET http://good.example@evil.example/x HTTP/1.0\u003c/code\u003e with no \u003ccode\u003eHost\u003c/code\u003e header, or \u003ccode\u003eCONNECT good.example@evil.example:443 HTTP/1.1\u003c/code\u003e. Both are accepted, and the \u003ccode\u003eCONNECT\u003c/code\u003e form leaves \u003ccode\u003econn.port\u003c/code\u003e as the binary \u003ccode\u003e\"443\"\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Start Bandit over plaintext HTTP with a plug that returns `conn.host` and `conn.port`.\n2. Open a raw TCP connection to the listener and send `GET http://good.example@evil.example/x HTTP/1.1` followed by `Host: good.example` and an empty line. The response is 200 and `conn.host` is `good.example@evil.example`.\n3. Repeat with `GET http://ex\\x00am.com/x HTTP/1.1` (a NUL byte in the authority) or `GET http://ex.com:99999/x HTTP/1.1`. Both are accepted, with the NUL byte in `conn.host` and 99999 in `conn.port`.\n4. Send the same bytes in the `Host` header of an origin-form request instead. Bandit answers 400 for the NUL byte and the out-of-range port on 1.12.5, and for every payload once the `Host` grammar tightening on main is released.\n5. Send `GET http://good.example@evil.example/x HTTP/1.0` with no `Host` header, or `CONNECT good.example@evil.example:443 HTTP/1.1`. Both are accepted, and the `CONNECT` form leaves `conn.port` as the binary `\"443\"`."
                }
              ],
              "value": "* Start Bandit over plaintext HTTP with a plug that returns conn.host and conn.port.\n* Open a raw TCP connection to the listener and send GET http://good.example@evil.example/x HTTP/1.1 followed by Host: good.example and an empty line. The response is 200 and conn.host is good.example@evil.example.\n* Repeat with GET http://ex\\x00am.com/x HTTP/1.1 (a NUL byte in the authority) or GET http://ex.com:99999/x HTTP/1.1. Both are accepted, with the NUL byte in conn.host and 99999 in conn.port.\n* Send the same bytes in the Host header of an origin-form request instead. Bandit answers 400 for the NUL byte and the out-of-range port on 1.12.5, and for every payload once the Host grammar tightening on main is released.\n* Send GET http://good.example@evil.example/x HTTP/1.0 with no Host header, or CONNECT good.example@evil.example:443 HTTP/1.1. Both are accepted, and the CONNECT form leaves conn.port as the binary \"443\"."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Request-target parsing\u003c/strong\u003e (\u003ccode\u003elib/bandit/http1/socket.ex\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e:erlang.decode_packet/3\u003c/code\u003e returns an absolute-form target as \u003ccode\u003e{:absoluteURI, scheme, host, port, path}\u003c/code\u003e and an authority-form (\u003ccode\u003eCONNECT\u003c/code\u003e) target as \u003ccode\u003e{:scheme, host, port}\u003c/code\u003e. \u003ccode\u003eresolve_request_target!/2\u003c/code\u003e unwraps both into a \u003ccode\u003e{scheme, host, port, path}\u003c/code\u003e tuple and forwards \u003ccode\u003ehost\u003c/code\u003e and \u003ccode\u003eport\u003c/code\u003e as received. \u003ccode\u003evalidate_request_line!/2\u003c/code\u003e only rejects bytes at or below \u003ccode\u003e0x20\u003c/code\u003e and \u003ccode\u003e0x7F\u003c/code\u003e, so userinfo, delimiters, backslashes, bytes \u003ccode\u003e0x80\u003c/code\u003e to \u003ccode\u003e0xFF\u003c/code\u003e, invalid IP literals and out-of-range ports pass. For \u003ccode\u003eCONNECT\u003c/code\u003e, \u003ccode\u003eport\u003c/code\u003e is the unparsed text after the first colon.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Host selection\u003c/strong\u003e (\u003ccode\u003elib/bandit/pipeline.ex\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003edetermine_host_and_port!/4\u003c/code\u003e has three clauses. The HTTP/1.1 clause calls \u003ccode\u003erequired_host_header!/1\u003c/code\u003e, which parses the \u003ccode\u003eHost\u003c/code\u003e header with \u003ccode\u003eBandit.Headers.parse_hostlike_header!/1\u003c/code\u003e and raises a 400 on a malformed value, and then discards that result whenever \u003ccode\u003etarget_host\u003c/code\u003e is set, returning \u003ccode\u003eto_string(target_host)\u003c/code\u003e and \u003ccode\u003etarget_port\u003c/code\u003e instead. The fallback clause used for HTTP/1.0 and for \u003ccode\u003eCONNECT\u003c/code\u003e returns the target host and port directly and never consults the \u003ccode\u003eHost\u003c/code\u003e header. The precedence itself follows RFC 9112 section 3.2.2; the defect is that the value which wins is the one never validated.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Effect\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ebuild_conn!/6\u003c/code\u003e places the pair into the \u003ccode\u003eURI\u003c/code\u003e from which \u003ccode\u003ePlug.Conn.Adapter.conn/5\u003c/code\u003e builds \u003ccode\u003econn.host\u003c/code\u003e and \u003ccode\u003econn.port\u003c/code\u003e. The application therefore sees host values Bandit rejects on every other route into the same field, and for \u003ccode\u003eCONNECT\u003c/code\u003e a binary where \u003ccode\u003ePlug.Conn.port_number/0\u003c/code\u003e promises an integer, which makes \u003ccode\u003ePlug.Conn.request_url/1\u003c/code\u003e raise.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Request-target parsing** (`lib/bandit/http1/socket.ex`)\n\n`:erlang.decode_packet/3` returns an absolute-form target as `{:absoluteURI, scheme, host, port, path}` and an authority-form (`CONNECT`) target as `{:scheme, host, port}`. `resolve_request_target!/2` unwraps both into a `{scheme, host, port, path}` tuple and forwards `host` and `port` as received. `validate_request_line!/2` only rejects bytes at or below `0x20` and `0x7F`, so userinfo, delimiters, backslashes, bytes `0x80` to `0xFF`, invalid IP literals and out-of-range ports pass. For `CONNECT`, `port` is the unparsed text after the first colon.\n\n**2. Host selection** (`lib/bandit/pipeline.ex`)\n\n`determine_host_and_port!/4` has three clauses. The HTTP/1.1 clause calls `required_host_header!/1`, which parses the `Host` header with `Bandit.Headers.parse_hostlike_header!/1` and raises a 400 on a malformed value, and then discards that result whenever `target_host` is set, returning `to_string(target_host)` and `target_port` instead. The fallback clause used for HTTP/1.0 and for `CONNECT` returns the target host and port directly and never consults the `Host` header. The precedence itself follows RFC 9112 section 3.2.2; the defect is that the value which wins is the one never validated.\n\n**3. Effect**\n\n`build_conn!/6` places the pair into the `URI` from which `Plug.Conn.Adapter.conn/5` builds `conn.host` and `conn.port`. The application therefore sees host values Bandit rejects on every other route into the same field, and for `CONNECT` a binary where `Plug.Conn.port_number/0` promises an integer, which makes `Plug.Conn.request_url/1` raise."
                }
              ],
              "value": "1. Request-target parsing (lib/bandit/http1/socket.ex)\n\n:erlang.decode_packet/3 returns an absolute-form target as {:absoluteURI, scheme, host, port, path} and an authority-form (CONNECT) target as {:scheme, host, port}. resolve_request_target!/2 unwraps both into a {scheme, host, port, path} tuple and forwards host and port as received. validate_request_line!/2 only rejects bytes at or below 0x20 and 0x7F, so userinfo, delimiters, backslashes, bytes 0x80 to 0xFF, invalid IP literals and out-of-range ports pass. For CONNECT, port is the unparsed text after the first colon.\n\n2. Host selection (lib/bandit/pipeline.ex)\n\ndetermine_host_and_port!/4 has three clauses. The HTTP/1.1 clause calls required_host_header!/1, which parses the Host header with Bandit.Headers.parse_hostlike_header!/1 and raises a 400 on a malformed value, and then discards that result whenever target_host is set, returning to_string(target_host) and target_port instead. The fallback clause used for HTTP/1.0 and for CONNECT returns the target host and port directly and never consults the Host header. The precedence itself follows RFC 9112 section 3.2.2; the defect is that the value which wins is the one never validated.\n\n3. Effect\n\nbuild_conn!/6 places the pair into the URI from which Plug.Conn.Adapter.conn/5 builds conn.host and conn.port. The application therefore sees host values Bandit rejects on every other route into the same field, and for CONNECT a binary where Plug.Conn.port_number/0 promises an integer, which makes Plug.Conn.request_url/1 raise."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105660",
        "datePublished": "2026-10-15T18:37:02.485Z",
        "dateReserved": "2026-10-10T13:15:02.320Z",
        "dateUpdated": "2026-10-16T20:02:38.784Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-96596 (GCVE-0-2026-96596)

    Vulnerability from cvelistv5 – Published: 2026-10-14 16:56 – Updated: 2026-10-22 07:08
    VLAI
    Title
    erl_tar:extract/2 loops forever on a GNU sparse member with a trailing hole
    Summary
    Loop with Unreachable Exit Condition ('Infinite Loop') vulnerability in Erlang OTP stdlib (erl_tar module), hexpm hex_core (hex_erl_tar module), hexpm hex (mix_hex_erl_tar module) and erlang rebar3 (r3_hex_erl_tar module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member. When erl_tar:extract/1,2 writes a member to disk, erl_tar:stream_to_file_loop/3 reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, erl_tar:do_sparse_read/3 matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The {max_size, N} option prevents this only when N is below the real size of the member. Extraction with the memory option is not affected. hex_core ships its own copy of erl_tar as hex_erl_tar, and Hex and rebar3 ship hex_core with prefixed module names (mix_hex_erl_tar and r3_hex_erl_tar). These copies contain the same code and do not call stdlib's erl_tar, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3. This issue affects OTP from OTP 29.0 before OTP 29.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 20:07 UTC
    CWE
    • CWE-835 - Loop with Unreachable Exit Condition ('Infinite Loop')
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    erlang rebar3 Affected: 3.27.1 , < 3.28.0 (semver)
        cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*
    Create a notification for this product.
    erlang rebar3 Affected: 23dbdb5d1f87f56902aef296c88a20049cd0c6e4 , < 310e2ec8c7dd3df05d67af2b7fae23f30fc523c4 (git)
        cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex_core Affected: 0.14.0 , < 0.20.1 (semver)
        cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex_core Affected: 13bb9fbba418a406af614790b8d5cd458259ee70 , < a2ece683e6e88a3ec95ad20b09aca426e08b372e (git)
        cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: 29.0 , < 29.1.2 (otp)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: 8.0 , < 8.1.1 (otp)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: fbf2c7194f6fce4b7b66dc0bcaa4f6b4b85e088f , < 3086307f311b78b38644a7787a3aca6a3f95a9d5 (git)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 2.4.2 , < 2.5.3 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 5543a1919c709d8eb2b317f62aad76ca1eed375c , < * (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-10-14 16:56
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-96596",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T20:07:46.754736Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T20:08:17.542Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "r3_hex_erl_tar",
                "r3_hex_tarball"
              ],
              "packageName": "rebar3",
              "packageURL": "pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git",
              "product": "rebar3",
              "programFiles": [
                "apps/rebar/src/vendored/r3_hex_erl_tar.erl",
                "apps/rebar/src/vendored/r3_hex_tarball.erl"
              ],
              "programRoutines": [
                {
                  "name": "r3_hex_erl_tar:do_sparse_read/3"
                },
                {
                  "name": "r3_hex_erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "r3_hex_erl_tar:extract/1"
                },
                {
                  "name": "r3_hex_erl_tar:extract/2"
                },
                {
                  "name": "r3_hex_tarball:unpack/2"
                },
                {
                  "name": "r3_hex_tarball:unpack/3"
                },
                {
                  "name": "r3_hex_tarball:unpack_docs/2"
                },
                {
                  "name": "r3_hex_tarball:unpack_docs/3"
                }
              ],
              "repo": "https://github.com/erlang/rebar3",
              "vendor": "erlang",
              "versions": [
                {
                  "lessThan": "3.28.0",
                  "status": "affected",
                  "version": "3.27.1",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "r3_hex_erl_tar",
                "r3_hex_tarball"
              ],
              "packageName": "erlang/rebar3",
              "packageURL": "pkg:github/erlang/rebar3",
              "product": "rebar3",
              "programFiles": [
                "apps/rebar/src/vendored/r3_hex_erl_tar.erl",
                "apps/rebar/src/vendored/r3_hex_tarball.erl"
              ],
              "programRoutines": [
                {
                  "name": "r3_hex_erl_tar:do_sparse_read/3"
                },
                {
                  "name": "r3_hex_erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "r3_hex_erl_tar:extract/1"
                },
                {
                  "name": "r3_hex_erl_tar:extract/2"
                },
                {
                  "name": "r3_hex_tarball:unpack/2"
                },
                {
                  "name": "r3_hex_tarball:unpack/3"
                },
                {
                  "name": "r3_hex_tarball:unpack_docs/2"
                },
                {
                  "name": "r3_hex_tarball:unpack_docs/3"
                }
              ],
              "repo": "https://github.com/erlang/rebar3",
              "vendor": "erlang",
              "versions": [
                {
                  "lessThan": "310e2ec8c7dd3df05d67af2b7fae23f30fc523c4",
                  "status": "affected",
                  "version": "23dbdb5d1f87f56902aef296c88a20049cd0c6e4",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "hex_erl_tar",
                "hex_tarball"
              ],
              "packageName": "hex_core",
              "packageURL": "pkg:hex/hex_core",
              "product": "hex_core",
              "programFiles": [
                "src/hex_erl_tar.erl",
                "src/hex_tarball.erl"
              ],
              "programRoutines": [
                {
                  "name": "hex_erl_tar:do_sparse_read/3"
                },
                {
                  "name": "hex_erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "hex_erl_tar:extract/1"
                },
                {
                  "name": "hex_erl_tar:extract/2"
                },
                {
                  "name": "hex_tarball:unpack/2"
                },
                {
                  "name": "hex_tarball:unpack/3"
                },
                {
                  "name": "hex_tarball:unpack_docs/2"
                },
                {
                  "name": "hex_tarball:unpack_docs/3"
                }
              ],
              "repo": "https://github.com/hexpm/hex_core",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "0.20.1",
                  "status": "affected",
                  "version": "0.14.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "hex_erl_tar",
                "hex_tarball"
              ],
              "packageName": "hexpm/hex_core",
              "packageURL": "pkg:github/hexpm/hex_core",
              "product": "hex_core",
              "programFiles": [
                "src/hex_erl_tar.erl",
                "src/hex_tarball.erl"
              ],
              "programRoutines": [
                {
                  "name": "hex_erl_tar:do_sparse_read/3"
                },
                {
                  "name": "hex_erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "hex_erl_tar:extract/1"
                },
                {
                  "name": "hex_erl_tar:extract/2"
                },
                {
                  "name": "hex_tarball:unpack/2"
                },
                {
                  "name": "hex_tarball:unpack/3"
                },
                {
                  "name": "hex_tarball:unpack_docs/2"
                },
                {
                  "name": "hex_tarball:unpack_docs/3"
                }
              ],
              "repo": "https://github.com/hexpm/hex_core",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "a2ece683e6e88a3ec95ad20b09aca426e08b372e",
                  "status": "affected",
                  "version": "13bb9fbba418a406af614790b8d5cd458259ee70",
                  "versionType": "git"
                }
              ]
            },
            {
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "erl_tar"
              ],
              "packageName": "otp",
              "packageURL": "pkg:software-id/erlang.org/otp",
              "product": "OTP",
              "programFiles": [
                "lib/stdlib/src/erl_tar.erl"
              ],
              "programRoutines": [
                {
                  "name": "erl_tar:do_sparse_read/3"
                },
                {
                  "name": "erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "erl_tar:extract/1"
                },
                {
                  "name": "erl_tar:extract/2"
                }
              ],
              "vendor": "Erlang",
              "versions": [
                {
                  "lessThan": "29.1.2",
                  "status": "affected",
                  "version": "29.0",
                  "versionType": "otp"
                }
              ]
            },
            {
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "erl_tar"
              ],
              "packageName": "stdlib",
              "packageURL": "pkg:otp/stdlib?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Fotp\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Fotp.git",
              "product": "OTP",
              "programFiles": [
                "src/erl_tar.erl"
              ],
              "programRoutines": [
                {
                  "name": "erl_tar:do_sparse_read/3"
                },
                {
                  "name": "erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "erl_tar:extract/1"
                },
                {
                  "name": "erl_tar:extract/2"
                }
              ],
              "repo": "https://github.com/erlang/otp",
              "vendor": "Erlang",
              "versions": [
                {
                  "lessThan": "8.1.1",
                  "status": "affected",
                  "version": "8.0",
                  "versionType": "otp"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "erl_tar"
              ],
              "packageName": "erlang/otp",
              "packageURL": "pkg:github/erlang/otp",
              "product": "OTP",
              "programFiles": [
                "lib/stdlib/src/erl_tar.erl"
              ],
              "programRoutines": [
                {
                  "name": "erl_tar:do_sparse_read/3"
                },
                {
                  "name": "erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "erl_tar:extract/1"
                },
                {
                  "name": "erl_tar:extract/2"
                }
              ],
              "repo": "https://github.com/erlang/otp",
              "vendor": "Erlang",
              "versions": [
                {
                  "lessThan": "3086307f311b78b38644a7787a3aca6a3f95a9d5",
                  "status": "affected",
                  "version": "fbf2c7194f6fce4b7b66dc0bcaa4f6b4b85e088f",
                  "versionType": "git"
                }
              ]
            },
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "mix_hex_erl_tar",
                "mix_hex_tarball"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "src/mix_hex_erl_tar.erl",
                "src/mix_hex_tarball.erl"
              ],
              "programRoutines": [
                {
                  "name": "mix_hex_erl_tar:do_sparse_read/3"
                },
                {
                  "name": "mix_hex_erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "mix_hex_erl_tar:extract/1"
                },
                {
                  "name": "mix_hex_erl_tar:extract/2"
                },
                {
                  "name": "mix_hex_tarball:unpack/2"
                },
                {
                  "name": "mix_hex_tarball:unpack/3"
                },
                {
                  "name": "mix_hex_tarball:unpack_docs/2"
                },
                {
                  "name": "mix_hex_tarball:unpack_docs/3"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.3",
                  "status": "affected",
                  "version": "2.4.2",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "mix_hex_erl_tar",
                "mix_hex_tarball"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "src/mix_hex_erl_tar.erl",
                "src/mix_hex_tarball.erl"
              ],
              "programRoutines": [
                {
                  "name": "mix_hex_erl_tar:do_sparse_read/3"
                },
                {
                  "name": "mix_hex_erl_tar:stream_to_file_loop/3"
                },
                {
                  "name": "mix_hex_erl_tar:extract/1"
                },
                {
                  "name": "mix_hex_erl_tar:extract/2"
                },
                {
                  "name": "mix_hex_tarball:unpack/2"
                },
                {
                  "name": "mix_hex_tarball:unpack/3"
                },
                {
                  "name": "mix_hex_tarball:unpack_docs/2"
                },
                {
                  "name": "mix_hex_tarball:unpack_docs/3"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "8e1067131a13949eff30623f31648a945ffd5418",
                      "status": "unaffected"
                    },
                    {
                      "at": "7847c23d09ab52113614c3718e1e7d368743da5d",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "5543a1919c709d8eb2b317f62aad76ca1eed375c",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "3.28.0",
                      "versionStartIncluding": "3.27.1",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.20.1",
                      "versionStartIncluding": "0.14.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "29.1.2",
                      "versionStartIncluding": "29.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.3",
                      "versionStartIncluding": "2.4.2",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Eric Meadows-J\u00f6nsson"
            }
          ],
          "dateAssigned": "2026-10-07T13:11:38.000Z",
          "datePublic": "2026-10-14T16:56:18.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eLoop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) vulnerability in Erlang OTP stdlib (\u003ccode\u003eerl_tar\u003c/code\u003e module), hexpm hex_core (\u003ccode\u003ehex_erl_tar\u003c/code\u003e module), hexpm hex (\u003ccode\u003emix_hex_erl_tar\u003c/code\u003e module) and erlang rebar3 (\u003ccode\u003er3_hex_erl_tar\u003c/code\u003e module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.\u003c/p\u003e\n\u003cp\u003eWhen \u003ccode\u003eerl_tar:extract/1,2\u003c/code\u003e writes a member to disk, \u003ccode\u003eerl_tar:stream_to_file_loop/3\u003c/code\u003e reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, \u003ccode\u003eerl_tar:do_sparse_read/3\u003c/code\u003e matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The \u003ccode\u003e{max_size, N}\u003c/code\u003e option prevents this only when N is below the real size of the member. Extraction with the \u003ccode\u003ememory\u003c/code\u003e option is not affected.\u003c/p\u003e\n\u003cp\u003ehex_core ships its own copy of \u003ccode\u003eerl_tar\u003c/code\u003e as \u003ccode\u003ehex_erl_tar\u003c/code\u003e, and Hex and rebar3 ship hex_core with prefixed module names (\u003ccode\u003emix_hex_erl_tar\u003c/code\u003e and \u003ccode\u003er3_hex_erl_tar\u003c/code\u003e). These copies contain the same code and do not call stdlib\u0027s \u003ccode\u003eerl_tar\u003c/code\u003e, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.\u003c/p\u003e\n\u003cp\u003eThis issue affects OTP from OTP\u0026nbsp;29.0 before OTP\u0026nbsp;29.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) vulnerability in Erlang OTP stdlib (`erl_tar` module), hexpm hex_core (`hex_erl_tar` module), hexpm hex (`mix_hex_erl_tar` module) and erlang rebar3 (`r3_hex_erl_tar` module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.\n\nWhen `erl_tar:extract/1,2` writes a member to disk, `erl_tar:stream_to_file_loop/3` reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, `erl_tar:do_sparse_read/3` matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The `{max_size, N}` option prevents this only when N is below the real size of the member. Extraction with the `memory` option is not affected.\n\nhex_core ships its own copy of `erl_tar` as `hex_erl_tar`, and Hex and rebar3 ship hex_core with prefixed module names (`mix_hex_erl_tar` and `r3_hex_erl_tar`). These copies contain the same code and do not call stdlib\u0027s `erl_tar`, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.\n\nThis issue affects OTP from OTP\u00a029.0 before OTP\u00a029.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1."
                }
              ],
              "value": "Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) vulnerability in Erlang OTP stdlib (erl_tar module), hexpm hex_core (hex_erl_tar module), hexpm hex (mix_hex_erl_tar module) and erlang rebar3 (r3_hex_erl_tar module) allows an attacker who supplies a tar archive to cause a denial of service via a crafted GNU sparse member.\n\nWhen erl_tar:extract/1,2 writes a member to disk, erl_tar:stream_to_file_loop/3 reads it in chunks of 65536 bytes by default. For a GNU sparse member whose sparse map ends before its real size, erl_tar:do_sparse_read/3 matches its trailing-hole clause before its zero-length clause. After the chunk is used up, the trailing-hole clause reads zero bytes, does not advance the position, and recurses forever. A 2048-byte archive keeps the extracting process and one scheduler busy until the process is killed. The {max_size, N} option prevents this only when N is below the real size of the member. Extraction with the memory option is not affected.\n\nhex_core ships its own copy of erl_tar as hex_erl_tar, and Hex and rebar3 ship hex_core with prefixed module names (mix_hex_erl_tar and r3_hex_erl_tar). These copies contain the same code and do not call stdlib\u0027s erl_tar, so they are affected on any OTP version. OTP 28 is not affected, but an application running on OTP 28 is affected if it extracts archives through an affected version of hex_core, Hex or rebar3.\n\nThis issue affects OTP from OTP\u00a029.0 before OTP\u00a029.1.2, corresponding to rebar3 from 3.27.1 before 3.28.0, and stdlib from 8.0 before 8.1.1, and hex from 2.4.2 before 2.5.3; hex_core: from 0.14.0 before 0.20.1."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn attacker who can get an application to extract a tar archive to disk keeps the extracting process and one scheduler busy forever. Each further archive occupies another scheduler, until the node stops responding.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An attacker who can get an application to extract a tar archive to disk keeps the extracting process and one scheduler busy forever. Each further archive occupies another scheduler, until the node stops responding."
                    }
                  ],
                  "value": "An attacker who can get an application to extract a tar archive to disk keeps the extracting process and one scheduler busy forever. Each further archive occupies another scheduler, until the node stops responding."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "LOCAL",
                "baseScore": 5.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-835",
                  "description": "CWE-835 Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-22T07:08:09.364Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/erlang/otp/security/advisories/GHSA-phm5-m6cj-5382"
            },
            {
              "name": "EEF CNA record for CVE-2026-96596",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-96596.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-96596",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-96596"
            },
            {
              "name": "Erlang/OTP version ordering",
              "tags": [
                "x_version-scheme"
              ],
              "url": "https://www.erlang.org/doc/system/versions.html#order-of-versions"
            },
            {
              "name": "Introducing commit 23dbdb5 in erlang/rebar3",
              "tags": [
                "related"
              ],
              "url": "https://github.com/erlang/rebar3/commit/23dbdb5d1f87f56902aef296c88a20049cd0c6e4"
            },
            {
              "name": "Fix commit 310e2ec in erlang/rebar3",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/rebar3/commit/310e2ec8c7dd3df05d67af2b7fae23f30fc523c4"
            },
            {
              "name": "Introducing commit 13bb9fb in hexpm/hex_core",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex_core/commit/13bb9fbba418a406af614790b8d5cd458259ee70"
            },
            {
              "name": "Fix commit a2ece68 in hexpm/hex_core",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex_core/commit/a2ece683e6e88a3ec95ad20b09aca426e08b372e"
            },
            {
              "name": "Introducing commit fbf2c71 in erlang/otp",
              "tags": [
                "related"
              ],
              "url": "https://github.com/erlang/otp/commit/fbf2c7194f6fce4b7b66dc0bcaa4f6b4b85e088f"
            },
            {
              "name": "Fix commit 3086307 in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/3086307f311b78b38644a7787a3aca6a3f95a9d5"
            },
            {
              "name": "Introducing commit 5543a19 in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/5543a1919c709d8eb2b317f62aad76ca1eed375c"
            },
            {
              "name": "Fix commit 8e10671 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/8e1067131a13949eff30623f31648a945ffd5418"
            },
            {
              "name": "Fix commit 7847c23 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/7847c23d09ab52113614c3718e1e7d368743da5d"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "erl_tar:extract/2 loops forever on a GNU sparse member with a trailing hole",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003ePass \u003ccode\u003e{chunks, N}\u003c/code\u003e together with \u003ccode\u003e{max_size, N}\u003c/code\u003e, with the same value N, to \u003ccode\u003eerl_tar:extract/2\u003c/code\u003e. Extraction then rejects any member larger than N, and reads each remaining member in one call, so the loop cannot occur.\u003c/p\u003e\n\u003cp\u003eAlternatively, extract with the \u003ccode\u003ememory\u003c/code\u003e option and write the returned files to disk in the application.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Pass `{chunks, N}` together with `{max_size, N}`, with the same value N, to `erl_tar:extract/2`. Extraction then rejects any member larger than N, and reads each remaining member in one call, so the loop cannot occur.\n\nAlternatively, extract with the `memory` option and write the returned files to disk in the application."
                }
              ],
              "value": "Pass {chunks, N} together with {max_size, N}, with the same value N, to erl_tar:extract/2. Extraction then rejects any member larger than N, and reads each remaining member in one call, so the loop cannot occur.\n\nAlternatively, extract with the memory option and write the returned files to disk in the application."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eBuild a 2048-byte tar that holds one GNU sparse member (typeflag \u003ccode\u003eS\u003c/code\u003e). Give it a stored size of 4 bytes, one sparse map entry of 4 bytes at offset 0, and a real size of 1048576 bytes. Follow the 512-byte header with a 512-byte block that starts with the 4 data bytes, then 1024 zero bytes.\u003c/li\u003e\n\u003cli\u003eIn a separate process, call \u003ccode\u003eerl_tar:extract({binary, Tar}, [{cwd, Dir}])\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eThe call does not return. After 5 seconds the process has used more than 10^9 reductions. On OTP 28 the same call returns \u003ccode\u003eok\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Build a 2048-byte tar that holds one GNU sparse member (typeflag `S`). Give it a stored size of 4 bytes, one sparse map entry of 4 bytes at offset 0, and a real size of 1048576 bytes. Follow the 512-byte header with a 512-byte block that starts with the 4 data bytes, then 1024 zero bytes.\n2. In a separate process, call `erl_tar:extract({binary, Tar}, [{cwd, Dir}])`.\n3. The call does not return. After 5 seconds the process has used more than 10^9 reductions. On OTP 28 the same call returns `ok`."
                }
              ],
              "value": "* Build a 2048-byte tar that holds one GNU sparse member (typeflag S). Give it a stored size of 4 bytes, one sparse map entry of 4 bytes at offset 0, and a real size of 1048576 bytes. Follow the 512-byte header with a 512-byte block that starts with the 4 data bytes, then 1024 zero bytes.\n* In a separate process, call erl_tar:extract({binary, Tar}, [{cwd, Dir}]).\n* The call does not return. After 5 seconds the process has used more than 10^9 reductions. On OTP 28 the same call returns ok."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Chunked extraction.\u003c/strong\u003e \u003ccode\u003eerl_tar:extract/2\u003c/code\u003e writes each member to disk through \u003ccode\u003estream_to_file_loop/3\u003c/code\u003e. This function calls \u003ccode\u003edo_read(Reader, ChunkSize)\u003c/code\u003e until the \u003ccode\u003enum_bytes\u003c/code\u003e field of the reader is 0. The default chunk size is 65536 bytes.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Clause order in \u003ccode\u003edo_sparse_read/3\u003c/code\u003e.\u003c/strong\u003e For a sparse member, \u003ccode\u003edo_read/2\u003c/code\u003e calls \u003ccode\u003edo_sparse_read/3\u003c/code\u003e. The clause for a trailing hole matches when \u003ccode\u003esparse_map\u003c/code\u003e is \u003ccode\u003e[]\u003c/code\u003e and \u003ccode\u003ePos \u0026lt; Size\u003c/code\u003e. It comes before the clause that returns when \u003ccode\u003eLen\u003c/code\u003e is 0.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Unbounded recursion.\u003c/strong\u003e The member is a GNU sparse member (typeflag \u003ccode\u003eS\u003c/code\u003e) whose real size is larger than one chunk and whose sparse map ends before the real size. After the data fragments, the trailing-hole clause reads the rest of the chunk and recurses with \u003ccode\u003eLen = 0\u003c/code\u003e. The same clause matches again, and \u003ccode\u003eread_sparse_hole/3\u003c/code\u003e returns an empty binary and leaves \u003ccode\u003ePos\u003c/code\u003e unchanged. The zero-length clause is never reached, so the call never returns.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. No mitigation from \u003ccode\u003emax_size\u003c/code\u003e.\u003c/strong\u003e For a sparse member, \u003ccode\u003e{max_size, N}\u003c/code\u003e is checked against the real size from the header. It stops the loop only when N is below that size. \u003ccode\u003eerl_tar:table/2\u003c/code\u003e lists the member as \u003ccode\u003eregular\u003c/code\u003e, so a caller cannot skip sparse members by type.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Chunked extraction.** `erl_tar:extract/2` writes each member to disk through `stream_to_file_loop/3`. This function calls `do_read(Reader, ChunkSize)` until the `num_bytes` field of the reader is 0. The default chunk size is 65536 bytes.\n\n**2. Clause order in `do_sparse_read/3`.** For a sparse member, `do_read/2` calls `do_sparse_read/3`. The clause for a trailing hole matches when `sparse_map` is `[]` and `Pos \u003c Size`. It comes before the clause that returns when `Len` is 0.\n\n**3. Unbounded recursion.** The member is a GNU sparse member (typeflag `S`) whose real size is larger than one chunk and whose sparse map ends before the real size. After the data fragments, the trailing-hole clause reads the rest of the chunk and recurses with `Len = 0`. The same clause matches again, and `read_sparse_hole/3` returns an empty binary and leaves `Pos` unchanged. The zero-length clause is never reached, so the call never returns.\n\n**4. No mitigation from `max_size`.** For a sparse member, `{max_size, N}` is checked against the real size from the header. It stops the loop only when N is below that size. `erl_tar:table/2` lists the member as `regular`, so a caller cannot skip sparse members by type."
                }
              ],
              "value": "1. Chunked extraction. erl_tar:extract/2 writes each member to disk through stream_to_file_loop/3. This function calls do_read(Reader, ChunkSize) until the num_bytes field of the reader is 0. The default chunk size is 65536 bytes.\n\n2. Clause order in do_sparse_read/3. For a sparse member, do_read/2 calls do_sparse_read/3. The clause for a trailing hole matches when sparse_map is [] and Pos \u003c Size. It comes before the clause that returns when Len is 0.\n\n3. Unbounded recursion. The member is a GNU sparse member (typeflag S) whose real size is larger than one chunk and whose sparse map ends before the real size. After the data fragments, the trailing-hole clause reads the rest of the chunk and recurses with Len = 0. The same clause matches again, and read_sparse_hole/3 returns an empty binary and leaves Pos unchanged. The zero-length clause is never reached, so the call never returns.\n\n4. No mitigation from max_size. For a sparse member, {max_size, N} is checked against the real size from the header. It stops the loop only when N is below that size. erl_tar:table/2 lists the member as regular, so a caller cannot skip sparse members by type."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-96596",
        "datePublished": "2026-10-14T16:56:18.530Z",
        "dateReserved": "2026-09-27T12:00:01.540Z",
        "dateUpdated": "2026-10-22T07:08:09.364Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105236 (GCVE-0-2026-105236)

    Vulnerability from cvelistv5 – Published: 2026-10-14 16:55 – Updated: 2026-10-16 20:09
    VLAI
    Title
    TLS 1.2 session resumption in ssl crosses SNI virtual-host client-certificate trust boundaries
    Summary
    Improper Certificate Validation vulnerability in the ssl application in Erlang/OTP allows a client that holds a certificate trusted by one SNI virtual host to authenticate to a second SNI virtual host on the same listener by resuming its TLS 1.2 session there. A server that serves several SNI names from one listen socket through sni_fun or sni_hosts keeps one pre-TLS 1.3 session cache per listener and does not bind a cached session to the SNI name or client trust policy that established it. When an SNI option set switches to verify_peer without naming reuse_sessions, ssl_config:update_options/3 inherits the base listener's reuse_sessions value of true, so the default that disables reuse for client-authenticating servers does not apply. On an abbreviated handshake ssl_session:is_resumable/4 checks only that the cached session uses one of the current server certificates and carries some peer certificate, and never revalidates that certificate against the CA selected for the requested name. The names must share a server certificate, which is common with wildcard or multi-SAN certificates. TLS 1.3 does not use this cache and is not affected. This issue affects OTP from OTP 18.0 before OTP 27.3.4.19, OTP 28.5.0.8, and OTP 29.1.2, corresponding to ssl from 7.0 before 11.2.12.14, 11.6.0.7, and 11.7.8.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 20:09 UTC
    CWE
    • CWE-295 - Improper Certificate Validation
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    Erlang OTP Affected: 18.0 , < * (otp)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: 7.0 , < * (otp)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: 4fe38c4b8b2c8024afb60990e598ff823743fd54 , < * (git)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105236",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T20:09:27.807992Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T20:09:49.566Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "ssl_gen_statem",
                "ssl_config",
                "ssl_session",
                "ssl_handshake"
              ],
              "packageName": "otp",
              "packageURL": "pkg:software-id/erlang.org/otp",
              "product": "OTP",
              "programFiles": [
                "lib/ssl/src/ssl_gen_statem.erl",
                "lib/ssl/src/ssl_config.erl",
                "lib/ssl/src/ssl_session.erl",
                "lib/ssl/src/ssl_handshake.erl"
              ],
              "programRoutines": [
                {
                  "name": "ssl_gen_statem:handle_sni_extension/2"
                },
                {
                  "name": "ssl_gen_statem:handle_sni_hostname/2"
                },
                {
                  "name": "ssl_gen_statem:update_ssl_options_from_sni/2"
                },
                {
                  "name": "ssl_gen_statem:fallback_options/2"
                },
                {
                  "name": "ssl_config:update_options/3"
                },
                {
                  "name": "ssl_config:opt_reuse_sessions/3"
                },
                {
                  "name": "ssl_session:server_select_session/5"
                },
                {
                  "name": "ssl_session:is_resumable/4"
                },
                {
                  "name": "ssl_session:reusable_options/2"
                },
                {
                  "name": "ssl_handshake:select_session/8"
                }
              ],
              "vendor": "Erlang",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "27.3.4.19",
                      "status": "unaffected"
                    },
                    {
                      "at": "28.5.0.8",
                      "status": "unaffected"
                    },
                    {
                      "at": "29.1.2",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "18.0",
                  "versionType": "otp"
                }
              ]
            },
            {
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "ssl_gen_statem",
                "ssl_config",
                "ssl_session",
                "ssl_handshake"
              ],
              "packageName": "ssl",
              "packageURL": "pkg:otp/ssl?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Fotp\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Fotp.git",
              "product": "OTP",
              "programFiles": [
                "src/ssl_gen_statem.erl",
                "src/ssl_config.erl",
                "src/ssl_session.erl",
                "src/ssl_handshake.erl"
              ],
              "programRoutines": [
                {
                  "name": "ssl_gen_statem:handle_sni_extension/2"
                },
                {
                  "name": "ssl_gen_statem:handle_sni_hostname/2"
                },
                {
                  "name": "ssl_gen_statem:update_ssl_options_from_sni/2"
                },
                {
                  "name": "ssl_gen_statem:fallback_options/2"
                },
                {
                  "name": "ssl_config:update_options/3"
                },
                {
                  "name": "ssl_config:opt_reuse_sessions/3"
                },
                {
                  "name": "ssl_session:server_select_session/5"
                },
                {
                  "name": "ssl_session:is_resumable/4"
                },
                {
                  "name": "ssl_session:reusable_options/2"
                },
                {
                  "name": "ssl_handshake:select_session/8"
                }
              ],
              "repo": "https://github.com/erlang/otp",
              "vendor": "Erlang",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "11.2.12.14",
                      "status": "unaffected"
                    },
                    {
                      "at": "11.6.0.7",
                      "status": "unaffected"
                    },
                    {
                      "at": "11.7.8",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "7.0",
                  "versionType": "otp"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "ssl_gen_statem",
                "ssl_config",
                "ssl_session",
                "ssl_handshake"
              ],
              "packageName": "erlang/otp",
              "packageURL": "pkg:github/erlang/otp",
              "product": "OTP",
              "programFiles": [
                "lib/ssl/src/ssl_gen_statem.erl",
                "lib/ssl/src/ssl_config.erl",
                "lib/ssl/src/ssl_session.erl",
                "lib/ssl/src/ssl_handshake.erl"
              ],
              "programRoutines": [
                {
                  "name": "ssl_gen_statem:handle_sni_extension/2"
                },
                {
                  "name": "ssl_gen_statem:handle_sni_hostname/2"
                },
                {
                  "name": "ssl_gen_statem:update_ssl_options_from_sni/2"
                },
                {
                  "name": "ssl_gen_statem:fallback_options/2"
                },
                {
                  "name": "ssl_config:update_options/3"
                },
                {
                  "name": "ssl_config:opt_reuse_sessions/3"
                },
                {
                  "name": "ssl_session:server_select_session/5"
                },
                {
                  "name": "ssl_session:is_resumable/4"
                },
                {
                  "name": "ssl_session:reusable_options/2"
                },
                {
                  "name": "ssl_handshake:select_session/8"
                }
              ],
              "repo": "https://github.com/erlang/otp",
              "vendor": "Erlang",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "0c20f3683b3c81fd30b46047195be9b3d2c69d76",
                      "status": "unaffected"
                    },
                    {
                      "at": "4d76adbf39d547a931048544df09c3c087e56618",
                      "status": "unaffected"
                    },
                    {
                      "at": "10ee925d0fee8413f1170cc21bfcf123c8d22454",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "4fe38c4b8b2c8024afb60990e598ff823743fd54",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eExploitation requires all of the following. One TLS listener serves at least two SNI names through \u003ccode\u003esni_fun\u003c/code\u003e or \u003ccode\u003esni_hosts\u003c/code\u003e. The names use the same server certificate but select different client CAs with \u003ccode\u003everify_peer\u003c/code\u003e. TLS 1.2 or earlier is enabled. No SNI option set sets \u003ccode\u003ereuse_sessions\u003c/code\u003e to false while the base listener\u0027s effective value is true, which is the default when the base listener uses \u003ccode\u003everify_none\u003c/code\u003e. The attacker holds a client certificate that the lower-trust name accepts.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Exploitation requires all of the following. One TLS listener serves at least two SNI names through `sni_fun` or `sni_hosts`. The names use the same server certificate but select different client CAs with `verify_peer`. TLS 1.2 or earlier is enabled. No SNI option set sets `reuse_sessions` to false while the base listener\u0027s effective value is true, which is the default when the base listener uses `verify_none`. The attacker holds a client certificate that the lower-trust name accepts."
                }
              ],
              "value": "Exploitation requires all of the following. One TLS listener serves at least two SNI names through sni_fun or sni_hosts. The names use the same server certificate but select different client CAs with verify_peer. TLS 1.2 or earlier is enabled. No SNI option set sets reuse_sessions to false while the base listener\u0027s effective value is true, which is the default when the base listener uses verify_none. The attacker holds a client certificate that the lower-trust name accepts."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "27.3.4.19",
                      "versionStartIncluding": "18.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "28.5.0.8",
                      "versionStartIncluding": "28.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "29.1.2",
                      "versionStartIncluding": "29.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Yonghwa Lee / Xint by Theori"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Yonghwa Lee / Xint by Theori"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Ingela Anderton Andin"
            }
          ],
          "dateAssigned": "2026-10-08T15:40:48.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Certificate Validation vulnerability in the ssl application in Erlang/OTP allows a client that holds a certificate trusted by one SNI virtual host to authenticate to a second SNI virtual host on the same listener by resuming its TLS 1.2 session there.\u003c/p\u003e\n\u003cp\u003eA server that serves several SNI names from one listen socket through \u003ccode\u003esni_fun\u003c/code\u003e or \u003ccode\u003esni_hosts\u003c/code\u003e keeps one pre-TLS 1.3 session cache per listener and does not bind a cached session to the SNI name or client trust policy that established it. When an SNI option set switches to \u003ccode\u003everify_peer\u003c/code\u003e without naming \u003ccode\u003ereuse_sessions\u003c/code\u003e, \u003ccode\u003essl_config:update_options/3\u003c/code\u003e inherits the base listener\u0027s \u003ccode\u003ereuse_sessions\u003c/code\u003e value of true, so the default that disables reuse for client-authenticating servers does not apply. On an abbreviated handshake \u003ccode\u003essl_session:is_resumable/4\u003c/code\u003e checks only that the cached session uses one of the current server certificates and carries some peer certificate, and never revalidates that certificate against the CA selected for the requested name. The names must share a server certificate, which is common with wildcard or multi-SAN certificates. TLS 1.3 does not use this cache and is not affected.\u003c/p\u003e\n\u003cp\u003eThis issue affects OTP from OTP\u0026nbsp;18.0 before OTP\u0026nbsp;27.3.4.19, OTP\u0026nbsp;28.5.0.8, and OTP\u0026nbsp;29.1.2, corresponding to ssl from 7.0 before 11.2.12.14, 11.6.0.7, and 11.7.8.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Certificate Validation vulnerability in the ssl application in Erlang/OTP allows a client that holds a certificate trusted by one SNI virtual host to authenticate to a second SNI virtual host on the same listener by resuming its TLS 1.2 session there.\n\nA server that serves several SNI names from one listen socket through `sni_fun` or `sni_hosts` keeps one pre-TLS 1.3 session cache per listener and does not bind a cached session to the SNI name or client trust policy that established it. When an SNI option set switches to `verify_peer` without naming `reuse_sessions`, `ssl_config:update_options/3` inherits the base listener\u0027s `reuse_sessions` value of true, so the default that disables reuse for client-authenticating servers does not apply. On an abbreviated handshake `ssl_session:is_resumable/4` checks only that the cached session uses one of the current server certificates and carries some peer certificate, and never revalidates that certificate against the CA selected for the requested name. The names must share a server certificate, which is common with wildcard or multi-SAN certificates. TLS 1.3 does not use this cache and is not affected.\n\nThis issue affects OTP from OTP\u00a018.0 before OTP\u00a027.3.4.19, OTP\u00a028.5.0.8, and OTP\u00a029.1.2, corresponding to ssl from 7.0 before 11.2.12.14, 11.6.0.7, and 11.7.8."
                }
              ],
              "value": "Improper Certificate Validation vulnerability in the ssl application in Erlang/OTP allows a client that holds a certificate trusted by one SNI virtual host to authenticate to a second SNI virtual host on the same listener by resuming its TLS 1.2 session there.\n\nA server that serves several SNI names from one listen socket through sni_fun or sni_hosts keeps one pre-TLS 1.3 session cache per listener and does not bind a cached session to the SNI name or client trust policy that established it. When an SNI option set switches to verify_peer without naming reuse_sessions, ssl_config:update_options/3 inherits the base listener\u0027s reuse_sessions value of true, so the default that disables reuse for client-authenticating servers does not apply. On an abbreviated handshake ssl_session:is_resumable/4 checks only that the cached session uses one of the current server certificates and carries some peer certificate, and never revalidates that certificate against the CA selected for the requested name. The names must share a server certificate, which is common with wildcard or multi-SAN certificates. TLS 1.3 does not use this cache and is not affected.\n\nThis issue affects OTP from OTP\u00a018.0 before OTP\u00a027.3.4.19, OTP\u00a028.5.0.8, and OTP\u00a029.1.2, corresponding to ssl from 7.0 before 11.2.12.14, 11.6.0.7, and 11.7.8."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-114",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA client holding a certificate issued by the lower-trust SNI name\u0027s CA connects to the higher-trust name as a fully authenticated mTLS peer and obtains whatever that service grants on a successful client-certificate handshake. Multi-tenant or partner-facing listeners that separate trust domains by SNI name are affected.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A client holding a certificate issued by the lower-trust SNI name\u0027s CA connects to the higher-trust name as a fully authenticated mTLS peer and obtains whatever that service grants on a successful client-certificate handshake. Multi-tenant or partner-facing listeners that separate trust domains by SNI name are affected."
                    }
                  ],
                  "value": "A client holding a certificate issued by the lower-trust SNI name\u0027s CA connects to the higher-trust name as a fully authenticated mTLS peer and obtains whatever that service grants on a successful client-certificate handshake. Multi-tenant or partner-facing listeners that separate trust domains by SNI name are affected."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6.3,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "LOW",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-295",
                  "description": "CWE-295 Improper Certificate Validation",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-14T16:55:51.495Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/erlang/otp/security/advisories/GHSA-2g23-cgjh-34gq"
            },
            {
              "name": "EEF CNA record for CVE-2026-105236",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105236.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105236",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105236"
            },
            {
              "name": "Erlang/OTP version ordering",
              "tags": [
                "x_version-scheme"
              ],
              "url": "https://www.erlang.org/doc/system/versions.html#order-of-versions"
            },
            {
              "name": "Introducing commit 4fe38c4 in erlang/otp",
              "tags": [
                "related"
              ],
              "url": "https://github.com/erlang/otp/commit/4fe38c4b8b2c8024afb60990e598ff823743fd54"
            },
            {
              "name": "Fix commit 0c20f36 in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/0c20f3683b3c81fd30b46047195be9b3d2c69d76"
            },
            {
              "name": "Fix commit 4d76adb in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/4d76adbf39d547a931048544df09c3c087e56618"
            },
            {
              "name": "Fix commit 10ee925 in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/10ee925d0fee8413f1170cc21bfcf123c8d22454"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "TLS 1.2 session resumption in ssl crosses SNI virtual-host client-certificate trust boundaries",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cul\u003e\n\u003cli\u003eSet \u003ccode\u003e{reuse_sessions, false}\u003c/code\u003e in each SNI service\u0027s option set, or in the base listener options. The reporter\u0027s proof of concept confirmed that this blocks the cross-name resumption.\u003c/li\u003e\n\u003cli\u003eServe distinct SNI trust domains on separate listen sockets, which gives each its own session cache.\u003c/li\u003e\n\u003cli\u003eRestrict the listener to TLS 1.3 with \u003ccode\u003e{versions, [\u0027tlsv1.3\u0027]}\u003c/code\u003e. TLS 1.3 does not use the session-ID cache.\u003c/li\u003e\n\u003c/ul\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "- Set `{reuse_sessions, false}` in each SNI service\u0027s option set, or in the base listener options. The reporter\u0027s proof of concept confirmed that this blocks the cross-name resumption.\n- Serve distinct SNI trust domains on separate listen sockets, which gives each its own session cache.\n- Restrict the listener to TLS 1.3 with `{versions, [\u0027tlsv1.3\u0027]}`. TLS 1.3 does not use the session-ID cache."
                }
              ],
              "value": "* Set {reuse_sessions, false} in each SNI service\u0027s option set, or in the base listener options. The reporter\u0027s proof of concept confirmed that this blocks the cross-name resumption.\n* Serve distinct SNI trust domains on separate listen sockets, which gives each its own session cache.\n* Restrict the listener to TLS 1.3 with {versions, [\u0027tlsv1.3\u0027]}. TLS 1.3 does not use the session-ID cache."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eCreate one server certificate with subject alternative names \u003ccode\u003elow.test\u003c/code\u003e and \u003ccode\u003ehigh.test\u003c/code\u003e, two self-signed client CAs (\u003ccode\u003elow-ca\u003c/code\u003e, \u003ccode\u003ehigh-ca\u003c/code\u003e), and one client certificate signed by \u003ccode\u003elow-ca\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eStart an \u003ccode\u003essl:listen/2\u003c/code\u003e server on TLS 1.2 with \u003ccode\u003e{verify, verify_none}\u003c/code\u003e and an \u003ccode\u003esni_fun\u003c/code\u003e that returns \u003ccode\u003e{verify, verify_peer}\u003c/code\u003e, \u003ccode\u003e{fail_if_no_peer_cert, true}\u003c/code\u003e and the \u003ccode\u003elow-ca\u003c/code\u003e or \u003ccode\u003ehigh-ca\u003c/code\u003e \u003ccode\u003ecacertfile\u003c/code\u003e for the respective name. Do not set \u003ccode\u003ereuse_sessions\u003c/code\u003e anywhere.\u003c/li\u003e\n\u003cli\u003eConnect with \u003ccode\u003eopenssl s_client -servername high.test -tls1_2 -no_ticket\u003c/code\u003e using the low client certificate. The server rejects the handshake with \u003ccode\u003eunknown_ca\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eConnect with \u003ccode\u003e-servername low.test\u003c/code\u003e and the same certificate, saving the session with \u003ccode\u003e-sess_out\u003c/code\u003e. The handshake succeeds.\u003c/li\u003e\n\u003cli\u003eConnect with \u003ccode\u003e-servername high.test\u003c/code\u003e and \u003ccode\u003e-sess_in\u003c/code\u003e pointing at the saved session. The server resumes the session, \u003ccode\u003essl:connection_information/2\u003c/code\u003e reports \u003ccode\u003esession_resumption\u003c/code\u003e true for \u003ccode\u003ehigh.test\u003c/code\u003e, and the application behind the protected name serves the request to a client whose certificate \u003ccode\u003ehigh-ca\u003c/code\u003e never issued.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Create one server certificate with subject alternative names `low.test` and `high.test`, two self-signed client CAs (`low-ca`, `high-ca`), and one client certificate signed by `low-ca`.\n2. Start an `ssl:listen/2` server on TLS 1.2 with `{verify, verify_none}` and an `sni_fun` that returns `{verify, verify_peer}`, `{fail_if_no_peer_cert, true}` and the `low-ca` or `high-ca` `cacertfile` for the respective name. Do not set `reuse_sessions` anywhere.\n3. Connect with `openssl s_client -servername high.test -tls1_2 -no_ticket` using the low client certificate. The server rejects the handshake with `unknown_ca`.\n4. Connect with `-servername low.test` and the same certificate, saving the session with `-sess_out`. The handshake succeeds.\n5. Connect with `-servername high.test` and `-sess_in` pointing at the saved session. The server resumes the session, `ssl:connection_information/2` reports `session_resumption` true for `high.test`, and the application behind the protected name serves the request to a client whose certificate `high-ca` never issued."
                }
              ],
              "value": "* Create one server certificate with subject alternative names low.test and high.test, two self-signed client CAs (low-ca, high-ca), and one client certificate signed by low-ca.\n* Start an ssl:listen/2 server on TLS 1.2 with {verify, verify_none} and an sni_fun that returns {verify, verify_peer}, {fail_if_no_peer_cert, true} and the low-ca or high-ca cacertfile for the respective name. Do not set reuse_sessions anywhere.\n* Connect with openssl s_client -servername high.test -tls1_2 -no_ticket using the low client certificate. The server rejects the handshake with unknown_ca.\n* Connect with -servername low.test and the same certificate, saving the session with -sess_out. The handshake succeeds.\n* Connect with -servername high.test and -sess_in pointing at the saved session. The server resumes the session, ssl:connection_information/2 reports session_resumption true for high.test, and the application behind the protected name serves the request to a client whose certificate high-ca never issued."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. One session cache per listener.\u003c/strong\u003e \u003ccode\u003etls_socket:listen/3\u003c/code\u003e starts one pre-TLS 1.3 session-ID tracker per listen socket via \u003ccode\u003esession_id_tracker/2\u003c/code\u003e. Every SNI virtual host served from that socket shares it, and the cached \u003ccode\u003e#session{}\u003c/code\u003e record stores the peer and server certificates but no SNI name and no client trust policy.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. The SNI option set inherits session reuse.\u003c/strong\u003e When the ClientHello carries a server name, \u003ccode\u003essl_gen_statem:handle_sni_extension/2\u003c/code\u003e and \u003ccode\u003ehandle_sni_hostname/2\u003c/code\u003e call \u003ccode\u003eupdate_ssl_options_from_sni/2\u003c/code\u003e, which passes the base listener options as fallback into \u003ccode\u003essl_config:update_options/3\u003c/code\u003e. The base listener\u0027s \u003ccode\u003everify_none\u003c/code\u003e has already produced \u003ccode\u003ereuse_sessions = true\u003c/code\u003e. \u003ccode\u003essl_config:opt_reuse_sessions/3\u003c/code\u003e computes a \u003ccode\u003efalse\u003c/code\u003e default for \u003ccode\u003everify_peer\u003c/code\u003e, but \u003ccode\u003eget_opt_bool/4\u003c/code\u003e finds the inherited value and keeps it when the SNI option list does not mention \u003ccode\u003ereuse_sessions\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Resumption does not revalidate the peer certificate.\u003c/strong\u003e On an abbreviated handshake \u003ccode\u003essl_handshake:select_session/8\u003c/code\u003e calls \u003ccode\u003essl_session:server_select_session/5\u003c/code\u003e, which accepts the cached session when \u003ccode\u003essl_session:is_resumable/4\u003c/code\u003e finds that it is marked resumable, that \u003ccode\u003eis_owncert/2\u003c/code\u003e matches one of the current server certificates, that \u003ccode\u003ereusable_options/2\u003c/code\u003e sees any cached peer certificate when \u003ccode\u003efail_if_no_peer_cert\u003c/code\u003e is set, and that the \u003ccode\u003ereuse_session\u003c/code\u003e fun agrees. Nothing compares the ClientHello server name with the name that created the session, and the cached peer certificate is not validated against the CA selected for the new name.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. Result.\u003c/strong\u003e A client whose certificate chains to the lower-trust name\u0027s CA completes a full handshake there, then presents the same session ID with the higher-trust server name. The server resumes the session, reports \u003ccode\u003esession_resumption\u003c/code\u003e as true and the low-trust peer certificate through \u003ccode\u003essl:peercert/1\u003c/code\u003e, and the application behind the protected name sees an authenticated mTLS peer. \u003ccode\u003eis_owncert/2\u003c/code\u003e is why both names must share a server certificate.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. One session cache per listener.** `tls_socket:listen/3` starts one pre-TLS 1.3 session-ID tracker per listen socket via `session_id_tracker/2`. Every SNI virtual host served from that socket shares it, and the cached `#session{}` record stores the peer and server certificates but no SNI name and no client trust policy.\n\n**2. The SNI option set inherits session reuse.** When the ClientHello carries a server name, `ssl_gen_statem:handle_sni_extension/2` and `handle_sni_hostname/2` call `update_ssl_options_from_sni/2`, which passes the base listener options as fallback into `ssl_config:update_options/3`. The base listener\u0027s `verify_none` has already produced `reuse_sessions = true`. `ssl_config:opt_reuse_sessions/3` computes a `false` default for `verify_peer`, but `get_opt_bool/4` finds the inherited value and keeps it when the SNI option list does not mention `reuse_sessions`.\n\n**3. Resumption does not revalidate the peer certificate.** On an abbreviated handshake `ssl_handshake:select_session/8` calls `ssl_session:server_select_session/5`, which accepts the cached session when `ssl_session:is_resumable/4` finds that it is marked resumable, that `is_owncert/2` matches one of the current server certificates, that `reusable_options/2` sees any cached peer certificate when `fail_if_no_peer_cert` is set, and that the `reuse_session` fun agrees. Nothing compares the ClientHello server name with the name that created the session, and the cached peer certificate is not validated against the CA selected for the new name.\n\n**4. Result.** A client whose certificate chains to the lower-trust name\u0027s CA completes a full handshake there, then presents the same session ID with the higher-trust server name. The server resumes the session, reports `session_resumption` as true and the low-trust peer certificate through `ssl:peercert/1`, and the application behind the protected name sees an authenticated mTLS peer. `is_owncert/2` is why both names must share a server certificate."
                }
              ],
              "value": "1. One session cache per listener. tls_socket:listen/3 starts one pre-TLS 1.3 session-ID tracker per listen socket via session_id_tracker/2. Every SNI virtual host served from that socket shares it, and the cached #session{} record stores the peer and server certificates but no SNI name and no client trust policy.\n\n2. The SNI option set inherits session reuse. When the ClientHello carries a server name, ssl_gen_statem:handle_sni_extension/2 and handle_sni_hostname/2 call update_ssl_options_from_sni/2, which passes the base listener options as fallback into ssl_config:update_options/3. The base listener\u0027s verify_none has already produced reuse_sessions = true. ssl_config:opt_reuse_sessions/3 computes a false default for verify_peer, but get_opt_bool/4 finds the inherited value and keeps it when the SNI option list does not mention reuse_sessions.\n\n3. Resumption does not revalidate the peer certificate. On an abbreviated handshake ssl_handshake:select_session/8 calls ssl_session:server_select_session/5, which accepts the cached session when ssl_session:is_resumable/4 finds that it is marked resumable, that is_owncert/2 matches one of the current server certificates, that reusable_options/2 sees any cached peer certificate when fail_if_no_peer_cert is set, and that the reuse_session fun agrees. Nothing compares the ClientHello server name with the name that created the session, and the cached peer certificate is not validated against the CA selected for the new name.\n\n4. Result. A client whose certificate chains to the lower-trust name\u0027s CA completes a full handshake there, then presents the same session ID with the higher-trust server name. The server resumes the session, reports session_resumption as true and the low-trust peer certificate through ssl:peercert/1, and the application behind the protected name sees an authenticated mTLS peer. is_owncert/2 is why both names must share a server certificate."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105236",
        "datePublished": "2026-10-14T16:55:51.495Z",
        "dateReserved": "2026-10-08T14:45:01.411Z",
        "dateUpdated": "2026-10-16T20:09:49.566Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-94200 (GCVE-0-2026-94200)

    Vulnerability from cvelistv5 – Published: 2026-10-14 16:55 – Updated: 2026-10-16 20:09
    VLAI
    Title
    TLS 1.3 client ticket store crashed by NewSessionTicket with empty early_data extension in ssl
    Summary
    Improper Validation of Consistency within Input vulnerability in Erlang OTP ssl (tls_handshake_1_3, ssl_handshake and tls_client_ticket_store modules) allows a malicious TLS 1.3 server to cause a denial of service of the ssl application on the client node. The TLS 1.3 client decodes the extensions of a NewSessionTicket message as if they came from EncryptedExtensions. It therefore accepts a 0-length early_data extension, a form that is only valid in ClientHello and EncryptedExtensions. With {session_tickets, auto}, the client stores the ticket in the node-global tls_client_ticket_store. On the next automatic resumption, tls_handshake_1_3:get_max_early_data/1 crashes the store with a case_clause. Repeated crashes can exceed the restart intensity of the ssl supervisors, which use the rest_for_one strategy, and take down ssl for all connections on the node. This issue affects OTP from OTP 23.3 before OTP 27.3.4.19, OTP 28.5.0.8, and OTP 29.1.2, corresponding to ssl from 10.3 before 11.2.12.14, 11.6.0.7, and 11.7.8.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 20:08 UTC
    CWE
    • CWE-1288 - Improper Validation of Consistency within Input
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    Erlang OTP Affected: 23.3 , < * (otp)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: 10.3 , < * (otp)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Erlang OTP Affected: 0abe6f66db0d295cd06b0efab3bdb3caa2803930 , < * (git)
        cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-94200",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T20:08:52.033542Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T20:09:04.559Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "ssl_handshake",
                "tls_handshake_1_3",
                "tls_client_ticket_store"
              ],
              "packageName": "otp",
              "packageURL": "pkg:software-id/erlang.org/otp",
              "product": "OTP",
              "programFiles": [
                "lib/ssl/src/ssl_handshake.erl",
                "lib/ssl/src/tls_handshake_1_3.erl",
                "lib/ssl/src/tls_client_ticket_store.erl"
              ],
              "programRoutines": [
                {
                  "name": "ssl_handshake:decode_extensions/4"
                },
                {
                  "name": "tls_handshake_1_3:decode_handshake/2"
                },
                {
                  "name": "tls_handshake_1_3:get_max_early_data/1"
                },
                {
                  "name": "tls_client_ticket_store:find_ticket/5"
                },
                {
                  "name": "tls_client_ticket_store:get_tickets/2"
                }
              ],
              "vendor": "Erlang",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "27.3.4.19",
                      "status": "unaffected"
                    },
                    {
                      "at": "28.5.0.8",
                      "status": "unaffected"
                    },
                    {
                      "at": "29.1.2",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "23.3",
                  "versionType": "otp"
                }
              ]
            },
            {
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "ssl_handshake",
                "tls_handshake_1_3",
                "tls_client_ticket_store"
              ],
              "packageName": "ssl",
              "packageURL": "pkg:otp/ssl?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Fotp\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Fotp.git",
              "product": "OTP",
              "programFiles": [
                "src/ssl_handshake.erl",
                "src/tls_handshake_1_3.erl",
                "src/tls_client_ticket_store.erl"
              ],
              "programRoutines": [
                {
                  "name": "ssl_handshake:decode_extensions/4"
                },
                {
                  "name": "tls_handshake_1_3:decode_handshake/2"
                },
                {
                  "name": "tls_handshake_1_3:get_max_early_data/1"
                },
                {
                  "name": "tls_client_ticket_store:find_ticket/5"
                },
                {
                  "name": "tls_client_ticket_store:get_tickets/2"
                }
              ],
              "repo": "https://github.com/erlang/otp",
              "vendor": "Erlang",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "11.2.12.14",
                      "status": "unaffected"
                    },
                    {
                      "at": "11.6.0.7",
                      "status": "unaffected"
                    },
                    {
                      "at": "11.7.8",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "10.3",
                  "versionType": "otp"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "ssl_handshake",
                "tls_handshake_1_3",
                "tls_client_ticket_store"
              ],
              "packageName": "erlang/otp",
              "packageURL": "pkg:github/erlang/otp",
              "product": "OTP",
              "programFiles": [
                "lib/ssl/src/ssl_handshake.erl",
                "lib/ssl/src/tls_handshake_1_3.erl",
                "lib/ssl/src/tls_client_ticket_store.erl"
              ],
              "programRoutines": [
                {
                  "name": "ssl_handshake:decode_extensions/4"
                },
                {
                  "name": "tls_handshake_1_3:decode_handshake/2"
                },
                {
                  "name": "tls_handshake_1_3:get_max_early_data/1"
                },
                {
                  "name": "tls_client_ticket_store:find_ticket/5"
                },
                {
                  "name": "tls_client_ticket_store:get_tickets/2"
                }
              ],
              "repo": "https://github.com/erlang/otp",
              "vendor": "Erlang",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "66febc190e6dc42a5c4aedbfe250bf9a2edc2d5e",
                      "status": "unaffected"
                    },
                    {
                      "at": "00a333bcf2800b69f540369b54167e897dae7219",
                      "status": "unaffected"
                    },
                    {
                      "at": "cd0334854bf3ef56dc16f9fb728efe88fb05c4ac",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "0abe6f66db0d295cd06b0efab3bdb3caa2803930",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eOnly TLS 1.3 clients that set the \u003ccode\u003essl\u003c/code\u003e option \u003ccode\u003e{session_tickets, auto}\u003c/code\u003e are affected. The default value of \u003ccode\u003esession_tickets\u003c/code\u003e is \u003ccode\u003edisabled\u003c/code\u003e, and clients that use \u003ccode\u003emanual\u003c/code\u003e ticket handling do not store tickets in the node-global ticket store. The client must also connect to a server that the attacker controls, or that the attacker can impersonate.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Only TLS 1.3 clients that set the `ssl` option `{session_tickets, auto}` are affected. The default value of `session_tickets` is `disabled`, and clients that use `manual` ticket handling do not store tickets in the node-global ticket store. The client must also connect to a server that the attacker controls, or that the attacker can impersonate."
                }
              ],
              "value": "Only TLS 1.3 clients that set the ssl option {session_tickets, auto} are affected. The default value of session_tickets is disabled, and clients that use manual ticket handling do not store tickets in the node-global ticket store. The client must also connect to a server that the attacker controls, or that the attacker can impersonate."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "27.3.4.19",
                      "versionStartIncluding": "23.3",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "28.5.0.8",
                      "versionStartIncluding": "28.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "29.1.2",
                      "versionStartIncluding": "29.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "reporter",
              "value": "Lukas Backstr\u00f6m / Erlang Solutions"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Ingela Anderton Andin"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-01T08:50:00.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Validation of Consistency within Input vulnerability in Erlang OTP ssl (\u003ccode\u003etls_handshake_1_3\u003c/code\u003e, \u003ccode\u003essl_handshake\u003c/code\u003e and \u003ccode\u003etls_client_ticket_store\u003c/code\u003e modules) allows a malicious TLS 1.3 server to cause a denial of service of the \u003ccode\u003essl\u003c/code\u003e application on the client node.\u003c/p\u003e\n\u003cp\u003eThe TLS 1.3 client decodes the extensions of a \u003ccode\u003eNewSessionTicket\u003c/code\u003e message as if they came from \u003ccode\u003eEncryptedExtensions\u003c/code\u003e. It therefore accepts a 0-length \u003ccode\u003eearly_data\u003c/code\u003e extension, a form that is only valid in \u003ccode\u003eClientHello\u003c/code\u003e and \u003ccode\u003eEncryptedExtensions\u003c/code\u003e. With \u003ccode\u003e{session_tickets, auto}\u003c/code\u003e, the client stores the ticket in the node-global \u003ccode\u003etls_client_ticket_store\u003c/code\u003e. On the next automatic resumption, \u003ccode\u003etls_handshake_1_3:get_max_early_data/1\u003c/code\u003e crashes the store with a \u003ccode\u003ecase_clause\u003c/code\u003e. Repeated crashes can exceed the restart intensity of the \u003ccode\u003essl\u003c/code\u003e supervisors, which use the \u003ccode\u003erest_for_one\u003c/code\u003e strategy, and take down \u003ccode\u003essl\u003c/code\u003e for all connections on the node.\u003c/p\u003e\n\u003cp\u003eThis issue affects OTP from OTP\u0026nbsp;23.3 before OTP\u0026nbsp;27.3.4.19, OTP\u0026nbsp;28.5.0.8, and OTP\u0026nbsp;29.1.2, corresponding to ssl from 10.3 before 11.2.12.14, 11.6.0.7, and 11.7.8.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Validation of Consistency within Input vulnerability in Erlang OTP ssl (`tls_handshake_1_3`, `ssl_handshake` and `tls_client_ticket_store` modules) allows a malicious TLS 1.3 server to cause a denial of service of the `ssl` application on the client node.\n\nThe TLS 1.3 client decodes the extensions of a `NewSessionTicket` message as if they came from `EncryptedExtensions`. It therefore accepts a 0-length `early_data` extension, a form that is only valid in `ClientHello` and `EncryptedExtensions`. With `{session_tickets, auto}`, the client stores the ticket in the node-global `tls_client_ticket_store`. On the next automatic resumption, `tls_handshake_1_3:get_max_early_data/1` crashes the store with a `case_clause`. Repeated crashes can exceed the restart intensity of the `ssl` supervisors, which use the `rest_for_one` strategy, and take down `ssl` for all connections on the node.\n\nThis issue affects OTP from OTP\u00a023.3 before OTP\u00a027.3.4.19, OTP\u00a028.5.0.8, and OTP\u00a029.1.2, corresponding to ssl from 10.3 before 11.2.12.14, 11.6.0.7, and 11.7.8."
                }
              ],
              "value": "Improper Validation of Consistency within Input vulnerability in Erlang OTP ssl (tls_handshake_1_3, ssl_handshake and tls_client_ticket_store modules) allows a malicious TLS 1.3 server to cause a denial of service of the ssl application on the client node.\n\nThe TLS 1.3 client decodes the extensions of a NewSessionTicket message as if they came from EncryptedExtensions. It therefore accepts a 0-length early_data extension, a form that is only valid in ClientHello and EncryptedExtensions. With {session_tickets, auto}, the client stores the ticket in the node-global tls_client_ticket_store. On the next automatic resumption, tls_handshake_1_3:get_max_early_data/1 crashes the store with a case_clause. Repeated crashes can exceed the restart intensity of the ssl supervisors, which use the rest_for_one strategy, and take down ssl for all connections on the node.\n\nThis issue affects OTP from OTP\u00a023.3 before OTP\u00a027.3.4.19, OTP\u00a028.5.0.8, and OTP\u00a029.1.2, corresponding to ssl from 10.3 before 11.2.12.14, 11.6.0.7, and 11.7.8."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-153",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA malicious TLS 1.3 server can crash the client node\u0027s session ticket store, and by repeating the attack it can stop the \u003ccode\u003essl\u003c/code\u003e application for all TLS connections on that node. Only clients that use \u003ccode\u003e{session_tickets, auto}\u003c/code\u003e are affected.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A malicious TLS 1.3 server can crash the client node\u0027s session ticket store, and by repeating the attack it can stop the `ssl` application for all TLS connections on that node. Only clients that use `{session_tickets, auto}` are affected."
                    }
                  ],
                  "value": "A malicious TLS 1.3 server can crash the client node\u0027s session ticket store, and by repeating the attack it can stop the ssl application for all TLS connections on that node. Only clients that use {session_tickets, auto} are affected."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 8.2,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-1288",
                  "description": "CWE-1288 Improper Validation of Consistency within Input",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-14T16:55:23.328Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/erlang/otp/security/advisories/GHSA-m5mh-q53w-67jq"
            },
            {
              "name": "EEF CNA record for CVE-2026-94200",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-94200.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-94200",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-94200"
            },
            {
              "name": "Erlang/OTP version ordering",
              "tags": [
                "x_version-scheme"
              ],
              "url": "https://www.erlang.org/doc/system/versions.html#order-of-versions"
            },
            {
              "name": "Introducing commit 0abe6f6 in erlang/otp",
              "tags": [
                "related"
              ],
              "url": "https://github.com/erlang/otp/commit/0abe6f66db0d295cd06b0efab3bdb3caa2803930"
            },
            {
              "name": "Fix commit 66febc1 in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/66febc190e6dc42a5c4aedbfe250bf9a2edc2d5e"
            },
            {
              "name": "Fix commit 00a333b in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/00a333bcf2800b69f540369b54167e897dae7219"
            },
            {
              "name": "Fix commit cd03348 in erlang/otp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/otp/commit/cd0334854bf3ef56dc16f9fb728efe88fb05c4ac"
            }
          ],
          "source": {
            "discovery": "INTERNAL"
          },
          "title": "TLS 1.3 client ticket store crashed by NewSessionTicket with empty early_data extension in ssl",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eDo not set \u003ccode\u003e{session_tickets, auto}\u003c/code\u003e on TLS 1.3 clients. Use \u003ccode\u003emanual\u003c/code\u003e ticket handling, or keep the default value \u003ccode\u003edisabled\u003c/code\u003e.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Do not set `{session_tickets, auto}` on TLS 1.3 clients. Use `manual` ticket handling, or keep the default value `disabled`."
                }
              ],
              "value": "Do not set {session_tickets, auto} on TLS 1.3 clients. Use manual ticket handling, or keep the default value disabled."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Decoding.\u003c/strong\u003e \u003ccode\u003etls_handshake_1_3:decode_handshake/2\u003c/code\u003e decodes the extensions of a \u003ccode\u003eNewSessionTicket\u003c/code\u003e with the message type \u003ccode\u003eencrypted_extensions\u003c/code\u003e. \u003ccode\u003essl_handshake:decode_extensions/4\u003c/code\u003e does not check the message type for most extensions. A 0-length \u003ccode\u003eearly_data\u003c/code\u003e extension therefore decodes to \u003ccode\u003e#early_data_indication{}\u003c/code\u003e, the \u003ccode\u003eClientHello\u003c/code\u003e and \u003ccode\u003eEncryptedExtensions\u003c/code\u003e form, and not to \u003ccode\u003e#early_data_indication_nst{}\u003c/code\u003e, the 4-byte \u003ccode\u003emax_early_data_size\u003c/code\u003e form that RFC 8446 requires in a \u003ccode\u003eNewSessionTicket\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Storage.\u003c/strong\u003e In \u003ccode\u003eauto\u003c/code\u003e mode the client sends the ticket to the node-global \u003ccode\u003etls_client_ticket_store\u003c/code\u003e gen_server, which keeps it for later resumption.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Crash.\u003c/strong\u003e When a later connection resumes automatically, the store calls \u003ccode\u003etls_handshake_1_3:get_max_early_data/1\u003c/code\u003e on the stored extensions, from \u003ccode\u003emaybe_use_ticket\u003c/code\u003e and \u003ccode\u003eget_tickets\u003c/code\u003e. That function only handles \u003ccode\u003eundefined\u003c/code\u003e and \u003ccode\u003e#early_data_indication_nst{}\u003c/code\u003e, so the store process crashes with a \u003ccode\u003ecase_clause\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. Escalation.\u003c/strong\u003e The store is a \u003ccode\u003epermanent\u003c/code\u003e child of \u003ccode\u003essl_admin_sup\u003c/code\u003e. Each restart of the store also restarts the children after it, because the supervisor uses \u003ccode\u003erest_for_one\u003c/code\u003e. A server that keeps sending the malformed ticket makes the crashes repeat. When the crashes exceed the supervisor restart intensity, the failure goes up through \u003ccode\u003essl_sup\u003c/code\u003e and stops the \u003ccode\u003essl\u003c/code\u003e application for the whole node.\u003c/p\u003e\n\u003cp\u003eThe fix makes \u003ccode\u003essl_handshake:decode_extensions/4\u003c/code\u003e accept each extension form only in the handshake messages where it is permitted, and decodes \u003ccode\u003eNewSessionTicket\u003c/code\u003e extensions with the message type \u003ccode\u003enew_session_ticket\u003c/code\u003e.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Decoding.** `tls_handshake_1_3:decode_handshake/2` decodes the extensions of a `NewSessionTicket` with the message type `encrypted_extensions`. `ssl_handshake:decode_extensions/4` does not check the message type for most extensions. A 0-length `early_data` extension therefore decodes to `#early_data_indication{}`, the `ClientHello` and `EncryptedExtensions` form, and not to `#early_data_indication_nst{}`, the 4-byte `max_early_data_size` form that RFC 8446 requires in a `NewSessionTicket`.\n\n**2. Storage.** In `auto` mode the client sends the ticket to the node-global `tls_client_ticket_store` gen_server, which keeps it for later resumption.\n\n**3. Crash.** When a later connection resumes automatically, the store calls `tls_handshake_1_3:get_max_early_data/1` on the stored extensions, from `maybe_use_ticket` and `get_tickets`. That function only handles `undefined` and `#early_data_indication_nst{}`, so the store process crashes with a `case_clause`.\n\n**4. Escalation.** The store is a `permanent` child of `ssl_admin_sup`. Each restart of the store also restarts the children after it, because the supervisor uses `rest_for_one`. A server that keeps sending the malformed ticket makes the crashes repeat. When the crashes exceed the supervisor restart intensity, the failure goes up through `ssl_sup` and stops the `ssl` application for the whole node.\n\nThe fix makes `ssl_handshake:decode_extensions/4` accept each extension form only in the handshake messages where it is permitted, and decodes `NewSessionTicket` extensions with the message type `new_session_ticket`."
                }
              ],
              "value": "1. Decoding. tls_handshake_1_3:decode_handshake/2 decodes the extensions of a NewSessionTicket with the message type encrypted_extensions. ssl_handshake:decode_extensions/4 does not check the message type for most extensions. A 0-length early_data extension therefore decodes to #early_data_indication{}, the ClientHello and EncryptedExtensions form, and not to #early_data_indication_nst{}, the 4-byte max_early_data_size form that RFC 8446 requires in a NewSessionTicket.\n\n2. Storage. In auto mode the client sends the ticket to the node-global tls_client_ticket_store gen_server, which keeps it for later resumption.\n\n3. Crash. When a later connection resumes automatically, the store calls tls_handshake_1_3:get_max_early_data/1 on the stored extensions, from maybe_use_ticket and get_tickets. That function only handles undefined and #early_data_indication_nst{}, so the store process crashes with a case_clause.\n\n4. Escalation. The store is a permanent child of ssl_admin_sup. Each restart of the store also restarts the children after it, because the supervisor uses rest_for_one. A server that keeps sending the malformed ticket makes the crashes repeat. When the crashes exceed the supervisor restart intensity, the failure goes up through ssl_sup and stops the ssl application for the whole node.\n\nThe fix makes ssl_handshake:decode_extensions/4 accept each extension form only in the handshake messages where it is permitted, and decodes NewSessionTicket extensions with the message type new_session_ticket."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-94200",
        "datePublished": "2026-10-14T16:55:23.328Z",
        "dateReserved": "2026-09-21T12:15:01.330Z",
        "dateUpdated": "2026-10-16T20:09:04.559Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105659 (GCVE-0-2026-105659)

    Vulnerability from cvelistv5 – Published: 2026-10-14 16:54 – Updated: 2026-10-16 20:07
    VLAI
    Title
    HTTP/2 blocked-send deadlines reset on every WINDOW_UPDATE, letting a trickle pin stream processes indefinitely in Bandit
    Summary
    Uncontrolled Resource Consumption vulnerability in mtrudel bandit allows an unauthenticated remote attacker to pin HTTP/2 stream processes indefinitely via a trickle of small WINDOW_UPDATE frames. A stream whose response outruns the connection or stream send window blocks until the peer grants more window, and each block carries a 15 second deadline. Both deadlines restart whenever the peer makes partial progress. Bandit.HTTP2.Connection.finish_data/5 re-queues a partially advanced send in pending_sends with a fresh expires_at, so every WINDOW_UPDATE on stream 0, and every SETTINGS frame, resets the clock. Bandit.HTTP2.Stream.send_data/3 recurses on each stream WINDOW_UPDATE and re-arms a fresh after read_timeout. A peer that grants a few bytes every few seconds keeps the blocked stream process, its Plug state and any resource it holds mid-response alive for as long as it keeps dripping, and repeats this across streams and connections to exhaust server processes and memory. The two paths are independent. The stream-level block is reached on its own by widening the connection window with a large WINDOW_UPDATE on stream 0 and shrinking the per-stream window with a small SETTINGS_INITIAL_WINDOW_SIZE, so closing only the connection-level path leaves it exploitable. This issue affects bandit: from 1.4.0 before 1.13.0.
    SSVC
    Exploitation: poc Automatable: yes Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 20:06 UTC
    CWE
    • CWE-400 - Uncontrolled Resource Consumption
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    mtrudel bandit Affected: 1.4.0 , < 1.13.0 (semver)
        cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
    Create a notification for this product.
    mtrudel bandit Affected: fff06efe3be962b484bd4d3eb339372fda5a231f , < d88b21953c65061f21136ea76a89a4f95a2ac7a1 (git)
        cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-10-14 16:54
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105659",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "yes"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T20:06:31.239514Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T20:07:01.914Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-69m6-wcv4-rwcf"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Bandit.HTTP2.Connection\u0027",
                "\u0027Elixir.Bandit.HTTP2.Stream\u0027"
              ],
              "packageName": "bandit",
              "packageURL": "pkg:hex/bandit",
              "product": "bandit",
              "programFiles": [
                "lib/bandit/http2/connection.ex",
                "lib/bandit/http2/stream.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:finish_data/5"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:do_pending_sends/2"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:handle_frame/3"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Stream\u0027:send_data/3"
                }
              ],
              "repo": "https://github.com/mtrudel/bandit",
              "vendor": "mtrudel",
              "versions": [
                {
                  "lessThan": "1.13.0",
                  "status": "affected",
                  "version": "1.4.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Bandit.HTTP2.Connection\u0027",
                "\u0027Elixir.Bandit.HTTP2.Stream\u0027"
              ],
              "packageName": "mtrudel/bandit",
              "packageURL": "pkg:github/mtrudel/bandit",
              "product": "bandit",
              "programFiles": [
                "lib/bandit/http2/connection.ex",
                "lib/bandit/http2/stream.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:finish_data/5"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:do_pending_sends/2"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:handle_frame/3"
                },
                {
                  "name": "\u0027Elixir.Bandit.HTTP2.Stream\u0027:send_data/3"
                }
              ],
              "repo": "https://github.com/mtrudel/bandit",
              "vendor": "mtrudel",
              "versions": [
                {
                  "lessThan": "d88b21953c65061f21136ea76a89a4f95a2ac7a1",
                  "status": "affected",
                  "version": "fff06efe3be962b484bd4d3eb339372fda5a231f",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.13.0",
                      "versionStartIncluding": "1.4.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Thomas Cioppettini"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Thomas Cioppettini"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Mat Trudel"
            }
          ],
          "dateAssigned": "2026-10-12T16:13:25.000Z",
          "datePublic": "2026-10-14T16:54:46.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUncontrolled Resource Consumption vulnerability in mtrudel bandit allows an unauthenticated remote attacker to pin HTTP/2 stream processes indefinitely via a trickle of small \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e frames. A stream whose response outruns the connection or stream send window blocks until the peer grants more window, and each block carries a 15 second deadline. Both deadlines restart whenever the peer makes partial progress. \u003ccode\u003eBandit.HTTP2.Connection.finish_data/5\u003c/code\u003e re-queues a partially advanced send in \u003ccode\u003epending_sends\u003c/code\u003e with a fresh \u003ccode\u003eexpires_at\u003c/code\u003e, so every \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0, and every \u003ccode\u003eSETTINGS\u003c/code\u003e frame, resets the clock. \u003ccode\u003eBandit.HTTP2.Stream.send_data/3\u003c/code\u003e recurses on each stream \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e and re-arms a fresh \u003ccode\u003eafter read_timeout\u003c/code\u003e. A peer that grants a few bytes every few seconds keeps the blocked stream process, its Plug state and any resource it holds mid-response alive for as long as it keeps dripping, and repeats this across streams and connections to exhaust server processes and memory.\u003c/p\u003e\n\u003cp\u003eThe two paths are independent. The stream-level block is reached on its own by widening the connection window with a large \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0 and shrinking the per-stream window with a small \u003ccode\u003eSETTINGS_INITIAL_WINDOW_SIZE\u003c/code\u003e, so closing only the connection-level path leaves it exploitable.\u003c/p\u003e\n\u003cp\u003eThis issue affects bandit: from 1.4.0 before 1.13.0.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Uncontrolled Resource Consumption vulnerability in mtrudel bandit allows an unauthenticated remote attacker to pin HTTP/2 stream processes indefinitely via a trickle of small `WINDOW_UPDATE` frames. A stream whose response outruns the connection or stream send window blocks until the peer grants more window, and each block carries a 15 second deadline. Both deadlines restart whenever the peer makes partial progress. `Bandit.HTTP2.Connection.finish_data/5` re-queues a partially advanced send in `pending_sends` with a fresh `expires_at`, so every `WINDOW_UPDATE` on stream 0, and every `SETTINGS` frame, resets the clock. `Bandit.HTTP2.Stream.send_data/3` recurses on each stream `WINDOW_UPDATE` and re-arms a fresh `after read_timeout`. A peer that grants a few bytes every few seconds keeps the blocked stream process, its Plug state and any resource it holds mid-response alive for as long as it keeps dripping, and repeats this across streams and connections to exhaust server processes and memory.\n\nThe two paths are independent. The stream-level block is reached on its own by widening the connection window with a large `WINDOW_UPDATE` on stream 0 and shrinking the per-stream window with a small `SETTINGS_INITIAL_WINDOW_SIZE`, so closing only the connection-level path leaves it exploitable.\n\nThis issue affects bandit: from 1.4.0 before 1.13.0."
                }
              ],
              "value": "Uncontrolled Resource Consumption vulnerability in mtrudel bandit allows an unauthenticated remote attacker to pin HTTP/2 stream processes indefinitely via a trickle of small WINDOW_UPDATE frames. A stream whose response outruns the connection or stream send window blocks until the peer grants more window, and each block carries a 15 second deadline. Both deadlines restart whenever the peer makes partial progress. Bandit.HTTP2.Connection.finish_data/5 re-queues a partially advanced send in pending_sends with a fresh expires_at, so every WINDOW_UPDATE on stream 0, and every SETTINGS frame, resets the clock. Bandit.HTTP2.Stream.send_data/3 recurses on each stream WINDOW_UPDATE and re-arms a fresh after read_timeout. A peer that grants a few bytes every few seconds keeps the blocked stream process, its Plug state and any resource it holds mid-response alive for as long as it keeps dripping, and repeats this across streams and connections to exhaust server processes and memory.\n\nThe two paths are independent. The stream-level block is reached on its own by widening the connection window with a large WINDOW_UPDATE on stream 0 and shrinking the per-stream window with a small SETTINGS_INITIAL_WINDOW_SIZE, so closing only the connection-level path leaves it exploitable.\n\nThis issue affects bandit: from 1.4.0 before 1.13.0."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-227",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA remote, unauthenticated peer requests a response larger than a send window, then drips tiny \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e frames every few seconds. Each blocked stream process, with its Plug state and any upstream connection or file handle it holds, stays alive for as long as the trickle continues. Repeated across many streams and connections this exhausts server processes and memory.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A remote, unauthenticated peer requests a response larger than a send window, then drips tiny `WINDOW_UPDATE` frames every few seconds. Each blocked stream process, with its Plug state and any upstream connection or file handle it holds, stays alive for as long as the trickle continues. Repeated across many streams and connections this exhausts server processes and memory."
                    }
                  ],
                  "value": "A remote, unauthenticated peer requests a response larger than a send window, then drips tiny WINDOW_UPDATE frames every few seconds. Each blocked stream process, with its Plug state and any upstream connection or file handle it holds, stays alive for as long as the trickle continues. Repeated across many streams and connections this exhausts server processes and memory."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "NONE",
                "attackVector": "NETWORK",
                "baseScore": 8.7,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-400",
                  "description": "CWE-400 Uncontrolled Resource Consumption",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-15T18:37:59.434Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-69m6-wcv4-rwcf"
            },
            {
              "name": "EEF CNA record for CVE-2026-105659",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105659.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105659",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105659"
            },
            {
              "name": "Introducing commit fff06ef in mtrudel/bandit",
              "tags": [
                "related"
              ],
              "url": "https://github.com/mtrudel/bandit/commit/fff06efe3be962b484bd4d3eb339372fda5a231f"
            },
            {
              "name": "Fix commit d88b219 in mtrudel/bandit",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/mtrudel/bandit/commit/d88b21953c65061f21136ea76a89a4f95a2ac7a1"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "HTTP/2 blocked-send deadlines reset on every WINDOW_UPDATE, letting a trickle pin stream processes indefinitely in Bandit",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eDisable HTTP/2 with \u003ccode\u003ehttp_2_options: [enabled: false]\u003c/code\u003e, which removes both vulnerable paths. Alternatively, terminate HTTP/2 at a reverse proxy that enforces its own send timeouts and speaks HTTP/1 to Bandit.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Disable HTTP/2 with `http_2_options: [enabled: false]`, which removes both vulnerable paths. Alternatively, terminate HTTP/2 at a reverse proxy that enforces its own send timeouts and speaks HTTP/1 to Bandit."
                }
              ],
              "value": "Disable HTTP/2 with http_2_options: [enabled: false], which removes both vulnerable paths. Alternatively, terminate HTTP/2 at a reverse proxy that enforces its own send timeouts and speaks HTTP/1 to Bandit."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eConnection-level path:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eOpen a stream whose response exceeds the connection send window (65,535 bytes by default).\u003c/li\u003e\n\u003cli\u003eThe server sends the window\u0027s worth and queues the remainder in \u003ccode\u003epending_sends\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e for stream 0 with an increment of 1 every few seconds, or an empty \u003ccode\u003eSETTINGS\u003c/code\u003e frame. Each one re-queues the remainder with a fresh deadline, so \u003ccode\u003eexpire_pending_sends/1\u003c/code\u003e never expires it.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eStream-level path:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eSend a large \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e for stream 0 so the connection window never blocks.\u003c/li\u003e\n\u003cli\u003eAdvertise a small \u003ccode\u003eSETTINGS_INITIAL_WINDOW_SIZE\u003c/code\u003e so each stream window is tiny.\u003c/li\u003e\n\u003cli\u003eRequest a response larger than the stream window. The server sends the window\u0027s worth and blocks in the recursive \u003ccode\u003ereceive\u003c/code\u003e in \u003ccode\u003esend_data/3\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e for the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a fresh \u003ccode\u003eread_timeout\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRepeat across streams, up to \u003ccode\u003emax_concurrent_streams\u003c/code\u003e per connection, and across connections.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Connection-level path:\n\n1. Open a stream whose response exceeds the connection send window (65,535 bytes by default).\n2. The server sends the window\u0027s worth and queues the remainder in `pending_sends`.\n3. Send `WINDOW_UPDATE` for stream 0 with an increment of 1 every few seconds, or an empty `SETTINGS` frame. Each one re-queues the remainder with a fresh deadline, so `expire_pending_sends/1` never expires it.\n\nStream-level path:\n\n1. Send a large `WINDOW_UPDATE` for stream 0 so the connection window never blocks.\n2. Advertise a small `SETTINGS_INITIAL_WINDOW_SIZE` so each stream window is tiny.\n3. Request a response larger than the stream window. The server sends the window\u0027s worth and blocks in the recursive `receive` in `send_data/3`.\n4. Send `WINDOW_UPDATE` for the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a fresh `read_timeout`.\n5. Repeat across streams, up to `max_concurrent_streams` per connection, and across connections."
                }
              ],
              "value": "Connection-level path:\n\n* Open a stream whose response exceeds the connection send window (65,535 bytes by default).\n* The server sends the window\u0027s worth and queues the remainder in pending_sends.\n* Send WINDOW_UPDATE for stream 0 with an increment of 1 every few seconds, or an empty SETTINGS frame. Each one re-queues the remainder with a fresh deadline, so expire_pending_sends/1 never expires it.\n\nStream-level path:\n\n* Send a large WINDOW_UPDATE for stream 0 so the connection window never blocks.\n* Advertise a small SETTINGS_INITIAL_WINDOW_SIZE so each stream window is tiny.\n* Request a response larger than the stream window. The server sends the window\u0027s worth and blocks in the recursive receive in send_data/3.\n* Send WINDOW_UPDATE for the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a fresh read_timeout.\n* Repeat across streams, up to max_concurrent_streams per connection, and across connections."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Connection-level block\u003c/strong\u003e (\u003ccode\u003elib/bandit/http2/connection.ex\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003eA send that outruns the connection send window is queued in \u003ccode\u003epending_sends\u003c/code\u003e with an \u003ccode\u003eexpires_at\u003c/code\u003e deadline computed in \u003ccode\u003efinish_data/5\u003c/code\u003e as now plus \u003ccode\u003e@pending_send_timeout\u003c/code\u003e (15 seconds). \u003ccode\u003eexpire_pending_sends/1\u003c/code\u003e drops any queued send whose deadline has passed whenever data arrives on the connection. \u003ccode\u003edo_pending_sends/2\u003c/code\u003e runs on every \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0 and on every \u003ccode\u003eSETTINGS\u003c/code\u003e frame (including an empty one, through \u003ccode\u003ehandle_frame/3\u003c/code\u003e). When the granted window is smaller than the queued remainder, the leftover is re-queued through \u003ccode\u003efinish_data/5\u003c/code\u003e, which recomputes \u003ccode\u003eexpires_at\u003c/code\u003e from the current time instead of preserving the original deadline. Each partial advance therefore restarts the timer, and an empty \u003ccode\u003eSETTINGS\u003c/code\u003e frame restarts it without granting any window at all.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Stream-level block\u003c/strong\u003e (\u003ccode\u003elib/bandit/http2/stream.ex\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esend_data/3\u003c/code\u003e runs in the stream process. When the stream send window is exhausted it enters a \u003ccode\u003ereceive\u003c/code\u003e that waits for \u003ccode\u003e{:bandit, {:send_window_update, delta}}\u003c/code\u003e with \u003ccode\u003eafter stream.read_timeout\u003c/code\u003e (the struct default of 15 seconds, never overridden in \u003ccode\u003einit/5\u003c/code\u003e). On each stream \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e it applies the delta and recurses into \u003ccode\u003esend_data/3\u003c/code\u003e with the remainder, which re-enters the same \u003ccode\u003ereceive\u003c/code\u003e with a fresh \u003ccode\u003eafter\u003c/code\u003e timeout. Each partial advance restarts the 15 second wait.\u003c/p\u003e\n\u003cp\u003eFor the stream-level block to be the one stuck, the stream window must be exhausted while the connection window has room. The peer controls both: a large \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0 opens the connection window, and a small \u003ccode\u003eSETTINGS_INITIAL_WINDOW_SIZE\u003c/code\u003e shrinks every stream window. Closing only the connection-level path therefore leaves the stream-level path fully exploitable.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Effect\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe trickle also keeps the transport-level read timeout from firing, since the connection continuously receives valid frames. The blocked stream process, its Plug state, and anything it holds mid-response stay retained for as long as the peer keeps dripping, instead of being released after 15 seconds.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Connection-level block** (`lib/bandit/http2/connection.ex`)\n\nA send that outruns the connection send window is queued in `pending_sends` with an `expires_at` deadline computed in `finish_data/5` as now plus `@pending_send_timeout` (15 seconds). `expire_pending_sends/1` drops any queued send whose deadline has passed whenever data arrives on the connection. `do_pending_sends/2` runs on every `WINDOW_UPDATE` on stream 0 and on every `SETTINGS` frame (including an empty one, through `handle_frame/3`). When the granted window is smaller than the queued remainder, the leftover is re-queued through `finish_data/5`, which recomputes `expires_at` from the current time instead of preserving the original deadline. Each partial advance therefore restarts the timer, and an empty `SETTINGS` frame restarts it without granting any window at all.\n\n**2. Stream-level block** (`lib/bandit/http2/stream.ex`)\n\n`send_data/3` runs in the stream process. When the stream send window is exhausted it enters a `receive` that waits for `{:bandit, {:send_window_update, delta}}` with `after stream.read_timeout` (the struct default of 15 seconds, never overridden in `init/5`). On each stream `WINDOW_UPDATE` it applies the delta and recurses into `send_data/3` with the remainder, which re-enters the same `receive` with a fresh `after` timeout. Each partial advance restarts the 15 second wait.\n\nFor the stream-level block to be the one stuck, the stream window must be exhausted while the connection window has room. The peer controls both: a large `WINDOW_UPDATE` on stream 0 opens the connection window, and a small `SETTINGS_INITIAL_WINDOW_SIZE` shrinks every stream window. Closing only the connection-level path therefore leaves the stream-level path fully exploitable.\n\n**3. Effect**\n\nThe trickle also keeps the transport-level read timeout from firing, since the connection continuously receives valid frames. The blocked stream process, its Plug state, and anything it holds mid-response stay retained for as long as the peer keeps dripping, instead of being released after 15 seconds."
                }
              ],
              "value": "1. Connection-level block (lib/bandit/http2/connection.ex)\n\nA send that outruns the connection send window is queued in pending_sends with an expires_at deadline computed in finish_data/5 as now plus @pending_send_timeout (15 seconds). expire_pending_sends/1 drops any queued send whose deadline has passed whenever data arrives on the connection. do_pending_sends/2 runs on every WINDOW_UPDATE on stream 0 and on every SETTINGS frame (including an empty one, through handle_frame/3). When the granted window is smaller than the queued remainder, the leftover is re-queued through finish_data/5, which recomputes expires_at from the current time instead of preserving the original deadline. Each partial advance therefore restarts the timer, and an empty SETTINGS frame restarts it without granting any window at all.\n\n2. Stream-level block (lib/bandit/http2/stream.ex)\n\nsend_data/3 runs in the stream process. When the stream send window is exhausted it enters a receive that waits for {:bandit, {:send_window_update, delta}} with after stream.read_timeout (the struct default of 15 seconds, never overridden in init/5). On each stream WINDOW_UPDATE it applies the delta and recurses into send_data/3 with the remainder, which re-enters the same receive with a fresh after timeout. Each partial advance restarts the 15 second wait.\n\nFor the stream-level block to be the one stuck, the stream window must be exhausted while the connection window has room. The peer controls both: a large WINDOW_UPDATE on stream 0 opens the connection window, and a small SETTINGS_INITIAL_WINDOW_SIZE shrinks every stream window. Closing only the connection-level path therefore leaves the stream-level path fully exploitable.\n\n3. Effect\n\nThe trickle also keeps the transport-level read timeout from firing, since the connection continuously receives valid frames. The blocked stream process, its Plug state, and anything it holds mid-response stay retained for as long as the peer keeps dripping, instead of being released after 15 seconds."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105659",
        "datePublished": "2026-10-14T16:54:46.484Z",
        "dateReserved": "2026-10-12T10:00:01.326Z",
        "dateUpdated": "2026-10-16T20:07:01.914Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-103061 (GCVE-0-2026-103061)

    Vulnerability from cvelistv5 – Published: 2026-10-12 18:23 – Updated: 2026-10-16 17:49
    VLAI
    Title
    Path traversal in Gleam via unvalidated package application names
    Summary
    Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in gleam-lang gleam allows a malicious package publisher to delete a directory outside the project's build directory when a developer builds a project. A Hex package release's metadata carries an app field with the release's OTP application name. Gleam reads this field from the hex.pm release API when it downloads dependencies and uses it as the package's build directory name without validating it (hex_manifest_package in compiler-cli/src/dependencies.rs, ProjectPaths::build_directory_for_package). When a dependency fails to compile, Gleam deletes that directory with remove_dir_all. hex.pm didn't restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as ../../../../target or an absolute path. When a project depends on such a release directly or transitively and the release fails to compile, gleam build, gleam check, gleam run, gleam test or gleam export deletes the directory the name resolves to. For a pure Gleam package no publisher code runs. This issue affects gleam: from 0.32.4 before 1.19.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:49 UTC
    CWE
    • CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    Gleam Gleam Affected: 0.32.4 , < 1.19.2 (semver)
        cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Gleam Gleam Affected: v0.32.4-elixir , < v1.19.2-elixir (other)
    Affected: v0.32.4-erlang , < v1.19.2-erlang (other)
    Affected: v0.32.4-node , < v1.19.2-node (other)
    Affected: v0.32.4-node-slim , < v1.19.2-node-slim (other)
    Affected: v0.32.4-elixir-slim , < v1.19.2-elixir-slim (other)
    Affected: v0.32.4-erlang-slim , < v1.19.2-erlang-slim (other)
    Affected: v0.32.4-erlang-alpine , < v1.19.2-erlang-alpine (other)
    Affected: v0.32.4-elixir-alpine , < v1.19.2-elixir-alpine (other)
    Affected: v0.32.4-node-alpine , < v1.19.2-node-alpine (other)
    Affected: v0.32.4-scratch , < v1.19.2-scratch (other)
        cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Gleam Gleam Affected: d13f27ee80aae7a521d2e9433c4deaf53f892574 , < * (git)
        cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-103061",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:49:26.374732Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:49:42.484Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "gleam_cli::dependencies",
                "gleam_core::manifest",
                "gleam_core::paths",
                "gleam_core::build::project_compiler",
                "gleam_cli::fs"
              ],
              "packageName": "gleam",
              "packageURL": "pkg:software-id/gleam.run/gleam",
              "product": "Gleam",
              "programFiles": [
                "compiler-cli/src/dependencies.rs",
                "compiler-core/src/manifest.rs",
                "compiler-core/src/paths.rs",
                "compiler-core/src/build/project_compiler.rs",
                "compiler-cli/src/fs.rs"
              ],
              "programRoutines": [
                {
                  "name": "gleam_cli::dependencies::hex_manifest_package"
                },
                {
                  "name": "gleam_core::manifest::ManifestPackage::application_name"
                },
                {
                  "name": "gleam_core::paths::ProjectPaths::build_directory_for_package"
                },
                {
                  "name": "gleam_core::build::project_compiler::ProjectCompiler::load_cache_or_compile_package"
                },
                {
                  "name": "gleam_cli::fs::delete_directory"
                }
              ],
              "vendor": "Gleam",
              "versions": [
                {
                  "lessThan": "1.19.2",
                  "status": "affected",
                  "version": "0.32.4",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://ghcr.io",
              "cpes": [
                "cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "gleam_cli::dependencies",
                "gleam_core::manifest",
                "gleam_core::paths",
                "gleam_core::build::project_compiler",
                "gleam_cli::fs"
              ],
              "packageName": "gleam-lang/gleam",
              "packageURL": "pkg:oci/gleam?repository_url=ghcr.io%2Fgleam-lang",
              "product": "Gleam",
              "programFiles": [
                "compiler-cli/src/dependencies.rs",
                "compiler-core/src/manifest.rs",
                "compiler-core/src/paths.rs",
                "compiler-core/src/build/project_compiler.rs",
                "compiler-cli/src/fs.rs"
              ],
              "programRoutines": [
                {
                  "name": "gleam_cli::dependencies::hex_manifest_package"
                },
                {
                  "name": "gleam_core::manifest::ManifestPackage::application_name"
                },
                {
                  "name": "gleam_core::paths::ProjectPaths::build_directory_for_package"
                },
                {
                  "name": "gleam_core::build::project_compiler::ProjectCompiler::load_cache_or_compile_package"
                },
                {
                  "name": "gleam_cli::fs::delete_directory"
                }
              ],
              "vendor": "Gleam",
              "versions": [
                {
                  "lessThan": "v1.19.2-elixir",
                  "status": "affected",
                  "version": "v0.32.4-elixir",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-erlang",
                  "status": "affected",
                  "version": "v0.32.4-erlang",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-node",
                  "status": "affected",
                  "version": "v0.32.4-node",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-node-slim",
                  "status": "affected",
                  "version": "v0.32.4-node-slim",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-elixir-slim",
                  "status": "affected",
                  "version": "v0.32.4-elixir-slim",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-erlang-slim",
                  "status": "affected",
                  "version": "v0.32.4-erlang-slim",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-erlang-alpine",
                  "status": "affected",
                  "version": "v0.32.4-erlang-alpine",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-elixir-alpine",
                  "status": "affected",
                  "version": "v0.32.4-elixir-alpine",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-node-alpine",
                  "status": "affected",
                  "version": "v0.32.4-node-alpine",
                  "versionType": "other"
                },
                {
                  "lessThan": "v1.19.2-scratch",
                  "status": "affected",
                  "version": "v0.32.4-scratch",
                  "versionType": "other"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "gleam_cli::dependencies",
                "gleam_core::manifest",
                "gleam_core::paths",
                "gleam_core::build::project_compiler",
                "gleam_cli::fs"
              ],
              "packageName": "gleam-lang/gleam",
              "packageURL": "pkg:github/gleam-lang/gleam",
              "product": "Gleam",
              "programFiles": [
                "compiler-cli/src/dependencies.rs",
                "compiler-core/src/manifest.rs",
                "compiler-core/src/paths.rs",
                "compiler-core/src/build/project_compiler.rs",
                "compiler-cli/src/fs.rs"
              ],
              "programRoutines": [
                {
                  "name": "gleam_cli::dependencies::hex_manifest_package"
                },
                {
                  "name": "gleam_core::manifest::ManifestPackage::application_name"
                },
                {
                  "name": "gleam_core::paths::ProjectPaths::build_directory_for_package"
                },
                {
                  "name": "gleam_core::build::project_compiler::ProjectCompiler::load_cache_or_compile_package"
                },
                {
                  "name": "gleam_cli::fs::delete_directory"
                }
              ],
              "repo": "https://github.com/gleam-lang/gleam",
              "vendor": "Gleam",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "d15c981fac6e533d70ac8ee89a6ef4dbf139adc3",
                      "status": "unaffected"
                    },
                    {
                      "at": "54bbb2b98af1505b6eed93c45b1936fc76412e5c",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "d13f27ee80aae7a521d2e9433c4deaf53f892574",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects projects whose dependency tree, directly or transitively, contains a hex.pm package release whose OTP application name is a path, when the project is built with \u003ccode\u003egleam build\u003c/code\u003e, \u003ccode\u003egleam check\u003c/code\u003e, \u003ccode\u003egleam run\u003c/code\u003e, \u003ccode\u003egleam test\u003c/code\u003e or \u003ccode\u003egleam export\u003c/code\u003e. \u003ccode\u003egleam deps download\u003c/code\u003e alone doesn\u0027t reach the delete. Gleam only uses hex.pm, which rejects such application names when a release is published since 2026-10-12, so only releases published before then can have one.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects projects whose dependency tree, directly or transitively, contains a hex.pm package release whose OTP application name is a path, when the project is built with `gleam build`, `gleam check`, `gleam run`, `gleam test` or `gleam export`. `gleam deps download` alone doesn\u0027t reach the delete. Gleam only uses hex.pm, which rejects such application names when a release is published since 2026-10-12, so only releases published before then can have one."
                }
              ],
              "value": "Affects projects whose dependency tree, directly or transitively, contains a hex.pm package release whose OTP application name is a path, when the project is built with gleam build, gleam check, gleam run, gleam test or gleam export. gleam deps download alone doesn\u0027t reach the delete. Gleam only uses hex.pm, which rejects such application names when a release is published since 2026-10-12, so only releases published before then can have one."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:gleam-lang:gleam:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.19.2",
                      "versionStartIncluding": "0.32.4",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Louis Pilfold"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-07T22:06:26.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in gleam-lang gleam allows a malicious package publisher to delete a directory outside the project\u0027s build directory when a developer builds a project.\u003c/p\u003e\n\u003cp\u003eA Hex package release\u0027s metadata carries an \u003ccode\u003eapp\u003c/code\u003e field with the release\u0027s OTP application name. Gleam reads this field from the hex.pm release API when it downloads dependencies and uses it as the package\u0027s build directory name without validating it (\u003ccode\u003ehex_manifest_package\u003c/code\u003e in \u003ccode\u003ecompiler-cli/src/dependencies.rs\u003c/code\u003e, \u003ccode\u003eProjectPaths::build_directory_for_package\u003c/code\u003e). When a dependency fails to compile, Gleam deletes that directory with \u003ccode\u003eremove_dir_all\u003c/code\u003e. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as \u003ccode\u003e../../../../target\u003c/code\u003e or an absolute path. When a project depends on such a release directly or transitively and the release fails to compile, \u003ccode\u003egleam build\u003c/code\u003e, \u003ccode\u003egleam check\u003c/code\u003e, \u003ccode\u003egleam run\u003c/code\u003e, \u003ccode\u003egleam test\u003c/code\u003e or \u003ccode\u003egleam export\u003c/code\u003e deletes the directory the name resolves to. For a pure Gleam package no publisher code runs.\u003c/p\u003e\n\u003cp\u003eThis issue affects gleam: from 0.32.4 before 1.19.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in gleam-lang gleam allows a malicious package publisher to delete a directory outside the project\u0027s build directory when a developer builds a project.\n\nA Hex package release\u0027s metadata carries an `app` field with the release\u0027s OTP application name. Gleam reads this field from the hex.pm release API when it downloads dependencies and uses it as the package\u0027s build directory name without validating it (`hex_manifest_package` in `compiler-cli/src/dependencies.rs`, `ProjectPaths::build_directory_for_package`). When a dependency fails to compile, Gleam deletes that directory with `remove_dir_all`. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as `../../../../target` or an absolute path. When a project depends on such a release directly or transitively and the release fails to compile, `gleam build`, `gleam check`, `gleam run`, `gleam test` or `gleam export` deletes the directory the name resolves to. For a pure Gleam package no publisher code runs.\n\nThis issue affects gleam: from 0.32.4 before 1.19.2."
                }
              ],
              "value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in gleam-lang gleam allows a malicious package publisher to delete a directory outside the project\u0027s build directory when a developer builds a project.\n\nA Hex package release\u0027s metadata carries an app field with the release\u0027s OTP application name. Gleam reads this field from the hex.pm release API when it downloads dependencies and uses it as the package\u0027s build directory name without validating it (hex_manifest_package in compiler-cli/src/dependencies.rs, ProjectPaths::build_directory_for_package). When a dependency fails to compile, Gleam deletes that directory with remove_dir_all. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as ../../../../target or an absolute path. When a project depends on such a release directly or transitively and the release fails to compile, gleam build, gleam check, gleam run, gleam test or gleam export deletes the directory the name resolves to. For a pure Gleam package no publisher code runs.\n\nThis issue affects gleam: from 0.32.4 before 1.19.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-126",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA publisher of a package in a project\u0027s dependency tree can make a Gleam build delete a directory that the developer\u0027s account can write, outside the project\u0027s build directory or anywhere on the filesystem when the application name is an absolute path. For a pure Gleam package this happens without running any of the publisher\u0027s code. Developers and CI jobs that build projects with Gleam are affected; \u003ccode\u003egleam deps download\u003c/code\u003e alone isn\u0027t.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A publisher of a package in a project\u0027s dependency tree can make a Gleam build delete a directory that the developer\u0027s account can write, outside the project\u0027s build directory or anywhere on the filesystem when the application name is an absolute path. For a pure Gleam package this happens without running any of the publisher\u0027s code. Developers and CI jobs that build projects with Gleam are affected; `gleam deps download` alone isn\u0027t."
                    }
                  ],
                  "value": "A publisher of a package in a project\u0027s dependency tree can make a Gleam build delete a directory that the developer\u0027s account can write, outside the project\u0027s build directory or anywhere on the filesystem when the application name is an absolute path. For a pure Gleam package this happens without running any of the publisher\u0027s code. Developers and CI jobs that build projects with Gleam are affected; gleam deps download alone isn\u0027t."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 5.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-22",
                  "description": "CWE-22 Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T18:23:41.507Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/gleam-lang/gleam/security/advisories/GHSA-jhcr-c7qm-8589"
            },
            {
              "name": "EEF CNA record for CVE-2026-103061",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-103061.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-103061",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-103061"
            },
            {
              "name": "Introducing commit d13f27e in gleam-lang/gleam",
              "tags": [
                "related"
              ],
              "url": "https://github.com/gleam-lang/gleam/commit/d13f27ee80aae7a521d2e9433c4deaf53f892574"
            },
            {
              "name": "Fix commit d15c981 in gleam-lang/gleam",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/gleam-lang/gleam/commit/d15c981fac6e533d70ac8ee89a6ef4dbf139adc3"
            },
            {
              "name": "Fix commit 54bbb2b in gleam-lang/gleam",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/gleam-lang/gleam/commit/54bbb2b98af1505b6eed93c45b1936fc76412e5c"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Gleam to 1.19.2 or later. Gleam then rejects a hex.pm release whose OTP application name, or the application name it gives a dependency, isn\u0027t a lowercase letter followed by lowercase letters, digits and \u003ccode\u003e_\u003c/code\u003e.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Gleam to 1.19.2 or later. Gleam then rejects a hex.pm release whose OTP application name, or the application name it gives a dependency, isn\u0027t a lowercase letter followed by lowercase letters, digits and `_`."
                }
              ],
              "value": "Upgrade Gleam to 1.19.2 or later. Gleam then rejects a hex.pm release whose OTP application name, or the application name it gives a dependency, isn\u0027t a lowercase letter followed by lowercase letters, digits and _."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Path traversal in Gleam via unvalidated package application names",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eNot reproduced end to end, because Gleam only uses hex.pm and has no setting for another repository. A unit test calling \u003ccode\u003eProjectPaths::build_directory_for_package\u003c/code\u003e with the application names \u003ccode\u003e../../../../canary\u003c/code\u003e and an absolute path computed build directories outside the project, and \u003ccode\u003estd::fs::remove_dir_all\u003c/code\u003e, the call behind \u003ccode\u003edelete_directory\u003c/code\u003e, deleted a canary directory placed there. The chain from \u003ccode\u003emeta.app\u003c/code\u003e to the delete was established by reading the source.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Not reproduced end to end, because Gleam only uses hex.pm and has no setting for another repository. A unit test calling `ProjectPaths::build_directory_for_package` with the application names `../../../../canary` and an absolute path computed build directories outside the project, and `std::fs::remove_dir_all`, the call behind `delete_directory`, deleted a canary directory placed there. The chain from `meta.app` to the delete was established by reading the source."
                }
              ],
              "value": "Not reproduced end to end, because Gleam only uses hex.pm and has no setting for another repository. A unit test calling ProjectPaths::build_directory_for_package with the application names ../../../../canary and an absolute path computed build directories outside the project, and std::fs::remove_dir_all, the call behind delete_directory, deleted a canary directory placed there. The chain from meta.app to the delete was established by reading the source."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003ehex_manifest_package\u003c/code\u003e (\u003ccode\u003ecompiler-cli/src/dependencies.rs\u003c/code\u003e) sets the manifest package\u0027s \u003ccode\u003eotp_app\u003c/code\u003e to \u003ccode\u003emeta.app\u003c/code\u003e from \u003ccode\u003eGET packages/\u0026lt;name\u0026gt;/releases/\u0026lt;version\u0026gt;\u003c/code\u003e without validation. hex.pm doesn\u0027t restrict this field to a directory name. The signed registry record\u0027s dependency \u003ccode\u003eapp\u003c/code\u003e label is decoded but never used as a path.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eManifestPackage::application_name\u003c/code\u003e (\u003ccode\u003ecompiler-core/src/manifest.rs\u003c/code\u003e) returns \u003ccode\u003eotp_app\u003c/code\u003e when it is set.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eProjectPaths::build_directory_for_package\u003c/code\u003e (\u003ccode\u003ecompiler-core/src/paths.rs\u003c/code\u003e) joins the application name onto the target\u0027s build directory. A \u003ccode\u003e..\u003c/code\u003e component escapes the build directory, and an absolute path replaces the whole path.\u003c/li\u003e\n\u003cli\u003eWhen a dependency fails to compile, \u003ccode\u003eProjectCompiler::load_cache_or_compile_package\u003c/code\u003e (\u003ccode\u003ecompiler-core/src/build/project_compiler.rs\u003c/code\u003e) calls \u003ccode\u003edelete_directory\u003c/code\u003e on that path, which runs \u003ccode\u003estd::fs::remove_dir_all\u003c/code\u003e (\u003ccode\u003ecompiler-cli/src/fs.rs\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003eThe manifest built by \u003ccode\u003edownload_dependencies\u003c/code\u003e is passed to \u003ccode\u003ebuild::main\u003c/code\u003e in the same run, so the name reaches compilation without a \u003ccode\u003emanifest.toml\u003c/code\u003e round trip. \u003ccode\u003eotp_app\u003c/code\u003e is validated against \u003ccode\u003e^[a-z][a-z0-9_]*$\u003c/code\u003e only when \u003ccode\u003emanifest.toml\u003c/code\u003e is read from disk (commit 2dc0467, first released in v1.16.0), which rejects a stored malicious manifest on a later run but not the first run that produces it.\u003c/li\u003e\n\u003cli\u003eExtraction uses the package name, which hex.pm validates, so the effect is a directory delete without an overwrite. For a package with \u003ccode\u003ebuild_tools = [\"gleam\"]\u003c/code\u003e no publisher code runs, so source that fails to compile or a malformed \u003ccode\u003egleam.toml\u003c/code\u003e triggers the delete. For rebar3 and Mix dependencies, publisher code runs anyway.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `hex_manifest_package` (`compiler-cli/src/dependencies.rs`) sets the manifest package\u0027s `otp_app` to `meta.app` from `GET packages/\u003cname\u003e/releases/\u003cversion\u003e` without validation. hex.pm doesn\u0027t restrict this field to a directory name. The signed registry record\u0027s dependency `app` label is decoded but never used as a path.\n2. `ManifestPackage::application_name` (`compiler-core/src/manifest.rs`) returns `otp_app` when it is set.\n3. `ProjectPaths::build_directory_for_package` (`compiler-core/src/paths.rs`) joins the application name onto the target\u0027s build directory. A `..` component escapes the build directory, and an absolute path replaces the whole path.\n4. When a dependency fails to compile, `ProjectCompiler::load_cache_or_compile_package` (`compiler-core/src/build/project_compiler.rs`) calls `delete_directory` on that path, which runs `std::fs::remove_dir_all` (`compiler-cli/src/fs.rs`).\n5. The manifest built by `download_dependencies` is passed to `build::main` in the same run, so the name reaches compilation without a `manifest.toml` round trip. `otp_app` is validated against `^[a-z][a-z0-9_]*$` only when `manifest.toml` is read from disk (commit 2dc0467, first released in v1.16.0), which rejects a stored malicious manifest on a later run but not the first run that produces it.\n6. Extraction uses the package name, which hex.pm validates, so the effect is a directory delete without an overwrite. For a package with `build_tools = [\"gleam\"]` no publisher code runs, so source that fails to compile or a malformed `gleam.toml` triggers the delete. For rebar3 and Mix dependencies, publisher code runs anyway."
                }
              ],
              "value": "* hex_manifest_package (compiler-cli/src/dependencies.rs) sets the manifest package\u0027s otp_app to meta.app from GET packages/\u003cname\u003e/releases/\u003cversion\u003e without validation. hex.pm doesn\u0027t restrict this field to a directory name. The signed registry record\u0027s dependency app label is decoded but never used as a path.\n* ManifestPackage::application_name (compiler-core/src/manifest.rs) returns otp_app when it is set.\n* ProjectPaths::build_directory_for_package (compiler-core/src/paths.rs) joins the application name onto the target\u0027s build directory. A .. component escapes the build directory, and an absolute path replaces the whole path.\n* When a dependency fails to compile, ProjectCompiler::load_cache_or_compile_package (compiler-core/src/build/project_compiler.rs) calls delete_directory on that path, which runs std::fs::remove_dir_all (compiler-cli/src/fs.rs).\n* The manifest built by download_dependencies is passed to build::main in the same run, so the name reaches compilation without a manifest.toml round trip. otp_app is validated against ^[a-z][a-z0-9_]*$ only when manifest.toml is read from disk (commit 2dc0467, first released in v1.16.0), which rejects a stored malicious manifest on a later run but not the first run that produces it.\n* Extraction uses the package name, which hex.pm validates, so the effect is a directory delete without an overwrite. For a package with build_tools = [\"gleam\"] no publisher code runs, so source that fails to compile or a malformed gleam.toml triggers the delete. For rebar3 and Mix dependencies, publisher code runs anyway."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-103061",
        "datePublished": "2026-10-12T18:23:41.507Z",
        "dateReserved": "2026-10-07T13:15:01.364Z",
        "dateUpdated": "2026-10-16T17:49:42.484Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105274 (GCVE-0-2026-105274)

    Vulnerability from cvelistv5 – Published: 2026-10-12 16:43 – Updated: 2026-10-15 18:05
    VLAI
    Title
    hex_core decompresses registry and Erlang term API responses without size limits
    Summary
    Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core's API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (mix_hex_repo, mix_hex_api) and rebar3 (r3_hex_repo, r3_hex_api). hex_repo:get_names/1, hex_repo:get_versions/1, hex_repo:get_package/2 and hex_repo:get_policy/2 decompress the registry response with zlib:gunzip/1 and decode the signed envelope before verifying its signature. A mirror doesn't have the repository's private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it. hex_api decodes responses with the application/vnd.hex+erlang content type through hex_safe_binary_to_term:safe_binary_to_term/1, which calls binary_to_term(Binary, [safe]). The safe option prevents atom creation, but it accepts compressed terms and doesn't limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the safe option and that check without bounding the decoded size. This issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-15 17:51 UTC
    CWE
    • CWE-409 - Improper Handling of Highly Compressed Data (Data Amplification)
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    erlang rebar3 Affected: 3.9.1 , < 3.27.3 (semver)
        cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*
    Create a notification for this product.
    erlang rebar3 Affected: 209c02ec57c2cc3207ee0174c3af3675b8dc8f79 , < 360838a22f5b3a88c0358207f2923059bcbb3afb (git)
        cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex_core Affected: 0.1.0 , < 0.20.0 (semver)
        cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex_core Affected: fbb6d9de472e5edb8d6fb6b32a91cb266137892e , < ebe25d110717f6a3addba5232cd4a7dc1c76b628 (git)
        cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 2.1.0 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 671f695ac548ed01f5828c2a739ecaa942b21007 , < d8b60eee97abb119d525306f048969a217d793a5 (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-10-12 16:43
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105274",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-15T17:51:41.556015Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-15T18:05:12.446Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "r3_hex_repo",
                "r3_hex_api"
              ],
              "packageName": "rebar3",
              "packageURL": "pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git",
              "product": "rebar3",
              "programFiles": [
                "apps/rebar/src/vendored/r3_hex_repo.erl",
                "apps/rebar/src/vendored/r3_hex_api.erl"
              ],
              "programRoutines": [
                {
                  "name": "r3_hex_repo:get_names/1"
                },
                {
                  "name": "r3_hex_repo:get_versions/1"
                },
                {
                  "name": "r3_hex_repo:get_package/2"
                },
                {
                  "name": "r3_hex_api:request/4"
                }
              ],
              "repo": "https://github.com/erlang/rebar3",
              "vendor": "erlang",
              "versions": [
                {
                  "lessThan": "3.27.3",
                  "status": "affected",
                  "version": "3.9.1",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "r3_hex_repo",
                "r3_hex_api"
              ],
              "packageName": "erlang/rebar3",
              "packageURL": "pkg:github/erlang/rebar3",
              "product": "rebar3",
              "programFiles": [
                "apps/rebar/src/vendored/r3_hex_repo.erl",
                "apps/rebar/src/vendored/r3_hex_api.erl"
              ],
              "programRoutines": [
                {
                  "name": "r3_hex_repo:get_names/1"
                },
                {
                  "name": "r3_hex_repo:get_versions/1"
                },
                {
                  "name": "r3_hex_repo:get_package/2"
                },
                {
                  "name": "r3_hex_api:request/4"
                }
              ],
              "repo": "https://github.com/erlang/rebar3",
              "vendor": "erlang",
              "versions": [
                {
                  "lessThan": "360838a22f5b3a88c0358207f2923059bcbb3afb",
                  "status": "affected",
                  "version": "209c02ec57c2cc3207ee0174c3af3675b8dc8f79",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "hex_repo",
                "hex_api",
                "hex_safe_binary_to_term"
              ],
              "packageName": "hex_core",
              "packageURL": "pkg:hex/hex_core",
              "product": "hex_core",
              "programFiles": [
                "src/hex_repo.erl",
                "src/hex_api.erl",
                "src/hex_safe_binary_to_term.erl"
              ],
              "programRoutines": [
                {
                  "name": "hex_repo:get_names/1"
                },
                {
                  "name": "hex_repo:get_versions/1"
                },
                {
                  "name": "hex_repo:get_package/2"
                },
                {
                  "name": "hex_repo:get_policy/2"
                },
                {
                  "name": "hex_api:request/4"
                },
                {
                  "name": "hex_safe_binary_to_term:safe_binary_to_term/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex_core",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "0.20.0",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "hex_repo",
                "hex_api",
                "hex_safe_binary_to_term"
              ],
              "packageName": "hexpm/hex_core",
              "packageURL": "pkg:github/hexpm/hex_core",
              "product": "hex_core",
              "programFiles": [
                "src/hex_repo.erl",
                "src/hex_api.erl",
                "src/hex_safe_binary_to_term.erl"
              ],
              "programRoutines": [
                {
                  "name": "hex_repo:get_names/1"
                },
                {
                  "name": "hex_repo:get_versions/1"
                },
                {
                  "name": "hex_repo:get_package/2"
                },
                {
                  "name": "hex_repo:get_policy/2"
                },
                {
                  "name": "hex_api:request/4"
                },
                {
                  "name": "hex_safe_binary_to_term:safe_binary_to_term/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex_core",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "ebe25d110717f6a3addba5232cd4a7dc1c76b628",
                  "status": "affected",
                  "version": "fbb6d9de472e5edb8d6fb6b32a91cb266137892e",
                  "versionType": "git"
                }
              ]
            },
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "mix_hex_repo",
                "mix_hex_api"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "src/mix_hex_repo.erl",
                "src/mix_hex_api.erl"
              ],
              "programRoutines": [
                {
                  "name": "mix_hex_repo:get_names/1"
                },
                {
                  "name": "mix_hex_repo:get_versions/1"
                },
                {
                  "name": "mix_hex_repo:get_package/2"
                },
                {
                  "name": "mix_hex_repo:get_policy/2"
                },
                {
                  "name": "mix_hex_api:request/4"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "2.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "mix_hex_repo",
                "mix_hex_api"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "src/mix_hex_repo.erl",
                "src/mix_hex_api.erl"
              ],
              "programRoutines": [
                {
                  "name": "mix_hex_repo:get_names/1"
                },
                {
                  "name": "mix_hex_repo:get_versions/1"
                },
                {
                  "name": "mix_hex_repo:get_package/2"
                },
                {
                  "name": "mix_hex_repo:get_policy/2"
                },
                {
                  "name": "mix_hex_api:request/4"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "d8b60eee97abb119d525306f048969a217d793a5",
                  "status": "affected",
                  "version": "671f695ac548ed01f5828c2a739ecaa942b21007",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eRegistry decoding is affected when a repository\u0027s URL points at a mirror or another server the user doesn\u0027t trust with the availability of their builds, for example Hex\u0027s \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e and rebar3\u0027s \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e. API decoding is affected when the API URL points at such a server or an attacker can alter API responses. Requests to repo.hex.pm and hex.pm over verified HTTPS aren\u0027t affected.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Registry decoding is affected when a repository\u0027s URL points at a mirror or another server the user doesn\u0027t trust with the availability of their builds, for example Hex\u0027s `HEX_MIRROR_URL` or `mirror_url` and rebar3\u0027s `HEX_MIRROR_URL`. API decoding is affected when the API URL points at such a server or an attacker can alter API responses. Requests to repo.hex.pm and hex.pm over verified HTTPS aren\u0027t affected."
                }
              ],
              "value": "Registry decoding is affected when a repository\u0027s URL points at a mirror or another server the user doesn\u0027t trust with the availability of their builds, for example Hex\u0027s HEX_MIRROR_URL or mirror_url and rebar3\u0027s HEX_MIRROR_URL. API decoding is affected when the API URL points at such a server or an attacker can alter API responses. Requests to repo.hex.pm and hex.pm over verified HTTPS aren\u0027t affected."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "3.27.3",
                      "versionStartIncluding": "3.9.1",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.20.0",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "2.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-10T13:01:00.000Z",
          "datePublic": "2026-10-12T16:43:59.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core\u0027s API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (\u003ccode\u003emix_hex_repo\u003c/code\u003e, \u003ccode\u003emix_hex_api\u003c/code\u003e) and rebar3 (\u003ccode\u003er3_hex_repo\u003c/code\u003e, \u003ccode\u003er3_hex_api\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehex_repo:get_names/1\u003c/code\u003e, \u003ccode\u003ehex_repo:get_versions/1\u003c/code\u003e, \u003ccode\u003ehex_repo:get_package/2\u003c/code\u003e and \u003ccode\u003ehex_repo:get_policy/2\u003c/code\u003e decompress the registry response with \u003ccode\u003ezlib:gunzip/1\u003c/code\u003e and decode the signed envelope before verifying its signature. A mirror doesn\u0027t have the repository\u0027s private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehex_api\u003c/code\u003e decodes responses with the \u003ccode\u003eapplication/vnd.hex+erlang\u003c/code\u003e content type through \u003ccode\u003ehex_safe_binary_to_term:safe_binary_to_term/1\u003c/code\u003e, which calls \u003ccode\u003ebinary_to_term(Binary, [safe])\u003c/code\u003e. The \u003ccode\u003esafe\u003c/code\u003e option prevents atom creation, but it accepts compressed terms and doesn\u0027t limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the \u003ccode\u003esafe\u003c/code\u003e option and that check without bounding the decoded size.\u003c/p\u003e\n\u003cp\u003eThis issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core\u0027s API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (`mix_hex_repo`, `mix_hex_api`) and rebar3 (`r3_hex_repo`, `r3_hex_api`).\n\n`hex_repo:get_names/1`, `hex_repo:get_versions/1`, `hex_repo:get_package/2` and `hex_repo:get_policy/2` decompress the registry response with `zlib:gunzip/1` and decode the signed envelope before verifying its signature. A mirror doesn\u0027t have the repository\u0027s private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.\n\n`hex_api` decodes responses with the `application/vnd.hex+erlang` content type through `hex_safe_binary_to_term:safe_binary_to_term/1`, which calls `binary_to_term(Binary, [safe])`. The `safe` option prevents atom creation, but it accepts compressed terms and doesn\u0027t limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the `safe` option and that check without bounding the decoded size.\n\nThis issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0."
                }
              ],
              "value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex_core allows an untrusted repository mirror, or a server answering hex_core\u0027s API requests, to exhaust the memory of the Erlang VM running hex_core. The same code is vendored in Hex (mix_hex_repo, mix_hex_api) and rebar3 (r3_hex_repo, r3_hex_api).\n\nhex_repo:get_names/1, hex_repo:get_versions/1, hex_repo:get_package/2 and hex_repo:get_policy/2 decompress the registry response with zlib:gunzip/1 and decode the signed envelope before verifying its signature. A mirror doesn\u0027t have the repository\u0027s private key, but it chooses the compressed bytes, so a small response decompresses into an envelope of any size before the signature check rejects it.\n\nhex_api decodes responses with the application/vnd.hex+erlang content type through hex_safe_binary_to_term:safe_binary_to_term/1, which calls binary_to_term(Binary, [safe]). The safe option prevents atom creation, but it accepts compressed terms and doesn\u0027t limit the size of the decoded term, and the check that rejects functions and ports runs after the term has been built. The fix for CVE-2026-21619 added the safe option and that check without bounding the decoded size.\n\nThis issue affects rebar3: from 3.9.1 before 3.27.3; hex: from 2.1.0 before 2.5.2; hex_core: from 0.1.0 before 0.20.0."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA mirror can make every registry fetch allocate memory far beyond the size of its response: in a test against Hex main 851b938, which vendors this code, a 65 KB registry response decompressed into a 64 MiB envelope before the signature check failed. The API server, or an attacker who can alter its responses, can do the same with API responses: 8 KB of compressed Erlang term format decoded into a list of 4.2 million elements. Large enough responses stop the Erlang VM, which fails the build tool command or the program using hex_core. No package code has to run.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A mirror can make every registry fetch allocate memory far beyond the size of its response: in a test against Hex main 851b938, which vendors this code, a 65 KB registry response decompressed into a 64 MiB envelope before the signature check failed. The API server, or an attacker who can alter its responses, can do the same with API responses: 8 KB of compressed Erlang term format decoded into a list of 4.2 million elements. Large enough responses stop the Erlang VM, which fails the build tool command or the program using hex_core. No package code has to run."
                    }
                  ],
                  "value": "A mirror can make every registry fetch allocate memory far beyond the size of its response: in a test against Hex main 851b938, which vendors this code, a 65 KB registry response decompressed into a 64 MiB envelope before the signature check failed. The API server, or an attacker who can alter its responses, can do the same with API responses: 8 KB of compressed Erlang term format decoded into a list of 4.2 million elements. Large enough responses stop the Erlang VM, which fails the build tool command or the program using hex_core. No package code has to run."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 2.1,
                "baseSeverity": "LOW",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "LOW",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-409",
                  "description": "CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T18:16:57.155Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq"
            },
            {
              "name": "EEF CNA record for CVE-2026-105274",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105274.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105274",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105274"
            },
            {
              "name": "Introducing commit 209c02e in erlang/rebar3",
              "tags": [
                "related"
              ],
              "url": "https://github.com/erlang/rebar3/commit/209c02ec57c2cc3207ee0174c3af3675b8dc8f79"
            },
            {
              "name": "Fix commit 360838a in erlang/rebar3",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/rebar3/commit/360838a22f5b3a88c0358207f2923059bcbb3afb"
            },
            {
              "name": "Introducing commit fbb6d9d in hexpm/hex_core",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex_core/commit/fbb6d9de472e5edb8d6fb6b32a91cb266137892e"
            },
            {
              "name": "Fix commit ebe25d1 in hexpm/hex_core",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex_core/commit/ebe25d110717f6a3addba5232cd4a7dc1c76b628"
            },
            {
              "name": "Introducing commit 671f695 in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/671f695ac548ed01f5828c2a739ecaa942b21007"
            },
            {
              "name": "Fix commit d8b60ee in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/d8b60eee97abb119d525306f048969a217d793a5"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade hex_core to 0.20.0 or later. Registry resources larger than 16 MiB after decompression are rejected with \u003ccode\u003e{too_big_uncompressed, MaxSize}\u003c/code\u003e, configurable with \u003ccode\u003eregistry_max_uncompressed_size\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eUpgrade Hex to 2.5.2 or later, which vendors hex_core 0.20.0. No rebar3 release vendors hex_core 0.20.0 yet.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade hex_core to 0.20.0 or later. Registry resources larger than 16 MiB after decompression are rejected with `{too_big_uncompressed, MaxSize}`, configurable with `registry_max_uncompressed_size`.\n\nUpgrade Hex to 2.5.2 or later, which vendors hex_core 0.20.0. No rebar3 release vendors hex_core 0.20.0 yet."
                }
              ],
              "value": "Upgrade hex_core to 0.20.0 or later. Registry resources larger than 16 MiB after decompression are rejected with {too_big_uncompressed, MaxSize}, configurable with registry_max_uncompressed_size.\n\nUpgrade Hex to 2.5.2 or later, which vendors hex_core 0.20.0. No rebar3 release vendors hex_core 0.20.0 yet."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "hex_core decompresses registry and Erlang term API responses without size limits",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003ePoint repository URLs at repo.hex.pm or a mirror you operate, and the API URL at hex.pm.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Point repository URLs at repo.hex.pm or a mirror you operate, and the API URL at hex.pm."
                }
              ],
              "value": "Point repository URLs at repo.hex.pm or a mirror you operate, and the API URL at hex.pm."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003ePoint a repository at a loopback mirror (in Hex, \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e) that answers \u003ccode\u003e/packages/probe\u003c/code\u003e with a 65,268-byte gzip stream decompressing to a 67,108,872-byte signed envelope with a 64 MiB payload and a one-byte signature.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.Repo.get_package(\"hexpm\", \"probe\", nil)\u003c/code\u003e, which calls the vendored \u003ccode\u003ehex_repo:get_package/2\u003c/code\u003e, returns \u003ccode\u003e{:error, :bad_signature}\u003c/code\u003e after allocating the envelope. Peak RSS was 234 MiB, against 96 MiB for a 1,030-byte invalid envelope.\u003c/li\u003e\n\u003cli\u003eAnswer an API request with \u003ccode\u003econtent-type: application/vnd.hex+erlang\u003c/code\u003e and 8,178 bytes of compressed Erlang term format encoding a list of 4,194,304 elements. \u003ccode\u003emix_hex_api:get/2\u003c/code\u003e accepts the decoded term, and BEAM memory rises from 58 MiB to 272 MiB.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Point a repository at a loopback mirror (in Hex, `HEX_MIRROR_URL`) that answers `/packages/probe` with a 65,268-byte gzip stream decompressing to a 67,108,872-byte signed envelope with a 64 MiB payload and a one-byte signature.\n2. `Hex.Repo.get_package(\"hexpm\", \"probe\", nil)`, which calls the vendored `hex_repo:get_package/2`, returns `{:error, :bad_signature}` after allocating the envelope. Peak RSS was 234 MiB, against 96 MiB for a 1,030-byte invalid envelope.\n3. Answer an API request with `content-type: application/vnd.hex+erlang` and 8,178 bytes of compressed Erlang term format encoding a list of 4,194,304 elements. `mix_hex_api:get/2` accepts the decoded term, and BEAM memory rises from 58 MiB to 272 MiB."
                }
              ],
              "value": "* Point a repository at a loopback mirror (in Hex, HEX_MIRROR_URL) that answers /packages/probe with a 65,268-byte gzip stream decompressing to a 67,108,872-byte signed envelope with a 64 MiB payload and a one-byte signature.\n* Hex.Repo.get_package(\"hexpm\", \"probe\", nil), which calls the vendored hex_repo:get_package/2, returns {:error, :bad_signature} after allocating the envelope. Peak RSS was 234 MiB, against 96 MiB for a 1,030-byte invalid envelope.\n* Answer an API request with content-type: application/vnd.hex+erlang and 8,178 bytes of compressed Erlang term format encoding a list of 4,194,304 elements. mix_hex_api:get/2 accepts the decoded term, and BEAM memory rises from 58 MiB to 272 MiB."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003ehex_repo:get_protobuf/3\u003c/code\u003e, used by \u003ccode\u003eget_names/1\u003c/code\u003e, \u003ccode\u003eget_versions/1\u003c/code\u003e, \u003ccode\u003eget_package/2\u003c/code\u003e and \u003ccode\u003eget_policy/2\u003c/code\u003e, calls \u003ccode\u003ezlib:gunzip/1\u003c/code\u003e on the whole response body with no output limit.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehex_repo:decode/4\u003c/code\u003e then calls \u003ccode\u003ehex_registry:decode_and_verify_signed/2\u003c/code\u003e, which decodes the protobuf envelope, copying its payload and signature, before checking the signature.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehex_api:request/4\u003c/code\u003e passes every response whose content type contains \u003ccode\u003eapplication/vnd.hex+erlang\u003c/code\u003e to \u003ccode\u003ehex_safe_binary_to_term:safe_binary_to_term/1\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esafe_binary_to_term/1\u003c/code\u003e calls \u003ccode\u003ebinary_to_term(Binary, [safe])\u003c/code\u003e, which decompresses compressed terms and builds the whole term, and only then walks it to reject functions and ports.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `hex_repo:get_protobuf/3`, used by `get_names/1`, `get_versions/1`, `get_package/2` and `get_policy/2`, calls `zlib:gunzip/1` on the whole response body with no output limit.\n2. `hex_repo:decode/4` then calls `hex_registry:decode_and_verify_signed/2`, which decodes the protobuf envelope, copying its payload and signature, before checking the signature.\n3. `hex_api:request/4` passes every response whose content type contains `application/vnd.hex+erlang` to `hex_safe_binary_to_term:safe_binary_to_term/1`.\n4. `safe_binary_to_term/1` calls `binary_to_term(Binary, [safe])`, which decompresses compressed terms and builds the whole term, and only then walks it to reject functions and ports."
                }
              ],
              "value": "* hex_repo:get_protobuf/3, used by get_names/1, get_versions/1, get_package/2 and get_policy/2, calls zlib:gunzip/1 on the whole response body with no output limit.\n* hex_repo:decode/4 then calls hex_registry:decode_and_verify_signed/2, which decodes the protobuf envelope, copying its payload and signature, before checking the signature.\n* hex_api:request/4 passes every response whose content type contains application/vnd.hex+erlang to hex_safe_binary_to_term:safe_binary_to_term/1.\n* safe_binary_to_term/1 calls binary_to_term(Binary, [safe]), which decompresses compressed terms and builds the whole term, and only then walks it to reject functions and ports."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105274",
        "datePublished": "2026-10-12T16:43:58.730Z",
        "dateReserved": "2026-10-10T13:00:02.355Z",
        "dateUpdated": "2026-10-15T18:05:12.446Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-81856 (GCVE-0-2026-81856)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:46 – Updated: 2026-10-16 17:47
    VLAI
    Title
    Terminal escape sequence injection in Hex via unneutralized server-supplied output
    Summary
    Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks. Hex prints strings supplied by the package repository and the documentation search service straight to the terminal through Hex.Shell.info/1 and Hex.Shell.error/1, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in mix hex.info, search results in mix hex.package search and mix hex.search, owner and organization listings, publish and sponsor URLs, the authenticated username in mix hex.user, policy identities in mix hex.policy, and API error bodies rendered by Hex.Utils.print_error_result/1. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package. This issue affects hex: from 0.0.1 before 2.5.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:46 UTC
    CWE
    • CWE-150 - Improper Neutralization of Escape, Meta, or Control Sequences
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex Affected: 0.0.1 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 211303e3f68f5d3ce06450c1ff1a7a770a9ab236 , < dd3305ddf0032f6d0b468fd40dbbc3072bfcb8b8 (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-81856",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:46:03.963539Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:47:15.339Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mix.Tasks.Hex.Search\u0027"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "lib/mix/tasks/hex.search.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mix.Tasks.Hex.Search\u0027:run/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "0.0.1",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mix.Tasks.Hex.Search\u0027"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "lib/mix/tasks/hex.search.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mix.Tasks.Hex.Search\u0027:run/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "dd3305ddf0032f6d0b468fd40dbbc3072bfcb8b8",
                  "status": "affected",
                  "version": "211303e3f68f5d3ce06450c1ff1a7a770a9ab236",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReachable only when a developer runs an affected Mix task in a terminal emulator that interprets ANSI escape sequences, and the printed content originates from an attacker-published package or a hostile repository server.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reachable only when a developer runs an affected Mix task in a terminal emulator that interprets ANSI escape sequences, and the printed content originates from an attacker-published package or a hostile repository server."
                }
              ],
              "value": "Reachable only when a developer runs an affected Mix task in a terminal emulator that interprets ANSI escape sequences, and the printed content originates from an attacker-published package or a hostile repository server."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "0.0.1",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "reporter",
              "value": "Zach Daniel"
            },
            {
              "lang": "en",
              "type": "remediation reviewer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Jonatan M\u00e4nnchen"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen"
            }
          ],
          "dateAssigned": "2026-09-09T16:47:17.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.\u003c/p\u003e\n\u003cp\u003eHex prints strings supplied by the package repository and the documentation search service straight to the terminal through \u003ccode\u003eHex.Shell.info/1\u003c/code\u003e and \u003ccode\u003eHex.Shell.error/1\u003c/code\u003e, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in \u003ccode\u003emix hex.info\u003c/code\u003e, search results in \u003ccode\u003emix hex.package search\u003c/code\u003e and \u003ccode\u003emix hex.search\u003c/code\u003e, owner and organization listings, publish and sponsor URLs, the authenticated username in \u003ccode\u003emix hex.user\u003c/code\u003e, policy identities in \u003ccode\u003emix hex.policy\u003c/code\u003e, and API error bodies rendered by \u003ccode\u003eHex.Utils.print_error_result/1\u003c/code\u003e. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.0.1 before 2.5.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.\n\nHex prints strings supplied by the package repository and the documentation search service straight to the terminal through `Hex.Shell.info/1` and `Hex.Shell.error/1`, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in `mix hex.info`, search results in `mix hex.package search` and `mix hex.search`, owner and organization listings, publish and sponsor URLs, the authenticated username in `mix hex.user`, policy identities in `mix hex.policy`, and API error bodies rendered by `Hex.Utils.print_error_result/1`. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.\n\nThis issue affects hex: from 0.0.1 before 2.5.2."
                }
              ],
              "value": "Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in hexpm hex allows a malicious package publisher or a hostile repository server to inject terminal control sequences into the output of Mix tasks.\n\nHex prints strings supplied by the package repository and the documentation search service straight to the terminal through Hex.Shell.info/1 and Hex.Shell.error/1, neutralizing no control characters. Affected sinks span package metadata and configuration snippets in mix hex.info, search results in mix hex.package search and mix hex.search, owner and organization listings, publish and sponsor URLs, the authenticated username in mix hex.user, policy identities in mix hex.policy, and API error bodies rendered by Hex.Utils.print_error_result/1. A malicious package can embed ANSI escape sequences (cursor movement, line erase, carriage returns, OSC 52 clipboard writes) or Unicode bidirectional overrides in any of those fields, so the bytes reach the terminal unchanged, forging displayed URLs, package descriptions and suggested commands, concealing real output, or writing to the clipboard. No authentication or privileged position is required; only publishing a package.\n\nThis issue affects hex: from 0.0.1 before 2.5.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-93",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA malicious package publisher can forge what a developer sees when running \u003ccode\u003emix hex.search\u003c/code\u003e, rewriting the displayed hexdocs URL or a suggested command, concealing real output, or writing attacker-chosen text to the clipboard through OSC 52. Any developer who runs a documentation search that surfaces the attacker\u0027s package is affected.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A malicious package publisher can forge what a developer sees when running `mix hex.search`, rewriting the displayed hexdocs URL or a suggested command, concealing real output, or writing attacker-chosen text to the clipboard through OSC 52. Any developer who runs a documentation search that surfaces the attacker\u0027s package is affected."
                    }
                  ],
                  "value": "A malicious package publisher can forge what a developer sees when running mix hex.search, rewriting the displayed hexdocs URL or a suggested command, concealing real output, or writing attacker-chosen text to the clipboard through OSC 52. Any developer who runs a documentation search that surfaces the attacker\u0027s package is affected."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 2.3,
                "baseSeverity": "LOW",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "PASSIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-150",
                  "description": "CWE-150 Improper Neutralization of Escape, Meta, or Control Sequences",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:46:12.146Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex/security/advisories/GHSA-fpmj-3cxg-p766"
            },
            {
              "name": "EEF CNA record for CVE-2026-81856",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-81856.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-81856",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-81856"
            },
            {
              "name": "Introducing commit 211303e in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/211303e3f68f5d3ce06450c1ff1a7a770a9ab236"
            },
            {
              "name": "Fix commit dd3305d in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/dd3305ddf0032f6d0b468fd40dbbc3072bfcb8b8"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. Hex then escapes terminal control characters in text from hex.pm and the registry before printing it, and rejects registry dependencies whose package or repository name isn\u0027t a valid name.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. Hex then escapes terminal control characters in text from hex.pm and the registry before printing it, and rejects registry dependencies whose package or repository name isn\u0027t a valid name."
                }
              ],
              "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. Hex then escapes terminal control characters in text from hex.pm and the registry before printing it, and rejects registry dependencies whose package or repository name isn\u0027t a valid name."
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Terminal escape sequence injection in Hex via unneutralized server-supplied output"
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-81856",
        "datePublished": "2026-10-12T15:46:12.146Z",
        "dateReserved": "2026-08-31T00:45:02.431Z",
        "dateUpdated": "2026-10-16T17:47:15.339Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-103060 (GCVE-0-2026-103060)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:43 – Updated: 2026-10-16 17:44
    VLAI
    Title
    Path traversal in rebar3 via unvalidated dependency app names
    Summary
    Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project's _build directory and extract package files into it when a developer fetches dependencies. A package release's dependencies in a Hex registry carry an app field that names the dependency's OTP application. rebar3 uses this field as the dependency's name without validating it (rebar_packages:parse_deps/1) and builds the dependency's directory from that name (rebar_app_utils:dep_to_app/7). rebar_fetch:download_source_online/2 deletes that directory with rm -rf and moves the fetched package into it. hex.pm didn't restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as ../../../../target. When a project depends on such a release directly or transitively, rebar3 get-deps deletes the directory the name resolves to and writes the dependency's files there. This happens before the dependency's own rebar.config.script is evaluated, and get-deps compiles nothing. This issue affects rebar3: from 3.7.0-rc1 before 3.27.2.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: total
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:43 UTC
    CWE
    • CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    erlang rebar3 Affected: 3.7.0-rc1 , < 3.27.2 (semver)
        cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*
    Create a notification for this product.
    erlang rebar3 Affected: 56b7d88975aa8da6446857cfd92de0825024bf63 , < e35282b26e499b1d9e8f30bba9dccd71cd8b02ba (git)
        cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-103060",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "total"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:43:00.338501Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:44:16.145Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://github.com/erlang/rebar3/security/advisories/GHSA-6ggr-r7mh-2mv8"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "rebar_packages",
                "rebar_app_utils",
                "rebar_fetch"
              ],
              "packageName": "rebar3",
              "packageURL": "pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git",
              "product": "rebar3",
              "programFiles": [
                "apps/rebar/src/rebar_packages.erl",
                "apps/rebar/src/rebar_app_utils.erl",
                "apps/rebar/src/rebar_fetch.erl"
              ],
              "programRoutines": [
                {
                  "name": "rebar_packages:parse_deps/1"
                },
                {
                  "name": "rebar_app_utils:dep_to_app/7"
                },
                {
                  "name": "rebar_fetch:download_source/2"
                },
                {
                  "name": "rebar_fetch:download_source_online/2"
                }
              ],
              "repo": "https://github.com/erlang/rebar3",
              "vendor": "erlang",
              "versions": [
                {
                  "lessThan": "3.27.2",
                  "status": "affected",
                  "version": "3.7.0-rc1",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "rebar_packages",
                "rebar_app_utils",
                "rebar_fetch"
              ],
              "packageName": "erlang/rebar3",
              "packageURL": "pkg:github/erlang/rebar3",
              "product": "rebar3",
              "programFiles": [
                "apps/rebar/src/rebar_packages.erl",
                "apps/rebar/src/rebar_app_utils.erl",
                "apps/rebar/src/rebar_fetch.erl"
              ],
              "programRoutines": [
                {
                  "name": "rebar_packages:parse_deps/1"
                },
                {
                  "name": "rebar_app_utils:dep_to_app/7"
                },
                {
                  "name": "rebar_fetch:download_source/2"
                },
                {
                  "name": "rebar_fetch:download_source_online/2"
                }
              ],
              "repo": "https://github.com/erlang/rebar3",
              "vendor": "erlang",
              "versions": [
                {
                  "lessThan": "e35282b26e499b1d9e8f30bba9dccd71cd8b02ba",
                  "status": "affected",
                  "version": "56b7d88975aa8da6446857cfd92de0825024bf63",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted \u003ccode\u003eapp\u003c/code\u003e name, when dependencies are fetched with \u003ccode\u003erebar3 get-deps\u003c/code\u003e or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement \u003ccode\u003eapp\u003c/code\u003e name in hex.pm\u0027s public registry on 2026-10-06 was a letter followed by letters, digits, \u003ccode\u003e_\u003c/code\u003e and \u003ccode\u003e-\u003c/code\u003e. Packages on other Hex repositories are affected when the repository operator signs such metadata.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted `app` name, when dependencies are fetched with `rebar3 get-deps` or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement `app` name in hex.pm\u0027s public registry on 2026-10-06 was a letter followed by letters, digits, `_` and `-`. Packages on other Hex repositories are affected when the repository operator signs such metadata."
                }
              ],
              "value": "Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted app name, when dependencies are fetched with rebar3 get-deps or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement app name in hex.pm\u0027s public registry on 2026-10-06 was a letter followed by letters, digits, _ and -. Packages on other Hex repositories are affected when the repository operator signs such metadata."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "3.27.2",
                      "versionStartIncluding": "3.7.0-rc1",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-07T22:04:17.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s \u003ccode\u003e_build\u003c/code\u003e directory and extract package files into it when a developer fetches dependencies.\u003c/p\u003e\n\u003cp\u003eA package release\u0027s dependencies in a Hex registry carry an \u003ccode\u003eapp\u003c/code\u003e field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (\u003ccode\u003erebar_packages:parse_deps/1\u003c/code\u003e) and builds the dependency\u0027s directory from that name (\u003ccode\u003erebar_app_utils:dep_to_app/7\u003c/code\u003e). \u003ccode\u003erebar_fetch:download_source_online/2\u003c/code\u003e deletes that directory with \u003ccode\u003erm -rf\u003c/code\u003e and moves the fetched package into it. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as \u003ccode\u003e../../../../target\u003c/code\u003e. When a project depends on such a release directly or transitively, \u003ccode\u003erebar3 get-deps\u003c/code\u003e deletes the directory the name resolves to and writes the dependency\u0027s files there. This happens before the dependency\u0027s own \u003ccode\u003erebar.config.script\u003c/code\u003e is evaluated, and \u003ccode\u003eget-deps\u003c/code\u003e compiles nothing.\u003c/p\u003e\n\u003cp\u003eThis issue affects rebar3: from 3.7.0-rc1 before 3.27.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s `_build` directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s dependencies in a Hex registry carry an `app` field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (`rebar_packages:parse_deps/1`) and builds the dependency\u0027s directory from that name (`rebar_app_utils:dep_to_app/7`). `rebar_fetch:download_source_online/2` deletes that directory with `rm -rf` and moves the fetched package into it. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as `../../../../target`. When a project depends on such a release directly or transitively, `rebar3 get-deps` deletes the directory the name resolves to and writes the dependency\u0027s files there. This happens before the dependency\u0027s own `rebar.config.script` is evaluated, and `get-deps` compiles nothing.\n\nThis issue affects rebar3: from 3.7.0-rc1 before 3.27.2."
                }
              ],
              "value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s _build directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s dependencies in a Hex registry carry an app field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (rebar_packages:parse_deps/1) and builds the dependency\u0027s directory from that name (rebar_app_utils:dep_to_app/7). rebar_fetch:download_source_online/2 deletes that directory with rm -rf and moves the fetched package into it. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as ../../../../target. When a project depends on such a release directly or transitively, rebar3 get-deps deletes the directory the name resolves to and writes the dependency\u0027s files there. This happens before the dependency\u0027s own rebar.config.script is evaluated, and get-deps compiles nothing.\n\nThis issue affects rebar3: from 3.7.0-rc1 before 3.27.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-126",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA publisher of a package in a project\u0027s dependency tree can make \u003ccode\u003erebar3 get-deps\u003c/code\u003e delete a directory outside the project\u0027s \u003ccode\u003e_build\u003c/code\u003e directory that the developer\u0027s account can write, such as the project directory or its parent, and replace its contents with a dependency package\u0027s files. Replacing a directory whose contents are later run or loaded, such as a project directory whose \u003ccode\u003erebar.config.script\u003c/code\u003e the next rebar3 command there evaluates, can lead to code execution. It works when the package is a transitive dependency and needs no compilation. Developers, CI jobs and dependency update bots that fetch dependencies with rebar3 are affected.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A publisher of a package in a project\u0027s dependency tree can make `rebar3 get-deps` delete a directory outside the project\u0027s `_build` directory that the developer\u0027s account can write, such as the project directory or its parent, and replace its contents with a dependency package\u0027s files. Replacing a directory whose contents are later run or loaded, such as a project directory whose `rebar.config.script` the next rebar3 command there evaluates, can lead to code execution. It works when the package is a transitive dependency and needs no compilation. Developers, CI jobs and dependency update bots that fetch dependencies with rebar3 are affected."
                    }
                  ],
                  "value": "A publisher of a package in a project\u0027s dependency tree can make rebar3 get-deps delete a directory outside the project\u0027s _build directory that the developer\u0027s account can write, such as the project directory or its parent, and replace its contents with a dependency package\u0027s files. Replacing a directory whose contents are later run or loaded, such as a project directory whose rebar.config.script the next rebar3 command there evaluates, can lead to code execution. It works when the package is a transitive dependency and needs no compilation. Developers, CI jobs and dependency update bots that fetch dependencies with rebar3 are affected."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 7.5,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "HIGH",
                "vulnIntegrityImpact": "HIGH",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-22",
                  "description": "CWE-22 Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:43:57.636Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/erlang/rebar3/security/advisories/GHSA-6ggr-r7mh-2mv8"
            },
            {
              "name": "EEF CNA record for CVE-2026-103060",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-103060.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-103060",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-103060"
            },
            {
              "name": "Introducing commit 56b7d88 in erlang/rebar3",
              "tags": [
                "related"
              ],
              "url": "https://github.com/erlang/rebar3/commit/56b7d88975aa8da6446857cfd92de0825024bf63"
            },
            {
              "name": "Fix commit e35282b in erlang/rebar3",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/erlang/rebar3/commit/e35282b26e499b1d9e8f30bba9dccd71cd8b02ba"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade rebar3 to 3.27.2 or later, for example with \u003ccode\u003erebar3 local upgrade\u003c/code\u003e. rebar3 then fails with an error when a dependency\u0027s name, including the \u003ccode\u003eapp\u003c/code\u003e name a package release gives a dependency, isn\u0027t a letter followed by letters, digits, \u003ccode\u003e_\u003c/code\u003e and \u003ccode\u003e-\u003c/code\u003e.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade rebar3 to 3.27.2 or later, for example with `rebar3 local upgrade`. rebar3 then fails with an error when a dependency\u0027s name, including the `app` name a package release gives a dependency, isn\u0027t a letter followed by letters, digits, `_` and `-`."
                }
              ],
              "value": "Upgrade rebar3 to 3.27.2 or later, for example with rebar3 local upgrade. rebar3 then fails with an error when a dependency\u0027s name, including the app name a package release gives a dependency, isn\u0027t a letter followed by letters, digits, _ and -."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Path traversal in rebar3 via unvalidated dependency app names",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eBuild a signed local Hex repository serving \u003ccode\u003eparent\u003c/code\u003e 1.0.0, whose dependency is \u003ccode\u003e#{package =\u0026gt; \u0026lt;\u0026lt;\"evildep\"\u0026gt;\u0026gt;, requirement =\u0026gt; \u0026lt;\u0026lt;\"1.0.0\"\u0026gt;\u0026gt;, optional =\u0026gt; false, app =\u0026gt; \u0026lt;\u0026lt;\"../../../../CANARY_OUTSIDE\"\u0026gt;\u0026gt;}\u003c/code\u003e, and \u003ccode\u003eevildep\u003c/code\u003e 1.0.0.\u003c/li\u003e\n\u003cli\u003eCreate a project with \u003ccode\u003e{deps, [{parent, \"1.0.0\"}]}\u003c/code\u003e that uses the local repository, and a file \u003ccode\u003eCANARY_OUTSIDE/precious.txt\u003c/code\u003e one level above the project root.\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003erebar3 get-deps\u003c/code\u003e. It prints \u003ccode\u003eFetching parent v1.0.0\u003c/code\u003e and then \u003ccode\u003eFetching ../../../../CANARY_OUTSIDE v1.0.0\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCANARY_OUTSIDE/precious.txt\u003c/code\u003e is gone and \u003ccode\u003eCANARY_OUTSIDE/\u003c/code\u003e holds \u003ccode\u003eevildep\u003c/code\u003e\u0027s extracted files (\u003ccode\u003ehex_metadata.config\u003c/code\u003e, \u003ccode\u003esrc/evildep.app.src\u003c/code\u003e). No scripts, plugins or hooks are in either package.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:\n\n1. Build a signed local Hex repository serving `parent` 1.0.0, whose dependency is `#{package =\u003e \u003c\u003c\"evildep\"\u003e\u003e, requirement =\u003e \u003c\u003c\"1.0.0\"\u003e\u003e, optional =\u003e false, app =\u003e \u003c\u003c\"../../../../CANARY_OUTSIDE\"\u003e\u003e}`, and `evildep` 1.0.0.\n2. Create a project with `{deps, [{parent, \"1.0.0\"}]}` that uses the local repository, and a file `CANARY_OUTSIDE/precious.txt` one level above the project root.\n3. Run `rebar3 get-deps`. It prints `Fetching parent v1.0.0` and then `Fetching ../../../../CANARY_OUTSIDE v1.0.0`.\n4. `CANARY_OUTSIDE/precious.txt` is gone and `CANARY_OUTSIDE/` holds `evildep`\u0027s extracted files (`hex_metadata.config`, `src/evildep.app.src`). No scripts, plugins or hooks are in either package."
                }
              ],
              "value": "Reproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:\n\n* Build a signed local Hex repository serving parent 1.0.0, whose dependency is #{package =\u003e \u003c\u003c\"evildep\"\u003e\u003e, requirement =\u003e \u003c\u003c\"1.0.0\"\u003e\u003e, optional =\u003e false, app =\u003e \u003c\u003c\"../../../../CANARY_OUTSIDE\"\u003e\u003e}, and evildep 1.0.0.\n* Create a project with {deps, [{parent, \"1.0.0\"}]} that uses the local repository, and a file CANARY_OUTSIDE/precious.txt one level above the project root.\n* Run rebar3 get-deps. It prints Fetching parent v1.0.0 and then Fetching ../../../../CANARY_OUTSIDE v1.0.0.\n* CANARY_OUTSIDE/precious.txt is gone and CANARY_OUTSIDE/ holds evildep\u0027s extracted files (hex_metadata.config, src/evildep.app.src). No scripts, plugins or hooks are in either package."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003ehex.pm\u0027s registry record lists each release\u0027s dependencies as maps with \u003ccode\u003epackage\u003c/code\u003e, \u003ccode\u003erequirement\u003c/code\u003e, \u003ccode\u003eoptional\u003c/code\u003e, \u003ccode\u003eapp\u003c/code\u003e and \u003ccode\u003erepository\u003c/code\u003e. The \u003ccode\u003eapp\u003c/code\u003e value comes from the publisher\u0027s package metadata and hex.pm doesn\u0027t restrict it to a directory name. The record is signed with the repository\u0027s key after the publisher has chosen the value, so the signature doesn\u0027t constrain it.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003er3_hex_registry:decode_package/3\u003c/code\u003e decodes each dependency, including \u003ccode\u003eapp\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erebar_packages:parse_deps/1\u003c/code\u003e sets the dependency\u0027s name to the \u003ccode\u003eapp\u003c/code\u003e value, falling back to the package name: \u003ccode\u003e{maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}\u003c/code\u003e. The name is stored in \u003ccode\u003e#package.dependencies\u003c/code\u003e and passed through \u003ccode\u003erebar_app_utils:update_source/3\u003c/code\u003e and \u003ccode\u003erebar_prv_install_deps:handle_dep/6\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erebar_app_utils:dep_to_app/7\u003c/code\u003e builds the fetch directory with \u003ccode\u003efilename:join([DepsDir, Name])\u003c/code\u003e and no check.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erebar_fetch:download_source_online/2\u003c/code\u003e runs \u003ccode\u003erebar_file_utils:rm_rf(filename:absname(FetchDir))\u003c/code\u003e and then \u003ccode\u003erebar_file_utils:mv(TmpDir, filename:absname(FetchDir))\u003c/code\u003e. \u003ccode\u003efilename:absname/1\u003c/code\u003e doesn\u0027t collapse \u003ccode\u003e..\u003c/code\u003e segments, and on Unix \u003ccode\u003erm_rf/1\u003c/code\u003e runs \u003ccode\u003erm -rf\u003c/code\u003e through the shell with \u003ccode\u003erebar_utils:escape_chars/1\u003c/code\u003e, which doesn\u0027t escape \u003ccode\u003e/\u003c/code\u003e or \u003ccode\u003e.\u003c/code\u003e, so the \u003ccode\u003e..\u003c/code\u003e segments resolve outside the deps directory.\u003c/li\u003e\n\u003cli\u003eThe delete and overwrite happen for the dependency named by \u003ccode\u003eapp\u003c/code\u003e before its own \u003ccode\u003erebar.config.script\u003c/code\u003e is consulted (later in \u003ccode\u003erebar_fetch:download_source/2\u003c/code\u003e), and \u003ccode\u003eget-deps\u003c/code\u003e compiles nothing. rebar3 already runs publisher code during \u003ccode\u003eget-deps\u003c/code\u003e by evaluating a fetched package\u0027s \u003ccode\u003erebar.config.script\u003c/code\u003e and installing a dependency\u0027s plugins; the \u003ccode\u003eapp\u003c/code\u003e name adds a delete and overwrite outside \u003ccode\u003e_build\u003c/code\u003e for a dependency named by its parent package.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. hex.pm\u0027s registry record lists each release\u0027s dependencies as maps with `package`, `requirement`, `optional`, `app` and `repository`. The `app` value comes from the publisher\u0027s package metadata and hex.pm doesn\u0027t restrict it to a directory name. The record is signed with the repository\u0027s key after the publisher has chosen the value, so the signature doesn\u0027t constrain it.\n2. `r3_hex_registry:decode_package/3` decodes each dependency, including `app`.\n3. `rebar_packages:parse_deps/1` sets the dependency\u0027s name to the `app` value, falling back to the package name: `{maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}`. The name is stored in `#package.dependencies` and passed through `rebar_app_utils:update_source/3` and `rebar_prv_install_deps:handle_dep/6`.\n4. `rebar_app_utils:dep_to_app/7` builds the fetch directory with `filename:join([DepsDir, Name])` and no check.\n5. `rebar_fetch:download_source_online/2` runs `rebar_file_utils:rm_rf(filename:absname(FetchDir))` and then `rebar_file_utils:mv(TmpDir, filename:absname(FetchDir))`. `filename:absname/1` doesn\u0027t collapse `..` segments, and on Unix `rm_rf/1` runs `rm -rf` through the shell with `rebar_utils:escape_chars/1`, which doesn\u0027t escape `/` or `.`, so the `..` segments resolve outside the deps directory.\n6. The delete and overwrite happen for the dependency named by `app` before its own `rebar.config.script` is consulted (later in `rebar_fetch:download_source/2`), and `get-deps` compiles nothing. rebar3 already runs publisher code during `get-deps` by evaluating a fetched package\u0027s `rebar.config.script` and installing a dependency\u0027s plugins; the `app` name adds a delete and overwrite outside `_build` for a dependency named by its parent package."
                }
              ],
              "value": "* hex.pm\u0027s registry record lists each release\u0027s dependencies as maps with package, requirement, optional, app and repository. The app value comes from the publisher\u0027s package metadata and hex.pm doesn\u0027t restrict it to a directory name. The record is signed with the repository\u0027s key after the publisher has chosen the value, so the signature doesn\u0027t constrain it.\n* r3_hex_registry:decode_package/3 decodes each dependency, including app.\n* rebar_packages:parse_deps/1 sets the dependency\u0027s name to the app value, falling back to the package name: {maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}. The name is stored in #package.dependencies and passed through rebar_app_utils:update_source/3 and rebar_prv_install_deps:handle_dep/6.\n* rebar_app_utils:dep_to_app/7 builds the fetch directory with filename:join([DepsDir, Name]) and no check.\n* rebar_fetch:download_source_online/2 runs rebar_file_utils:rm_rf(filename:absname(FetchDir)) and then rebar_file_utils:mv(TmpDir, filename:absname(FetchDir)). filename:absname/1 doesn\u0027t collapse .. segments, and on Unix rm_rf/1 runs rm -rf through the shell with rebar_utils:escape_chars/1, which doesn\u0027t escape / or ., so the .. segments resolve outside the deps directory.\n* The delete and overwrite happen for the dependency named by app before its own rebar.config.script is consulted (later in rebar_fetch:download_source/2), and get-deps compiles nothing. rebar3 already runs publisher code during get-deps by evaluating a fetched package\u0027s rebar.config.script and installing a dependency\u0027s plugins; the app name adds a delete and overwrite outside _build for a dependency named by its parent package."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-103060",
        "datePublished": "2026-10-12T15:43:57.636Z",
        "dateReserved": "2026-10-06T08:45:01.521Z",
        "dateUpdated": "2026-10-16T17:44:16.145Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-96612 (GCVE-0-2026-96612)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:39 – Updated: 2026-10-16 17:41
    VLAI
    Title
    Path traversal in Hex via unvalidated dependency app names
    Summary
    Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project's deps directory and extract package files into it when a developer fetches dependencies. A package release's requirements carry an app label that names the dependency's OTP application. Hex takes this label from the repository's signed registry and uses it as the dependency's application name without validating it (Hex.RemoteConverger.deps/2, Hex.Mix.to_lock/1). Mix derives the dependency's checkout directory as deps/<app>, and Hex.SCM.update/1 runs File.rm_rf!/1 on that directory and unpacks the package into it. hex.pm only checks the label's length, so a publisher can set it to a value such as ../../target. When a project depends on such a release directly or transitively, mix deps.get or mix deps.update deletes the directory the label resolves to and writes the labelled package's files there. This happens before any dependency code is compiled. This issue affects hex: from 0.6.0 before 2.5.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: total
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:41 UTC
    CWE
    • CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex Affected: 0.6.0 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 380f87054e1bc08ee13d2b5311bc01f98dad2fc6 , < * (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-96612",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "total"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:41:34.850974Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:41:58.545Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.RemoteConverger\u0027",
                "\u0027Elixir.Hex.Mix\u0027",
                "\u0027Elixir.Hex.SCM\u0027"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "lib/hex/remote_converger.ex",
                "lib/hex/mix.ex",
                "lib/hex/scm.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:deps/2"
                },
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:converge/2"
                },
                {
                  "name": "\u0027Elixir.Hex.Mix\u0027:to_lock/1"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:update/1"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:checkout/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "0.6.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.RemoteConverger\u0027",
                "\u0027Elixir.Hex.Mix\u0027",
                "\u0027Elixir.Hex.SCM\u0027"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "lib/hex/remote_converger.ex",
                "lib/hex/mix.ex",
                "lib/hex/scm.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:deps/2"
                },
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:converge/2"
                },
                {
                  "name": "\u0027Elixir.Hex.Mix\u0027:to_lock/1"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:update/1"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:checkout/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "1b3e2ae8f0dd7273a2c2625b9e50b6c67c8568fb",
                      "status": "unaffected"
                    },
                    {
                      "at": "72279a8e2004b32b20d8767ff010c117193dd02c",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "380f87054e1bc08ee13d2b5311bc01f98dad2fc6",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata carries a crafted dependency \u003ccode\u003eapp\u003c/code\u003e label, when dependencies are fetched with \u003ccode\u003emix deps.get\u003c/code\u003e or \u003ccode\u003emix deps.update\u003c/code\u003e. Packages on hex.pm are affected while hex.pm accepts such labels; packages on self-hosted repositories are affected when the repository operator signs such metadata.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata carries a crafted dependency `app` label, when dependencies are fetched with `mix deps.get` or `mix deps.update`. Packages on hex.pm are affected while hex.pm accepts such labels; packages on self-hosted repositories are affected when the repository operator signs such metadata."
                }
              ],
              "value": "Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata carries a crafted dependency app label, when dependencies are fetched with mix deps.get or mix deps.update. Packages on hex.pm are affected while hex.pm accepts such labels; packages on self-hosted repositories are affected when the repository operator signs such metadata."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "0.6.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-07T22:03:18.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project\u0027s \u003ccode\u003edeps\u003c/code\u003e directory and extract package files into it when a developer fetches dependencies.\u003c/p\u003e\n\u003cp\u003eA package release\u0027s requirements carry an \u003ccode\u003eapp\u003c/code\u003e label that names the dependency\u0027s OTP application. Hex takes this label from the repository\u0027s signed registry and uses it as the dependency\u0027s application name without validating it (\u003ccode\u003eHex.RemoteConverger.deps/2\u003c/code\u003e, \u003ccode\u003eHex.Mix.to_lock/1\u003c/code\u003e). Mix derives the dependency\u0027s checkout directory as \u003ccode\u003edeps/\u0026lt;app\u0026gt;\u003c/code\u003e, and \u003ccode\u003eHex.SCM.update/1\u003c/code\u003e runs \u003ccode\u003eFile.rm_rf!/1\u003c/code\u003e on that directory and unpacks the package into it. hex.pm only checks the label\u0027s length, so a publisher can set it to a value such as \u003ccode\u003e../../target\u003c/code\u003e. When a project depends on such a release directly or transitively, \u003ccode\u003emix deps.get\u003c/code\u003e or \u003ccode\u003emix deps.update\u003c/code\u003e deletes the directory the label resolves to and writes the labelled package\u0027s files there. This happens before any dependency code is compiled.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.6.0 before 2.5.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project\u0027s `deps` directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s requirements carry an `app` label that names the dependency\u0027s OTP application. Hex takes this label from the repository\u0027s signed registry and uses it as the dependency\u0027s application name without validating it (`Hex.RemoteConverger.deps/2`, `Hex.Mix.to_lock/1`). Mix derives the dependency\u0027s checkout directory as `deps/\u003capp\u003e`, and `Hex.SCM.update/1` runs `File.rm_rf!/1` on that directory and unpacks the package into it. hex.pm only checks the label\u0027s length, so a publisher can set it to a value such as `../../target`. When a project depends on such a release directly or transitively, `mix deps.get` or `mix deps.update` deletes the directory the label resolves to and writes the labelled package\u0027s files there. This happens before any dependency code is compiled.\n\nThis issue affects hex: from 0.6.0 before 2.5.2."
                }
              ],
              "value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in hexpm hex allows a malicious package publisher to delete a directory outside the project\u0027s deps directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s requirements carry an app label that names the dependency\u0027s OTP application. Hex takes this label from the repository\u0027s signed registry and uses it as the dependency\u0027s application name without validating it (Hex.RemoteConverger.deps/2, Hex.Mix.to_lock/1). Mix derives the dependency\u0027s checkout directory as deps/\u003capp\u003e, and Hex.SCM.update/1 runs File.rm_rf!/1 on that directory and unpacks the package into it. hex.pm only checks the label\u0027s length, so a publisher can set it to a value such as ../../target. When a project depends on such a release directly or transitively, mix deps.get or mix deps.update deletes the directory the label resolves to and writes the labelled package\u0027s files there. This happens before any dependency code is compiled.\n\nThis issue affects hex: from 0.6.0 before 2.5.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-126",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA publisher of any package in a project\u0027s dependency tree can make \u003ccode\u003emix deps.get\u003c/code\u003e delete a directory that the developer\u0027s account can write and that \u003ccode\u003e..\u003c/code\u003e segments relative to \u003ccode\u003edeps\u003c/code\u003e reach, such as the project directory or its parent, and fill it with the labelled package\u0027s files. Writing attacker-chosen files into a directory whose contents are later run or loaded can lead to code execution without the developer compiling the dependency. Developers who fetch dependencies to review them before compiling, CI jobs and dependency update bots that only run \u003ccode\u003emix deps.get\u003c/code\u003e or \u003ccode\u003emix deps.update\u003c/code\u003e are affected.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A publisher of any package in a project\u0027s dependency tree can make `mix deps.get` delete a directory that the developer\u0027s account can write and that `..` segments relative to `deps` reach, such as the project directory or its parent, and fill it with the labelled package\u0027s files. Writing attacker-chosen files into a directory whose contents are later run or loaded can lead to code execution without the developer compiling the dependency. Developers who fetch dependencies to review them before compiling, CI jobs and dependency update bots that only run `mix deps.get` or `mix deps.update` are affected."
                    }
                  ],
                  "value": "A publisher of any package in a project\u0027s dependency tree can make mix deps.get delete a directory that the developer\u0027s account can write and that .. segments relative to deps reach, such as the project directory or its parent, and fill it with the labelled package\u0027s files. Writing attacker-chosen files into a directory whose contents are later run or loaded can lead to code execution without the developer compiling the dependency. Developers who fetch dependencies to review them before compiling, CI jobs and dependency update bots that only run mix deps.get or mix deps.update are affected."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 7.5,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "HIGH",
                "vulnIntegrityImpact": "HIGH",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-22",
                  "description": "CWE-22 Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:39:49.862Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex/security/advisories/GHSA-2vjj-c5hr-6xc3"
            },
            {
              "name": "EEF CNA record for CVE-2026-96612",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-96612.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-96612",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-96612"
            },
            {
              "name": "Introducing commit 380f870 in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/380f87054e1bc08ee13d2b5311bc01f98dad2fc6"
            },
            {
              "name": "Fix commit 1b3e2ae in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/1b3e2ae8f0dd7273a2c2625b9e50b6c67c8568fb"
            },
            {
              "name": "Fix commit 72279a8 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/72279a8e2004b32b20d8767ff010c117193dd02c"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. Hex then skips package releases that give a dependency an application name other than a letter followed by letters, digits, \u003ccode\u003e_\u003c/code\u003e and \u003ccode\u003e-\u003c/code\u003e, and warns about them. Resolution fails if no other release of the package fits the requirements.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. Hex then skips package releases that give a dependency an application name other than a letter followed by letters, digits, `_` and `-`, and warns about them. Resolution fails if no other release of the package fits the requirements."
                }
              ],
              "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. Hex then skips package releases that give a dependency an application name other than a letter followed by letters, digits, _ and -, and warns about them. Resolution fails if no other release of the package fits the requirements."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Path traversal in Hex via unvalidated dependency app names",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eBefore running \u003ccode\u003emix deps.get\u003c/code\u003e on a \u003ccode\u003emix.lock\u003c/code\u003e from an untrusted change, check that every key in \u003ccode\u003emix.lock\u003c/code\u003e is a plain application name without \u003ccode\u003e/\u003c/code\u003e, \u003ccode\u003e\\\u003c/code\u003e or \u003ccode\u003e..\u003c/code\u003e. There\u0027s no workaround for new dependency resolution with \u003ccode\u003emix deps.update\u003c/code\u003e or \u003ccode\u003emix deps.get\u003c/code\u003e without a lock entry.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Before running `mix deps.get` on a `mix.lock` from an untrusted change, check that every key in `mix.lock` is a plain application name without `/`, `\\` or `..`. There\u0027s no workaround for new dependency resolution with `mix deps.update` or `mix deps.get` without a lock entry."
                }
              ],
              "value": "Before running mix deps.get on a mix.lock from an untrusted change, check that every key in mix.lock is a plain application name without /, \\ or ... There\u0027s no workaround for new dependency resolution with mix deps.update or mix deps.get without a lock entry."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003ePublish package \u003ccode\u003ebeta\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ePublish package \u003ccode\u003ealpha\u003c/code\u003e with a requirement on \u003ccode\u003ebeta\u003c/code\u003e whose \u003ccode\u003eapp\u003c/code\u003e is \u003ccode\u003e../../escape_marker\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eIn a project that depends on \u003ccode\u003ealpha\u003c/code\u003e, create \u003ccode\u003e../escape_marker/canary.txt\u003c/code\u003e next to the project directory.\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003emix deps.get\u003c/code\u003e. Hex prints \u003ccode\u003e* Updating ../../escape_marker (Hex package)\u003c/code\u003e, deletes \u003ccode\u003ecanary.txt\u003c/code\u003e and writes \u003ccode\u003ebeta\u003c/code\u003e\u0027s files into \u003ccode\u003e../escape_marker\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Publish package `beta`.\n2. Publish package `alpha` with a requirement on `beta` whose `app` is `../../escape_marker`.\n3. In a project that depends on `alpha`, create `../escape_marker/canary.txt` next to the project directory.\n4. Run `mix deps.get`. Hex prints `* Updating ../../escape_marker (Hex package)`, deletes `canary.txt` and writes `beta`\u0027s files into `../escape_marker`."
                }
              ],
              "value": "* Publish package beta.\n* Publish package alpha with a requirement on beta whose app is ../../escape_marker.\n* In a project that depends on alpha, create ../escape_marker/canary.txt next to the project directory.\n* Run mix deps.get. Hex prints * Updating ../../escape_marker (Hex package), deletes canary.txt and writes beta\u0027s files into ../escape_marker."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003ehex.pm stores each requirement\u0027s \u003ccode\u003eapp\u003c/code\u003e field with only a length check (255 codepoints) and copies it into the signed package registry resource when it differs from the dependency\u0027s package name.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.RemoteConverger.deps/2\u003c/code\u003e returns the dependency\u0027s children with \u003ccode\u003eString.to_atom/1\u003c/code\u003e of that label as the application name, and \u003ccode\u003eHex.Mix.to_lock/1\u003c/code\u003e writes it as the \u003ccode\u003emix.lock\u003c/code\u003e key.\u003c/li\u003e\n\u003cli\u003eMix computes the dependency destination as \u003ccode\u003ePath.join(Mix.Project.deps_path(), Atom.to_string(app))\u003c/code\u003e without validating the name.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.SCM.update/1\u003c/code\u003e calls \u003ccode\u003eFile.rm_rf!(dest)\u003c/code\u003e and then \u003ccode\u003eHex.Tar.unpack!(path, dest)\u003c/code\u003e, so a label containing \u003ccode\u003e..\u003c/code\u003e segments deletes and repopulates a directory outside \u003ccode\u003edeps\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eMix doesn\u0027t evaluate a Hex dependency\u0027s \u003ccode\u003emix.exs\u003c/code\u003e during \u003ccode\u003emix deps.get\u003c/code\u003e, so this is reachable before any package code runs.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. hex.pm stores each requirement\u0027s `app` field with only a length check (255 codepoints) and copies it into the signed package registry resource when it differs from the dependency\u0027s package name.\n2. `Hex.RemoteConverger.deps/2` returns the dependency\u0027s children with `String.to_atom/1` of that label as the application name, and `Hex.Mix.to_lock/1` writes it as the `mix.lock` key.\n3. Mix computes the dependency destination as `Path.join(Mix.Project.deps_path(), Atom.to_string(app))` without validating the name.\n4. `Hex.SCM.update/1` calls `File.rm_rf!(dest)` and then `Hex.Tar.unpack!(path, dest)`, so a label containing `..` segments deletes and repopulates a directory outside `deps`.\n5. Mix doesn\u0027t evaluate a Hex dependency\u0027s `mix.exs` during `mix deps.get`, so this is reachable before any package code runs."
                }
              ],
              "value": "* hex.pm stores each requirement\u0027s app field with only a length check (255 codepoints) and copies it into the signed package registry resource when it differs from the dependency\u0027s package name.\n* Hex.RemoteConverger.deps/2 returns the dependency\u0027s children with String.to_atom/1 of that label as the application name, and Hex.Mix.to_lock/1 writes it as the mix.lock key.\n* Mix computes the dependency destination as Path.join(Mix.Project.deps_path(), Atom.to_string(app)) without validating the name.\n* Hex.SCM.update/1 calls File.rm_rf!(dest) and then Hex.Tar.unpack!(path, dest), so a label containing .. segments deletes and repopulates a directory outside deps.\n* Mix doesn\u0027t evaluate a Hex dependency\u0027s mix.exs during mix deps.get, so this is reachable before any package code runs."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-96612",
        "datePublished": "2026-10-12T15:39:49.862Z",
        "dateReserved": "2026-10-01T09:00:02.103Z",
        "dateUpdated": "2026-10-16T17:41:58.545Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105668 (GCVE-0-2026-105668)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:31 – Updated: 2026-10-16 17:38
    VLAI
    Title
    Hex release-age cooldown is lifted package-wide when the locked release is retired
    Summary
    Protection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner's account, to get a newly published release past the release-age cooldown. The cooldown (cooldown config, HEX_COOLDOWN) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in mix.lock is retired or has a security advisory, Hex.RemoteConverger.build_cooldown_bypass/3 and Hex.RemoteConverger.unsafe_lock_bypass/1 lift the cooldown for every version of that package and of every package the locked release depends on, so that mix deps.update can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next mix deps.update that unlocks the package selects the new release regardless of its age. Hex.Registry.Cooldown.versions/2 checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected. This issue affects hex: from 2.5.0 before 2.5.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: total
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:37 UTC
    CWE
    • CWE-693 - Protection Mechanism Failure
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex Affected: 2.5.0 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: b4e665d46be8a5f81e8cbbe4c3a67058a19cf133 , < * (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105668",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "total"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:37:56.690913Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:38:34.235Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.RemoteConverger\u0027",
                "\u0027Elixir.Hex.Registry.Cooldown\u0027"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "lib/hex/remote_converger.ex",
                "lib/hex/registry/cooldown.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:build_cooldown_bypass/3"
                },
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:unsafe_lock_bypass/1"
                },
                {
                  "name": "\u0027Elixir.Hex.Registry.Cooldown\u0027:versions/2"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "2.5.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.RemoteConverger\u0027",
                "\u0027Elixir.Hex.Registry.Cooldown\u0027"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "lib/hex/remote_converger.ex",
                "lib/hex/registry/cooldown.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:build_cooldown_bypass/3"
                },
                {
                  "name": "\u0027Elixir.Hex.RemoteConverger\u0027:unsafe_lock_bypass/1"
                },
                {
                  "name": "\u0027Elixir.Hex.Registry.Cooldown\u0027:versions/2"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "eed2bdf85320ae9e244629511c03f5f3f8c5d0b5",
                      "status": "unaffected"
                    },
                    {
                      "at": "1b6fddfeadd98ed26a143f60356efade0494e2ae",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "b4e665d46be8a5f81e8cbbe4c3a67058a19cf133",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects projects that set the \u003ccode\u003ecooldown\u003c/code\u003e config or \u003ccode\u003eHEX_COOLDOWN\u003c/code\u003e and update dependencies with \u003ccode\u003emix deps.update\u003c/code\u003e, or otherwise unlock the package during resolution. Cooldowns enforced through an organization dependency policy aren\u0027t affected.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects projects that set the `cooldown` config or `HEX_COOLDOWN` and update dependencies with `mix deps.update`, or otherwise unlock the package during resolution. Cooldowns enforced through an organization dependency policy aren\u0027t affected."
                }
              ],
              "value": "Affects projects that set the cooldown config or HEX_COOLDOWN and update dependencies with mix deps.update, or otherwise unlock the package during resolution. Cooldowns enforced through an organization dependency policy aren\u0027t affected."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "2.5.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-10T13:01:56.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eProtection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner\u0027s account, to get a newly published release past the release-age cooldown.\u003c/p\u003e\n\u003cp\u003eThe cooldown (\u003ccode\u003ecooldown\u003c/code\u003e config, \u003ccode\u003eHEX_COOLDOWN\u003c/code\u003e) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in \u003ccode\u003emix.lock\u003c/code\u003e is retired or has a security advisory, \u003ccode\u003eHex.RemoteConverger.build_cooldown_bypass/3\u003c/code\u003e and \u003ccode\u003eHex.RemoteConverger.unsafe_lock_bypass/1\u003c/code\u003e lift the cooldown for every version of that package and of every package the locked release depends on, so that \u003ccode\u003emix deps.update\u003c/code\u003e can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next \u003ccode\u003emix deps.update\u003c/code\u003e that unlocks the package selects the new release regardless of its age. \u003ccode\u003eHex.Registry.Cooldown.versions/2\u003c/code\u003e checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 2.5.0 before 2.5.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Protection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner\u0027s account, to get a newly published release past the release-age cooldown.\n\nThe cooldown (`cooldown` config, `HEX_COOLDOWN`) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in `mix.lock` is retired or has a security advisory, `Hex.RemoteConverger.build_cooldown_bypass/3` and `Hex.RemoteConverger.unsafe_lock_bypass/1` lift the cooldown for every version of that package and of every package the locked release depends on, so that `mix deps.update` can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next `mix deps.update` that unlocks the package selects the new release regardless of its age. `Hex.Registry.Cooldown.versions/2` checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.\n\nThis issue affects hex: from 2.5.0 before 2.5.2."
                }
              ],
              "value": "Protection Mechanism Failure vulnerability in hexpm hex allows the owner of a package, or an attacker who has taken over the owner\u0027s account, to get a newly published release past the release-age cooldown.\n\nThe cooldown (cooldown config, HEX_COOLDOWN) withholds versions younger than the configured age during dependency resolution. When the release of a package locked in mix.lock is retired or has a security advisory, Hex.RemoteConverger.build_cooldown_bypass/3 and Hex.RemoteConverger.unsafe_lock_bypass/1 lift the cooldown for every version of that package and of every package the locked release depends on, so that mix deps.update can move off the unsafe release. Retiring a release needs the same package owner permission as publishing one. An attacker who publishes a malicious release can retire the currently locked release, and the next mix deps.update that unlocks the package selects the new release regardless of its age. Hex.Registry.Cooldown.versions/2 checks the bypass by package name alone, so the bypass also covers packages with the same name in other repositories. Cooldown configured through an organization dependency policy is not affected.\n\nThis issue affects hex: from 2.5.0 before 2.5.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-538",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eThe cooldown exists to keep a compromised release out of projects until it can be detected and retired. An attacker who controls a package owner account can defeat it by also retiring the currently locked release, so a malicious release reaches projects that rely on the local cooldown as soon as they run \u003ccode\u003emix deps.update\u003c/code\u003e, including automated dependency update jobs. The malicious release\u0027s code then runs when the dependency is compiled.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "The cooldown exists to keep a compromised release out of projects until it can be detected and retired. An attacker who controls a package owner account can defeat it by also retiring the currently locked release, so a malicious release reaches projects that rely on the local cooldown as soon as they run `mix deps.update`, including automated dependency update jobs. The malicious release\u0027s code then runs when the dependency is compiled."
                    }
                  ],
                  "value": "The cooldown exists to keep a compromised release out of projects until it can be detected and retired. An attacker who controls a package owner account can defeat it by also retiring the currently locked release, so a malicious release reaches projects that rely on the local cooldown as soon as they run mix deps.update, including automated dependency update jobs. The malicious release\u0027s code then runs when the dependency is compiled."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 7.1,
                "baseSeverity": "HIGH",
                "privilegesRequired": "HIGH",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "HIGH",
                "vulnIntegrityImpact": "HIGH",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-693",
                  "description": "CWE-693 Protection Mechanism Failure",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:31:58.926Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex/security/advisories/GHSA-pqhp-5x9x-69h7"
            },
            {
              "name": "EEF CNA record for CVE-2026-105668",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105668.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105668",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105668"
            },
            {
              "name": "Introducing commit b4e665d in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/b4e665d46be8a5f81e8cbbe4c3a67058a19cf133"
            },
            {
              "name": "Fix commit eed2bdf in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/eed2bdf85320ae9e244629511c03f5f3f8c5d0b5"
            },
            {
              "name": "Fix commit 1b6fddf in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/1b6fddfeadd98ed26a143f60356efade0494e2ae"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. A retired locked release then no longer lifts the release-age cooldown for its package. A locked release with a security advisory still lifts it for its package and the direct dependencies of the locked release, only in the repository the package is locked from.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. A retired locked release then no longer lifts the release-age cooldown for its package. A locked release with a security advisory still lifts it for its package and the direct dependencies of the locked release, only in the repository the package is locked from."
                }
              ],
              "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. A retired locked release then no longer lifts the release-age cooldown for its package. A locked release with a security advisory still lifts it for its package and the direct dependencies of the locked release, only in the repository the package is locked from."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Hex release-age cooldown is lifted package-wide when the locked release is retired",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eEnforce the cooldown through an organization dependency policy instead of the local \u003ccode\u003ecooldown\u003c/code\u003e setting, or run \u003ccode\u003emix hex.audit\u003c/code\u003e before \u003ccode\u003emix deps.update\u003c/code\u003e and don\u0027t update a package whose locked release was retired until its replacement has been reviewed.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Enforce the cooldown through an organization dependency policy instead of the local `cooldown` setting, or run `mix hex.audit` before `mix deps.update` and don\u0027t update a package whose locked release was retired until its replacement has been reviewed."
                }
              ],
              "value": "Enforce the cooldown through an organization dependency policy instead of the local cooldown setting, or run mix hex.audit before mix deps.update and don\u0027t update a package whose locked release was retired until its replacement has been reviewed."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eLock \u003ccode\u003efoo 1.0.0\u003c/code\u003e in a project with \u003ccode\u003eHEX_COOLDOWN=7d\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ePublish \u003ccode\u003efoo 1.0.1\u003c/code\u003e; \u003ccode\u003emix deps.update foo\u003c/code\u003e reports it as filtered by cooldown and keeps 1.0.0.\u003c/li\u003e\n\u003cli\u003eRetire \u003ccode\u003efoo 1.0.0\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003emix deps.update foo\u003c/code\u003e again; it upgrades to the 1-hour-old 1.0.1.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Lock `foo 1.0.0` in a project with `HEX_COOLDOWN=7d`.\n2. Publish `foo 1.0.1`; `mix deps.update foo` reports it as filtered by cooldown and keeps 1.0.0.\n3. Retire `foo 1.0.0`.\n4. Run `mix deps.update foo` again; it upgrades to the 1-hour-old 1.0.1."
                }
              ],
              "value": "* Lock foo 1.0.0 in a project with HEX_COOLDOWN=7d.\n* Publish foo 1.0.1; mix deps.update foo reports it as filtered by cooldown and keeps 1.0.0.\n* Retire foo 1.0.0.\n* Run mix deps.update foo again; it upgrades to the 1-hour-old 1.0.1."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003eHex.RemoteConverger.build_cooldown_bypass/3\u003c/code\u003e adds \u003ccode\u003eunsafe_lock_bypass(old_lock)\u003c/code\u003e to the set of packages exempt from the cooldown.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eunsafe_lock_bypass/1\u003c/code\u003e adds a package\u0027s name, and the names of its locked children, whenever \u003ccode\u003eRegistry.retired/3\u003c/code\u003e or \u003ccode\u003eRegistry.advisories/3\u003c/code\u003e reports the locked version as unsafe.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHex.Registry.Cooldown.versions/2\u003c/code\u003e returns every version unfiltered for any package name in that set, so freshly published versions become eligible as soon as the package is unlocked.\u003c/li\u003e\n\u003cli\u003ehex.pm lets the package owner retire any of their releases, so the owner controls the trigger. \u003ccode\u003eHex.Policy.Filter\u003c/code\u003e applies policy cooldowns without consulting the bypass set.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `Hex.RemoteConverger.build_cooldown_bypass/3` adds `unsafe_lock_bypass(old_lock)` to the set of packages exempt from the cooldown.\n2. `unsafe_lock_bypass/1` adds a package\u0027s name, and the names of its locked children, whenever `Registry.retired/3` or `Registry.advisories/3` reports the locked version as unsafe.\n3. `Hex.Registry.Cooldown.versions/2` returns every version unfiltered for any package name in that set, so freshly published versions become eligible as soon as the package is unlocked.\n4. hex.pm lets the package owner retire any of their releases, so the owner controls the trigger. `Hex.Policy.Filter` applies policy cooldowns without consulting the bypass set."
                }
              ],
              "value": "* Hex.RemoteConverger.build_cooldown_bypass/3 adds unsafe_lock_bypass(old_lock) to the set of packages exempt from the cooldown.\n* unsafe_lock_bypass/1 adds a package\u0027s name, and the names of its locked children, whenever Registry.retired/3 or Registry.advisories/3 reports the locked version as unsafe.\n* Hex.Registry.Cooldown.versions/2 returns every version unfiltered for any package name in that set, so freshly published versions become eligible as soon as the package is unlocked.\n* hex.pm lets the package owner retire any of their releases, so the owner controls the trigger. Hex.Policy.Filter applies policy cooldowns without consulting the bypass set."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105668",
        "datePublished": "2026-10-12T15:31:58.926Z",
        "dateReserved": "2026-10-07T22:15:01.885Z",
        "dateUpdated": "2026-10-16T17:38:34.235Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105280 (GCVE-0-2026-105280)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:30 – Updated: 2026-10-16 17:37
    VLAI
    Title
    Hex writes unverified package tarballs to standard output in mix hex.package fetch
    Summary
    Improper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that mix hex.package fetch PACKAGE VERSION --output - writes to standard output. Hex compares a fetched tarball's inner and outer checksums with the repository's signed registry before writing it to a file or unpacking it. The standard output branch of mix hex.package fetch writes the bytes returned by Hex.SCM.fetch/3 without that comparison, and Hex.SCM.fetch/3 doesn't verify freshly downloaded tarballs itself. When a mirror configured with HEX_MIRROR_URL or mirror_url serves altered bytes, the command prints them and exits successfully, while the same fetch with --output FILE fails with a checksum mismatch. Hex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the x-hex-message header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror's files. This happens even when the checksum check then fails, because the archive is already on standard output. This issue affects hex: from 0.20.2 before 2.5.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:35 UTC
    CWE
    • CWE-354 - Improper Validation of Integrity Check Value
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex Affected: 0.20.2 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: d1f5a085d9d4748e1f7d195e4bc39ebd77c28d9c , < * (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105280",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:35:17.920015Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:37:21.498Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mix.Tasks.Hex.Package\u0027",
                "\u0027Elixir.Hex.SCM\u0027"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "lib/mix/tasks/hex.package.ex",
                "lib/hex/scm.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mix.Tasks.Hex.Package\u0027:run/1"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "0.20.2",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mix.Tasks.Hex.Package\u0027",
                "\u0027Elixir.Hex.SCM\u0027"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "lib/mix/tasks/hex.package.ex",
                "lib/hex/scm.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mix.Tasks.Hex.Package\u0027:run/1"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "8a14632ca6ba8a8afd2a3f2098bc267c13e13d93",
                      "status": "unaffected"
                    },
                    {
                      "at": "69489f8b74213f03db10169e6e5407e6d5fee512",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "d1f5a085d9d4748e1f7d195e4bc39ebd77c28d9c",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects \u003ccode\u003emix hex.package fetch\u003c/code\u003e with \u003ccode\u003e--output -\u003c/code\u003e when packages are downloaded through a mirror set with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e. Downloads directly from the repository over HTTPS aren\u0027t affected unless TLS verification is broken.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects `mix hex.package fetch` with `--output -` when packages are downloaded through a mirror set with `HEX_MIRROR_URL` or `mirror_url`. Downloads directly from the repository over HTTPS aren\u0027t affected unless TLS verification is broken."
                }
              ],
              "value": "Affects mix hex.package fetch with --output - when packages are downloaded through a mirror set with HEX_MIRROR_URL or mirror_url. Downloads directly from the repository over HTTPS aren\u0027t affected unless TLS verification is broken."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "0.20.2",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-10T13:01:30.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that \u003ccode\u003emix hex.package fetch PACKAGE VERSION --output -\u003c/code\u003e writes to standard output.\u003c/p\u003e\n\u003cp\u003eHex compares a fetched tarball\u0027s inner and outer checksums with the repository\u0027s signed registry before writing it to a file or unpacking it. The standard output branch of \u003ccode\u003emix hex.package fetch\u003c/code\u003e writes the bytes returned by \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e without that comparison, and \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e doesn\u0027t verify freshly downloaded tarballs itself. When a mirror configured with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e serves altered bytes, the command prints them and exits successfully, while the same fetch with \u003ccode\u003e--output FILE\u003c/code\u003e fails with a checksum mismatch.\u003c/p\u003e\n\u003cp\u003eHex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the \u003ccode\u003ex-hex-message\u003c/code\u003e header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror\u0027s files. This happens even when the checksum check then fails, because the archive is already on standard output.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.20.2 before 2.5.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that `mix hex.package fetch PACKAGE VERSION --output -` writes to standard output.\n\nHex compares a fetched tarball\u0027s inner and outer checksums with the repository\u0027s signed registry before writing it to a file or unpacking it. The standard output branch of `mix hex.package fetch` writes the bytes returned by `Hex.SCM.fetch/3` without that comparison, and `Hex.SCM.fetch/3` doesn\u0027t verify freshly downloaded tarballs itself. When a mirror configured with `HEX_MIRROR_URL` or `mirror_url` serves altered bytes, the command prints them and exits successfully, while the same fetch with `--output FILE` fails with a checksum mismatch.\n\nHex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the `x-hex-message` header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror\u0027s files. This happens even when the checksum check then fails, because the archive is already on standard output.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
                }
              ],
              "value": "Improper Validation of Integrity Check Value vulnerability in hexpm hex allows a package mirror to substitute the tarball that mix hex.package fetch PACKAGE VERSION --output - writes to standard output.\n\nHex compares a fetched tarball\u0027s inner and outer checksums with the repository\u0027s signed registry before writing it to a file or unpacking it. The standard output branch of mix hex.package fetch writes the bytes returned by Hex.SCM.fetch/3 without that comparison, and Hex.SCM.fetch/3 doesn\u0027t verify freshly downloaded tarballs itself. When a mirror configured with HEX_MIRROR_URL or mirror_url serves altered bytes, the command prints them and exits successfully, while the same fetch with --output FILE fails with a checksum mismatch.\n\nHex also prints diagnostics to standard output while the command runs: the body of a failed HTTP response from the update check or a registry fetch, and API warnings from the x-hex-message header. A mirror that answers one of those requests with an error whose body is a tar archive gets that archive written to standard output ahead of the verified tarball, so a pipeline that unpacks standard output extracts the mirror\u0027s files. This happens even when the checksum check then fails, because the archive is already on standard output.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-186",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eHex treats mirrors as untrusted and relies on registry signatures and checksums for package integrity. Scripts that pipe \u003ccode\u003emix hex.package fetch --output -\u003c/code\u003e into another tool, such as an unpacker, a vendoring step or an installer, receive whatever the mirror served, so the mirror operator can deliver modified package contents that downstream steps treat as the published release.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "Hex treats mirrors as untrusted and relies on registry signatures and checksums for package integrity. Scripts that pipe `mix hex.package fetch --output -` into another tool, such as an unpacker, a vendoring step or an installer, receive whatever the mirror served, so the mirror operator can deliver modified package contents that downstream steps treat as the published release."
                    }
                  ],
                  "value": "Hex treats mirrors as untrusted and relies on registry signatures and checksums for package integrity. Scripts that pipe mix hex.package fetch --output - into another tool, such as an unpacker, a vendoring step or an installer, receive whatever the mirror served, so the mirror operator can deliver modified package contents that downstream steps treat as the published release."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "HIGH",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:H/VA:N/SC:N/SI:H/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "HIGH",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-354",
                  "description": "CWE-354 Improper Validation of Integrity Check Value",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:30:14.126Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex/security/advisories/GHSA-98jr-23pm-vj7c"
            },
            {
              "name": "EEF CNA record for CVE-2026-105280",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105280.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105280",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105280"
            },
            {
              "name": "Introducing commit d1f5a08 in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/d1f5a085d9d4748e1f7d195e4bc39ebd77c28d9c"
            },
            {
              "name": "Fix commit 8a14632 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/8a14632ca6ba8a8afd2a3f2098bc267c13e13d93"
            },
            {
              "name": "Fix commit 69489f8 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/69489f8b74213f03db10169e6e5407e6d5fee512"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. \u003ccode\u003emix hex.package fetch PACKAGE VERSION --output -\u003c/code\u003e then verifies the registry checksum before writing the tarball to standard output and prints its messages to standard error.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. `mix hex.package fetch PACKAGE VERSION --output -` then verifies the registry checksum before writing the tarball to standard output and prints its messages to standard error."
                }
              ],
              "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. mix hex.package fetch PACKAGE VERSION --output - then verifies the registry checksum before writing the tarball to standard output and prints its messages to standard error."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Hex writes unverified package tarballs to standard output in mix hex.package fetch",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eWrite the tarball to a file with \u003ccode\u003e--output FILE\u003c/code\u003e, which verifies the checksums, and read that file instead of standard output.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Write the tarball to a file with `--output FILE`, which verifies the checksums, and read that file instead of standard output."
                }
              ],
              "value": "Write the tarball to a file with --output FILE, which verifies the checksums, and read that file instead of standard output."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eSet \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e to a server that returns modified bytes for \u003ccode\u003etarballs/decimal-2.4.1.tar\u003c/code\u003e and proxies everything else to repo.hex.pm.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emix hex.package fetch decimal 2.4.1 --output -\u003c/code\u003e prints the modified bytes and exits 0.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emix hex.package fetch decimal 2.4.1 --output decimal.tar\u003c/code\u003e fails with \u003ccode\u003eChecksum mismatch against registry (outer)\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Set `HEX_MIRROR_URL` to a server that returns modified bytes for `tarballs/decimal-2.4.1.tar` and proxies everything else to repo.hex.pm.\n2. `mix hex.package fetch decimal 2.4.1 --output -` prints the modified bytes and exits 0.\n3. `mix hex.package fetch decimal 2.4.1 --output decimal.tar` fails with `Checksum mismatch against registry (outer)`."
                }
              ],
              "value": "* Set HEX_MIRROR_URL to a server that returns modified bytes for tarballs/decimal-2.4.1.tar and proxies everything else to repo.hex.pm.\n* mix hex.package fetch decimal 2.4.1 --output - prints the modified bytes and exits 0.\n* mix hex.package fetch decimal 2.4.1 --output decimal.tar fails with Checksum mismatch against registry (outer)."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003emix hex.package fetch PACKAGE VERSION --output -\u003c/code\u003e calls \u003ccode\u003efetch_tarball!/3\u003c/code\u003e, which calls \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eFor a fresh download, \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e writes the response body to the cache and returns it without comparing it to the registry checksum.\u003c/li\u003e\n\u003cli\u003eThe standard output branch passes those bytes to \u003ccode\u003eIO.binwrite/1\u003c/code\u003e. The file and \u003ccode\u003e--unpack\u003c/code\u003e branches call \u003ccode\u003everify_inner_checksum!/4\u003c/code\u003e and \u003ccode\u003everify_outer_checksum!/4\u003c/code\u003e first.\u003c/li\u003e\n\u003cli\u003eWhile the task runs, Hex prints messages with \u003ccode\u003eHex.Shell.info/1\u003c/code\u003e, which writes to standard output. \u003ccode\u003eHex.Utils.print_error_result/1\u003c/code\u003e prints the body of an error response that way; the update checker calls it when its request for \u003ccode\u003einstalls/hex-1.x.csv\u003c/code\u003e fails, and \u003ccode\u003eHex.Registry.Server\u003c/code\u003e calls it when a registry fetch fails and the cached registry is used. \u003ccode\u003eHex.HTTP\u003c/code\u003e prints the \u003ccode\u003ex-hex-message\u003c/code\u003e warning of each response the same way. These are printed before the tarball is written, so the mirror chooses bytes that come before the tarball on standard output.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `mix hex.package fetch PACKAGE VERSION --output -` calls `fetch_tarball!/3`, which calls `Hex.SCM.fetch/3`.\n2. For a fresh download, `Hex.SCM.fetch/3` writes the response body to the cache and returns it without comparing it to the registry checksum.\n3. The standard output branch passes those bytes to `IO.binwrite/1`. The file and `--unpack` branches call `verify_inner_checksum!/4` and `verify_outer_checksum!/4` first.\n4. While the task runs, Hex prints messages with `Hex.Shell.info/1`, which writes to standard output. `Hex.Utils.print_error_result/1` prints the body of an error response that way; the update checker calls it when its request for `installs/hex-1.x.csv` fails, and `Hex.Registry.Server` calls it when a registry fetch fails and the cached registry is used. `Hex.HTTP` prints the `x-hex-message` warning of each response the same way. These are printed before the tarball is written, so the mirror chooses bytes that come before the tarball on standard output."
                }
              ],
              "value": "* mix hex.package fetch PACKAGE VERSION --output - calls fetch_tarball!/3, which calls Hex.SCM.fetch/3.\n* For a fresh download, Hex.SCM.fetch/3 writes the response body to the cache and returns it without comparing it to the registry checksum.\n* The standard output branch passes those bytes to IO.binwrite/1. The file and --unpack branches call verify_inner_checksum!/4 and verify_outer_checksum!/4 first.\n* While the task runs, Hex prints messages with Hex.Shell.info/1, which writes to standard output. Hex.Utils.print_error_result/1 prints the body of an error response that way; the update checker calls it when its request for installs/hex-1.x.csv fails, and Hex.Registry.Server calls it when a registry fetch fails and the cached registry is used. Hex.HTTP prints the x-hex-message warning of each response the same way. These are printed before the tarball is written, so the mirror chooses bytes that come before the tarball on standard output."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105280",
        "datePublished": "2026-10-12T15:30:14.126Z",
        "dateReserved": "2026-10-08T13:45:02.134Z",
        "dateUpdated": "2026-10-16T17:37:21.498Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-96618 (GCVE-0-2026-96618)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:29 – Updated: 2026-10-16 17:33
    VLAI
    Title
    Hex exhausts memory and disk on oversized or compressed responses from an untrusted mirror
    Summary
    Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex allows an untrusted package mirror to exhaust the memory or disk space of the machine running mix deps.get, mix deps.update, mix hex.package fetch or mix hex.docs fetch. Hex treats a mirror configured with HEX_MIRROR_URL, HEX_MIRROR or mirror_url as untrusted: it doesn't send credentials to it, and it checks registry signatures and package checksums that the mirror can't forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run. Hex.HTTP.request/5 reads the whole response body into memory and decompresses it with :zlib.gunzip/1 whenever the response's Content-Encoding contains gzip, with no limit on the compressed or decompressed size. Hex doesn't ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses. Package tarballs have no download size limit. Hex 2.5.1 holds the whole tarball in memory and writes it to the cache, and the development branch streams it to a temporary file; the 16 MiB tarball limit is checked only when the file is unpacked. The tarball is then unpacked, into deps/<app> in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry. mix hex.docs fetch, and mix hex.docs offline when docs are missing, write the docs tarball to disk and extract it with :mix_hex_erl_tar.extract/2 without the 16 MiB compressed and 128 MiB decompressed limits that mix_hex_tarball:unpack_docs/3 applies. The signed registry doesn't cover docs tarballs, so nothing checks them. Registry responses are also decompressed before their signature is checked, in hex_core code that Hex vendors; that part is tracked in https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq. This issue affects hex: from 0.11.2 before 2.5.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:33 UTC
    CWE
    • CWE-409 - Improper Handling of Highly Compressed Data (Data Amplification)
    • CWE-770 - Allocation of Resources Without Limits or Throttling
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex Affected: 0.11.2 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: 51ea632424fb6ea8765148f96dc25dfeb526b1f8 , < * (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-96618",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:33:32.316951Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:33:51.114Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.HTTP\u0027",
                "\u0027Elixir.Hex.SCM\u0027",
                "\u0027Elixir.Mix.Tasks.Hex.Docs\u0027"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "lib/hex/http.ex",
                "lib/hex/scm.ex",
                "lib/mix/tasks/hex.docs.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.HTTP\u0027:request/5"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:update/1"
                },
                {
                  "name": "\u0027Elixir.Mix.Tasks.Hex.Docs\u0027:run/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "0.11.2",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.HTTP\u0027",
                "\u0027Elixir.Hex.SCM\u0027",
                "\u0027Elixir.Mix.Tasks.Hex.Docs\u0027"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "lib/hex/http.ex",
                "lib/hex/scm.ex",
                "lib/mix/tasks/hex.docs.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.HTTP\u0027:request/5"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:fetch/3"
                },
                {
                  "name": "\u0027Elixir.Hex.SCM\u0027:update/1"
                },
                {
                  "name": "\u0027Elixir.Mix.Tasks.Hex.Docs\u0027:run/1"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "bb5189eade682c5fc3f98d9725973a7beb4e1deb",
                      "status": "unaffected"
                    },
                    {
                      "at": "08956100a8283ba500a123e5539ac64d5d7e91d8",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "51ea632424fb6ea8765148f96dc25dfeb526b1f8",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects users who fetch packages or docs through a mirror set with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e, \u003ccode\u003eHEX_MIRROR\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e. Fetches from repo.hex.pm over HTTPS aren\u0027t affected unless TLS verification is broken. A trusted mirror (\u003ccode\u003eHEX_TRUSTED_MIRROR_URL\u003c/code\u003e) or a self-hosted repository can do the same, but Hex already trusts those with credentials.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects users who fetch packages or docs through a mirror set with `HEX_MIRROR_URL`, `HEX_MIRROR` or `mirror_url`. Fetches from repo.hex.pm over HTTPS aren\u0027t affected unless TLS verification is broken. A trusted mirror (`HEX_TRUSTED_MIRROR_URL`) or a self-hosted repository can do the same, but Hex already trusts those with credentials."
                }
              ],
              "value": "Affects users who fetch packages or docs through a mirror set with HEX_MIRROR_URL, HEX_MIRROR or mirror_url. Fetches from repo.hex.pm over HTTPS aren\u0027t affected unless TLS verification is broken. A trusted mirror (HEX_TRUSTED_MIRROR_URL) or a self-hosted repository can do the same, but Hex already trusts those with credentials."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "0.11.2",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-10T13:00:30.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex allows an untrusted package mirror to exhaust the memory or disk space of the machine running \u003ccode\u003emix deps.get\u003c/code\u003e, \u003ccode\u003emix deps.update\u003c/code\u003e, \u003ccode\u003emix hex.package fetch\u003c/code\u003e or \u003ccode\u003emix hex.docs fetch\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHex treats a mirror configured with \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e, \u003ccode\u003eHEX_MIRROR\u003c/code\u003e or \u003ccode\u003emirror_url\u003c/code\u003e as untrusted: it doesn\u0027t send credentials to it, and it checks registry signatures and package checksums that the mirror can\u0027t forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eHex.HTTP.request/5\u003c/code\u003e reads the whole response body into memory and decompresses it with \u003ccode\u003e:zlib.gunzip/1\u003c/code\u003e whenever the response\u0027s \u003ccode\u003eContent-Encoding\u003c/code\u003e contains \u003ccode\u003egzip\u003c/code\u003e, with no limit on the compressed or decompressed size. Hex doesn\u0027t ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses.\u003c/p\u003e\n\u003cp\u003ePackage tarballs have no download size limit. Hex 2.5.1 holds the whole tarball in memory and writes it to the cache, and the development branch streams it to a temporary file; the 16 MiB tarball limit is checked only when the file is unpacked. The tarball is then unpacked, into \u003ccode\u003edeps/\u0026lt;app\u0026gt;\u003c/code\u003e in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003emix hex.docs fetch\u003c/code\u003e, and \u003ccode\u003emix hex.docs offline\u003c/code\u003e when docs are missing, write the docs tarball to disk and extract it with \u003ccode\u003e:mix_hex_erl_tar.extract/2\u003c/code\u003e without the 16 MiB compressed and 128 MiB decompressed limits that \u003ccode\u003emix_hex_tarball:unpack_docs/3\u003c/code\u003e applies. The signed registry doesn\u0027t cover docs tarballs, so nothing checks them.\u003c/p\u003e\n\u003cp\u003eRegistry responses are also decompressed before their signature is checked, in hex_core code that Hex vendors; that part is tracked in \u003ca href=\"https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq\" rel=\"noopener noreferrer\"\u003ehttps://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.11.2 before 2.5.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex allows an untrusted package mirror to exhaust the memory or disk space of the machine running `mix deps.get`, `mix deps.update`, `mix hex.package fetch` or `mix hex.docs fetch`.\n\nHex treats a mirror configured with `HEX_MIRROR_URL`, `HEX_MIRROR` or `mirror_url` as untrusted: it doesn\u0027t send credentials to it, and it checks registry signatures and package checksums that the mirror can\u0027t forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.\n\n`Hex.HTTP.request/5` reads the whole response body into memory and decompresses it with `:zlib.gunzip/1` whenever the response\u0027s `Content-Encoding` contains `gzip`, with no limit on the compressed or decompressed size. Hex doesn\u0027t ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses.\n\nPackage tarballs have no download size limit. Hex 2.5.1 holds the whole tarball in memory and writes it to the cache, and the development branch streams it to a temporary file; the 16 MiB tarball limit is checked only when the file is unpacked. The tarball is then unpacked, into `deps/\u003capp\u003e` in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.\n\n`mix hex.docs fetch`, and `mix hex.docs offline` when docs are missing, write the docs tarball to disk and extract it with `:mix_hex_erl_tar.extract/2` without the 16 MiB compressed and 128 MiB decompressed limits that `mix_hex_tarball:unpack_docs/3` applies. The signed registry doesn\u0027t cover docs tarballs, so nothing checks them.\n\nRegistry responses are also decompressed before their signature is checked, in hex_core code that Hex vendors; that part is tracked in https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq.\n\nThis issue affects hex: from 0.11.2 before 2.5.2."
                }
              ],
              "value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in hexpm hex allows an untrusted package mirror to exhaust the memory or disk space of the machine running mix deps.get, mix deps.update, mix hex.package fetch or mix hex.docs fetch.\n\nHex treats a mirror configured with HEX_MIRROR_URL, HEX_MIRROR or mirror_url as untrusted: it doesn\u0027t send credentials to it, and it checks registry signatures and package checksums that the mirror can\u0027t forge. Several response paths consume memory or disk in proportion to what the mirror sends before any of those checks run.\n\nHex.HTTP.request/5 reads the whole response body into memory and decompresses it with :zlib.gunzip/1 whenever the response\u0027s Content-Encoding contains gzip, with no limit on the compressed or decompressed size. Hex doesn\u0027t ask for compressed responses, so the server decides whether this step runs. It applies to registry, package, docs and API responses.\n\nPackage tarballs have no download size limit. Hex 2.5.1 holds the whole tarball in memory and writes it to the cache, and the development branch streams it to a temporary file; the 16 MiB tarball limit is checked only when the file is unpacked. The tarball is then unpacked, into deps/\u003capp\u003e in 2.5.1 and into a staging directory on the development branch, before its checksums are compared with the registry.\n\nmix hex.docs fetch, and mix hex.docs offline when docs are missing, write the docs tarball to disk and extract it with :mix_hex_erl_tar.extract/2 without the 16 MiB compressed and 128 MiB decompressed limits that mix_hex_tarball:unpack_docs/3 applies. The signed registry doesn\u0027t cover docs tarballs, so nothing checks them.\n\nRegistry responses are also decompressed before their signature is checked, in hex_core code that Hex vendors; that part is tracked in https://github.com/hexpm/hex_core/security/advisories/GHSA-7c4r-cgf9-9jmq.\n\nThis issue affects hex: from 0.11.2 before 2.5.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA mirror operator, or an attacker who controls the mirror\u0027s responses, can make Hex allocate memory or write data far larger than what it sent: in tests against Hex main 851b938, a 65 KB gzip-encoded response decompressed to 64 MiB in memory, a 17 MiB tarball was downloaded in full despite the 16 MiB limit, and a 131 KB docs tarball wrote 129 MiB to disk. Larger responses stop the Erlang VM running Mix or fill the disk, failing builds and CI jobs, and a full disk affects other processes on the machine. No package code has to run, and the mirror doesn\u0027t need the repository\u0027s signing key.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A mirror operator, or an attacker who controls the mirror\u0027s responses, can make Hex allocate memory or write data far larger than what it sent: in tests against Hex main 851b938, a 65 KB gzip-encoded response decompressed to 64 MiB in memory, a 17 MiB tarball was downloaded in full despite the 16 MiB limit, and a 131 KB docs tarball wrote 129 MiB to disk. Larger responses stop the Erlang VM running Mix or fill the disk, failing builds and CI jobs, and a full disk affects other processes on the machine. No package code has to run, and the mirror doesn\u0027t need the repository\u0027s signing key."
                    }
                  ],
                  "value": "A mirror operator, or an attacker who controls the mirror\u0027s responses, can make Hex allocate memory or write data far larger than what it sent: in tests against Hex main 851b938, a 65 KB gzip-encoded response decompressed to 64 MiB in memory, a 17 MiB tarball was downloaded in full despite the 16 MiB limit, and a 131 KB docs tarball wrote 129 MiB to disk. Larger responses stop the Erlang VM running Mix or fill the disk, failing builds and CI jobs, and a full disk affects other processes on the machine. No package code has to run, and the mirror doesn\u0027t need the repository\u0027s signing key."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 2.1,
                "baseSeverity": "LOW",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "LOW",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-409",
                  "description": "CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            },
            {
              "descriptions": [
                {
                  "cweId": "CWE-770",
                  "description": "CWE-770 Allocation of Resources Without Limits or Throttling",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:29:19.117Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex/security/advisories/GHSA-gf42-pg2f-63jp"
            },
            {
              "name": "EEF CNA record for CVE-2026-96618",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-96618.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-96618",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-96618"
            },
            {
              "name": "Introducing commit 51ea632 in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/51ea632424fb6ea8765148f96dc25dfeb526b1f8"
            },
            {
              "name": "Fix commit bb5189e in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/bb5189eade682c5fc3f98d9725973a7beb4e1deb"
            },
            {
              "name": "Fix commit 0895610 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/08956100a8283ba500a123e5539ac64d5d7e91d8"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. Hex then stops downloading a registry file, package, docs tarball or other file from a repository or mirror once it\u0027s larger than 16 MiB, including after decompressing a gzip-encoded response, and checks packages against the registry checksum before writing them to the package cache or unpacking a cached copy.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. Hex then stops downloading a registry file, package, docs tarball or other file from a repository or mirror once it\u0027s larger than 16 MiB, including after decompressing a gzip-encoded response, and checks packages against the registry checksum before writing them to the package cache or unpacking a cached copy."
                }
              ],
              "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. Hex then stops downloading a registry file, package, docs tarball or other file from a repository or mirror once it\u0027s larger than 16 MiB, including after decompressing a gzip-encoded response, and checks packages against the registry checksum before writing them to the package cache or unpacking a cached copy."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Hex exhausts memory and disk on oversized or compressed responses from an untrusted mirror",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eFetch from repo.hex.pm or a mirror you operate. Running Mix under operating system memory and disk limits, such as container limits, confines the effect to that container.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Fetch from repo.hex.pm or a mirror you operate. Running Mix under operating system memory and disk limits, such as container limits, confines the effect to that container."
                }
              ],
              "value": "Fetch from repo.hex.pm or a mirror you operate. Running Mix under operating system memory and disk limits, such as container limits, confines the effect to that container."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eRun a loopback HTTP server that answers with a 65,255-byte gzip stream decompressing to 64 MiB and \u003ccode\u003eContent-Encoding: gzip\u003c/code\u003e. \u003ccode\u003eHex.HTTP.request(:get, url, %{}, nil)\u003c/code\u003e returns a 67,108,864-byte body; peak RSS was 220 MiB, against 91 MiB for an uncompressed control.\u003c/li\u003e\n\u003cli\u003eServe a 17 MiB file as a package tarball. \u003ccode\u003eHex.Repo.get_tarball/3\u003c/code\u003e returns all of it, and only the following unpack fails with \u003ccode\u003e{:tarball, :too_big}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eServe a 131,574-byte docs tarball containing a 129 MiB file and set \u003ccode\u003eHEX_MIRROR_URL\u003c/code\u003e to the server. \u003ccode\u003emix hex.docs fetch probe 1.0.0\u003c/code\u003e writes the 129 MiB file and reports success.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Run a loopback HTTP server that answers with a 65,255-byte gzip stream decompressing to 64 MiB and `Content-Encoding: gzip`. `Hex.HTTP.request(:get, url, %{}, nil)` returns a 67,108,864-byte body; peak RSS was 220 MiB, against 91 MiB for an uncompressed control.\n2. Serve a 17 MiB file as a package tarball. `Hex.Repo.get_tarball/3` returns all of it, and only the following unpack fails with `{:tarball, :too_big}`.\n3. Serve a 131,574-byte docs tarball containing a 129 MiB file and set `HEX_MIRROR_URL` to the server. `mix hex.docs fetch probe 1.0.0` writes the 129 MiB file and reports success."
                }
              ],
              "value": "* Run a loopback HTTP server that answers with a 65,255-byte gzip stream decompressing to 64 MiB and Content-Encoding: gzip. Hex.HTTP.request(:get, url, %{}, nil) returns a 67,108,864-byte body; peak RSS was 220 MiB, against 91 MiB for an uncompressed control.\n* Serve a 17 MiB file as a package tarball. Hex.Repo.get_tarball/3 returns all of it, and only the following unpack fails with {:tarball, :too_big}.\n* Serve a 131,574-byte docs tarball containing a 129 MiB file and set HEX_MIRROR_URL to the server. mix hex.docs fetch probe 1.0.0 writes the 129 MiB file and reports success."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003eHex.HTTP.request/5\u003c/code\u003e calls \u003ccode\u003e:httpc.request/5\u003c/code\u003e with \u003ccode\u003ebody_format: :binary\u003c/code\u003e, so the whole body is held in memory, and then calls \u003ccode\u003e:zlib.gunzip/1\u003c/code\u003e when the \u003ccode\u003econtent-encoding\u003c/code\u003e header contains \u003ccode\u003egzip\u003c/code\u003e. Hex doesn\u0027t send \u003ccode\u003eAccept-Encoding\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eWhen \u003ccode\u003emirror_url\u003c/code\u003e is set, \u003ccode\u003eHex.Repo\u003c/code\u003e sends registry, tarball and docs requests to the mirror and marks it untrusted, so no credentials are sent to it.\u003c/li\u003e\n\u003cli\u003eHex 2.5.1: \u003ccode\u003eHex.SCM.fetch/3\u003c/code\u003e writes the body returned by \u003ccode\u003eHex.Repo.get_tarball/3\u003c/code\u003e to the cache. \u003ccode\u003eHex.SCM.update/1\u003c/code\u003e deletes \u003ccode\u003edeps/\u0026lt;app\u0026gt;\u003c/code\u003e, unpacks the tarball into it with \u003ccode\u003eHex.Tar.unpack!/2\u003c/code\u003e and compares the inner and outer checksums afterwards. Development branch: \u003ccode\u003eHex.Repo.get_tarball_to_file/4\u003c/code\u003e streams the tarball to a temporary file without counting bytes; the file is renamed into the cache and unpacked into a staging directory before the checksums are compared.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eMix.Tasks.Hex.Docs\u003c/code\u003e writes the body returned by \u003ccode\u003eHex.Repo.get_docs/3\u003c/code\u003e to disk and calls \u003ccode\u003e:mix_hex_erl_tar.extract/2\u003c/code\u003e with \u003ccode\u003e[:compressed, cwd: ...]\u003c/code\u003e and no \u003ccode\u003emax_size\u003c/code\u003e, while \u003ccode\u003emix_hex_tarball:unpack_docs/3\u003c/code\u003e enforces a 16 MiB compressed and 128 MiB decompressed limit.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `Hex.HTTP.request/5` calls `:httpc.request/5` with `body_format: :binary`, so the whole body is held in memory, and then calls `:zlib.gunzip/1` when the `content-encoding` header contains `gzip`. Hex doesn\u0027t send `Accept-Encoding`.\n2. When `mirror_url` is set, `Hex.Repo` sends registry, tarball and docs requests to the mirror and marks it untrusted, so no credentials are sent to it.\n3. Hex 2.5.1: `Hex.SCM.fetch/3` writes the body returned by `Hex.Repo.get_tarball/3` to the cache. `Hex.SCM.update/1` deletes `deps/\u003capp\u003e`, unpacks the tarball into it with `Hex.Tar.unpack!/2` and compares the inner and outer checksums afterwards. Development branch: `Hex.Repo.get_tarball_to_file/4` streams the tarball to a temporary file without counting bytes; the file is renamed into the cache and unpacked into a staging directory before the checksums are compared.\n4. `Mix.Tasks.Hex.Docs` writes the body returned by `Hex.Repo.get_docs/3` to disk and calls `:mix_hex_erl_tar.extract/2` with `[:compressed, cwd: ...]` and no `max_size`, while `mix_hex_tarball:unpack_docs/3` enforces a 16 MiB compressed and 128 MiB decompressed limit."
                }
              ],
              "value": "* Hex.HTTP.request/5 calls :httpc.request/5 with body_format: :binary, so the whole body is held in memory, and then calls :zlib.gunzip/1 when the content-encoding header contains gzip. Hex doesn\u0027t send Accept-Encoding.\n* When mirror_url is set, Hex.Repo sends registry, tarball and docs requests to the mirror and marks it untrusted, so no credentials are sent to it.\n* Hex 2.5.1: Hex.SCM.fetch/3 writes the body returned by Hex.Repo.get_tarball/3 to the cache. Hex.SCM.update/1 deletes deps/\u003capp\u003e, unpacks the tarball into it with Hex.Tar.unpack!/2 and compares the inner and outer checksums afterwards. Development branch: Hex.Repo.get_tarball_to_file/4 streams the tarball to a temporary file without counting bytes; the file is renamed into the cache and unpacked into a staging directory before the checksums are compared.\n* Mix.Tasks.Hex.Docs writes the body returned by Hex.Repo.get_docs/3 to disk and calls :mix_hex_erl_tar.extract/2 with [:compressed, cwd: ...] and no max_size, while mix_hex_tarball:unpack_docs/3 enforces a 16 MiB compressed and 128 MiB decompressed limit."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-96618",
        "datePublished": "2026-10-12T15:29:19.117Z",
        "dateReserved": "2026-10-10T13:00:02.352Z",
        "dateUpdated": "2026-10-16T17:33:51.114Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-103503 (GCVE-0-2026-103503)

    Vulnerability from cvelistv5 – Published: 2026-10-12 15:17 – Updated: 2026-10-16 17:32
    VLAI
    Title
    Hex trusts its bundled CA certificates when cacerts_path is set
    Summary
    Improper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex's bundled CA store, to impersonate that host to users who set cacerts_path to a CA store that leaves that CA out. cacerts_path (HEX_CACERTS_PATH) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. Hex.HTTP.SSL.ssl_opts/1 passes the configured store as the cacerts option, but builds the partial_chain callback from the bundled store whether cacerts_path is set or not. Hex.HTTP.SSL.partial_chain/2 accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn't contain that CA. Hostname verification still applies. This issue affects hex: from 0.20.2 before 2.5.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 17:30 UTC
    CWE
    • CWE-295 - Improper Certificate Validation
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex Affected: 0.20.2 , < 2.5.2 (semver)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex Affected: c9b26a46928bfcd546773571e09c1d05f4cd9fb7 , < * (git)
        cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-103503",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T17:30:53.768172Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T17:32:45.735Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.HTTP.SSL\u0027"
              ],
              "packageName": "hex",
              "packageURL": "pkg:otp/hex?repository_url=https:%2F%2Fgithub.com%2Fhexpm%2Fhex\u0026vcs_url=repository_url=git%2Bhttps:%2F%2Fgithub.com%2Fhexpm%2Fhex.git",
              "product": "hex",
              "programFiles": [
                "lib/hex/http/ssl.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.HTTP.SSL\u0027:ssl_opts/1"
                },
                {
                  "name": "\u0027Elixir.Hex.HTTP.SSL\u0027:partial_chain/2"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "2.5.2",
                  "status": "affected",
                  "version": "0.20.2",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Hex.HTTP.SSL\u0027"
              ],
              "packageName": "hexpm/hex",
              "packageURL": "pkg:github/hexpm/hex",
              "product": "hex",
              "programFiles": [
                "lib/hex/http/ssl.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Hex.HTTP.SSL\u0027:ssl_opts/1"
                },
                {
                  "name": "\u0027Elixir.Hex.HTTP.SSL\u0027:partial_chain/2"
                }
              ],
              "repo": "https://github.com/hexpm/hex",
              "vendor": "hexpm",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "5f5a2136c7cf5991b6e8a30d645ca9f8e74b0785",
                      "status": "unaffected"
                    },
                    {
                      "at": "6232a6498015e559743bec4f4fbec9138837cec8",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "c9b26a46928bfcd546773571e09c1d05f4cd9fb7",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects users who set \u003ccode\u003ecacerts_path\u003c/code\u003e or \u003ccode\u003eHEX_CACERTS_PATH\u003c/code\u003e to a CA store that leaves out some of the CAs bundled with Hex. Users who don\u0027t set it trust the bundled store already and aren\u0027t affected.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects users who set `cacerts_path` or `HEX_CACERTS_PATH` to a CA store that leaves out some of the CAs bundled with Hex. Users who don\u0027t set it trust the bundled store already and aren\u0027t affected."
                }
              ],
              "value": "Affects users who set cacerts_path or HEX_CACERTS_PATH to a CA store that leaves out some of the CAs bundled with Hex. Users who don\u0027t set it trust the bundled store already and aren\u0027t affected."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "2.5.2",
                      "versionStartIncluding": "0.20.2",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-10T12:59:48.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex\u0027s bundled CA store, to impersonate that host to users who set \u003ccode\u003ecacerts_path\u003c/code\u003e to a CA store that leaves that CA out.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ecacerts_path\u003c/code\u003e (\u003ccode\u003eHEX_CACERTS_PATH\u003c/code\u003e) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. \u003ccode\u003eHex.HTTP.SSL.ssl_opts/1\u003c/code\u003e passes the configured store as the \u003ccode\u003ecacerts\u003c/code\u003e option, but builds the \u003ccode\u003epartial_chain\u003c/code\u003e callback from the bundled store whether \u003ccode\u003ecacerts_path\u003c/code\u003e is set or not. \u003ccode\u003eHex.HTTP.SSL.partial_chain/2\u003c/code\u003e accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn\u0027t contain that CA. Hostname verification still applies.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex: from 0.20.2 before 2.5.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex\u0027s bundled CA store, to impersonate that host to users who set `cacerts_path` to a CA store that leaves that CA out.\n\n`cacerts_path` (`HEX_CACERTS_PATH`) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. `Hex.HTTP.SSL.ssl_opts/1` passes the configured store as the `cacerts` option, but builds the `partial_chain` callback from the bundled store whether `cacerts_path` is set or not. `Hex.HTTP.SSL.partial_chain/2` accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn\u0027t contain that CA. Hostname verification still applies.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
                }
              ],
              "value": "Improper Certificate Validation vulnerability in hexpm hex allows an attacker who can intercept HTTPS connections, and holds a certificate for a Hex host issued by a CA in Hex\u0027s bundled CA store, to impersonate that host to users who set cacerts_path to a CA store that leaves that CA out.\n\ncacerts_path (HEX_CACERTS_PATH) is documented as the path to the CA certificate store, used instead of the CA bundle that ships with Hex. Hex.HTTP.SSL.ssl_opts/1 passes the configured store as the cacerts option, but builds the partial_chain callback from the bundled store whether cacerts_path is set or not. Hex.HTTP.SSL.partial_chain/2 accepts a server chain when any certificate in it has the same public key as a bundled CA certificate, so a chain that leads to a bundled CA passes verification although the configured store doesn\u0027t contain that CA. Hostname verification still applies.\n\nThis issue affects hex: from 0.20.2 before 2.5.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-94",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eUsers set \u003ccode\u003ecacerts_path\u003c/code\u003e to limit which CAs can authenticate Hex\u0027s servers, for example to their organization\u0027s internal CA. Every CA in Hex\u0027s bundled store can still authenticate those servers, so a certificate mis-issued by one of those CAs, or obtained through compromised domain validation, lets an attacker on the network path impersonate hex.pm, repo.hex.pm, a self-hosted repository or a mirror. The attacker receives the API keys, OAuth tokens and repository keys Hex sends, and can alter API responses. Registry signatures and package checksums still prevent changes to registry contents and packages.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "Users set `cacerts_path` to limit which CAs can authenticate Hex\u0027s servers, for example to their organization\u0027s internal CA. Every CA in Hex\u0027s bundled store can still authenticate those servers, so a certificate mis-issued by one of those CAs, or obtained through compromised domain validation, lets an attacker on the network path impersonate hex.pm, repo.hex.pm, a self-hosted repository or a mirror. The attacker receives the API keys, OAuth tokens and repository keys Hex sends, and can alter API responses. Registry signatures and package checksums still prevent changes to registry contents and packages."
                    }
                  ],
                  "value": "Users set cacerts_path to limit which CAs can authenticate Hex\u0027s servers, for example to their organization\u0027s internal CA. Every CA in Hex\u0027s bundled store can still authenticate those servers, so a certificate mis-issued by one of those CAs, or obtained through compromised domain validation, lets an attacker on the network path impersonate hex.pm, repo.hex.pm, a self-hosted repository or a mirror. The attacker receives the API keys, OAuth tokens and repository keys Hex sends, and can alter API responses. Registry signatures and package checksums still prevent changes to registry contents and packages."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "HIGH",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 5.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:A/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "HIGH",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-295",
                  "description": "CWE-295 Improper Certificate Validation",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T15:17:45.545Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex/security/advisories/GHSA-rw8q-hg7m-cxgr"
            },
            {
              "name": "EEF CNA record for CVE-2026-103503",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-103503.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-103503",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-103503"
            },
            {
              "name": "Introducing commit c9b26a4 in hexpm/hex",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex/commit/c9b26a46928bfcd546773571e09c1d05f4cd9fb7"
            },
            {
              "name": "Fix commit 5f5a213 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/5f5a2136c7cf5991b6e8a30d645ca9f8e74b0785"
            },
            {
              "name": "Fix commit 6232a64 in hexpm/hex",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex/commit/6232a6498015e559743bec4f4fbec9138837cec8"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade Hex to 2.5.2 or later, for example with \u003ccode\u003emix local.hex\u003c/code\u003e. With \u003ccode\u003ecacerts_path\u003c/code\u003e (\u003ccode\u003eHEX_CACERTS_PATH\u003c/code\u003e) set, Hex then only accepts server certificates that chain to a CA in that file, and it doesn\u0027t trust copies of CA certificates sent by the server.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade Hex to 2.5.2 or later, for example with `mix local.hex`. With `cacerts_path` (`HEX_CACERTS_PATH`) set, Hex then only accepts server certificates that chain to a CA in that file, and it doesn\u0027t trust copies of CA certificates sent by the server."
                }
              ],
              "value": "Upgrade Hex to 2.5.2 or later, for example with mix local.hex. With cacerts_path (HEX_CACERTS_PATH) set, Hex then only accepts server certificates that chain to a CA in that file, and it doesn\u0027t trust copies of CA certificates sent by the server."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "Hex trusts its bundled CA certificates when cacerts_path is set",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eThere\u0027s no Hex configuration that stops the bundled CAs from being trusted.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "There\u0027s no Hex configuration that stops the bundled CAs from being trusted."
                }
              ],
              "value": "There\u0027s no Hex configuration that stops the bundled CAs from being trusted."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eCreate a PEM file that contains only an unrelated test CA certificate and set \u003ccode\u003ecacerts_path\u003c/code\u003e to it.\u003c/li\u003e\n\u003cli\u003eOpen a TLS connection to hex.pm with the options from \u003ccode\u003eHex.HTTP.SSL.ssl_opts(\"https://hex.pm\")\u003c/code\u003e. The handshake succeeds, and the accepted chain leads to ISRG Root X2 cross-signed by ISRG Root X1, neither of which is in the PEM file.\u003c/li\u003e\n\u003cli\u003eRemove the \u003ccode\u003epartial_chain\u003c/code\u003e option and connect again. The handshake fails with \u003ccode\u003eunknown_ca\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Create a PEM file that contains only an unrelated test CA certificate and set `cacerts_path` to it.\n2. Open a TLS connection to hex.pm with the options from `Hex.HTTP.SSL.ssl_opts(\"https://hex.pm\")`. The handshake succeeds, and the accepted chain leads to ISRG Root X2 cross-signed by ISRG Root X1, neither of which is in the PEM file.\n3. Remove the `partial_chain` option and connect again. The handshake fails with `unknown_ca`."
                }
              ],
              "value": "* Create a PEM file that contains only an unrelated test CA certificate and set cacerts_path to it.\n* Open a TLS connection to hex.pm with the options from Hex.HTTP.SSL.ssl_opts(\"https://hex.pm\"). The handshake succeeds, and the accepted chain leads to ISRG Root X2 cross-signed by ISRG Root X1, neither of which is in the PEM file.\n* Remove the partial_chain option and connect again. The handshake fails with unknown_ca."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003eHex.HTTP.SSL.get_ca_certs/0\u003c/code\u003e returns the certificates from \u003ccode\u003ecacerts_path\u003c/code\u003e when it\u0027s set, and \u003ccode\u003essl_opts/1\u003c/code\u003e passes them as \u003ccode\u003ecacerts\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003essl_opts/1\u003c/code\u003e sets \u003ccode\u003epartial_chain\u003c/code\u003e to \u003ccode\u003e\u0026amp;partial_chain(Certs.cacerts(), \u0026amp;1)\u003c/code\u003e, which always uses the bundled store.\u003c/li\u003e\n\u003cli\u003eWhen the server\u0027s chain doesn\u0027t lead to a certificate in \u003ccode\u003ecacerts\u003c/code\u003e, OTP calls \u003ccode\u003epartial_chain\u003c/code\u003e with the chain. \u003ccode\u003epartial_chain/2\u003c/code\u003e returns \u003ccode\u003e{:trusted_ca, der}\u003c/code\u003e for the first certificate whose public key matches a bundled CA certificate, and OTP validates the chain with that certificate as the trust anchor.\u003c/li\u003e\n\u003cli\u003eChain validation and the hostname check from \u003ccode\u003ecustomize_hostname_check/1\u003c/code\u003e still run, so the attacker needs a certificate for the requested host that chains to a bundled CA.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `Hex.HTTP.SSL.get_ca_certs/0` returns the certificates from `cacerts_path` when it\u0027s set, and `ssl_opts/1` passes them as `cacerts`.\n2. `ssl_opts/1` sets `partial_chain` to `\u0026partial_chain(Certs.cacerts(), \u00261)`, which always uses the bundled store.\n3. When the server\u0027s chain doesn\u0027t lead to a certificate in `cacerts`, OTP calls `partial_chain` with the chain. `partial_chain/2` returns `{:trusted_ca, der}` for the first certificate whose public key matches a bundled CA certificate, and OTP validates the chain with that certificate as the trust anchor.\n4. Chain validation and the hostname check from `customize_hostname_check/1` still run, so the attacker needs a certificate for the requested host that chains to a bundled CA."
                }
              ],
              "value": "* Hex.HTTP.SSL.get_ca_certs/0 returns the certificates from cacerts_path when it\u0027s set, and ssl_opts/1 passes them as cacerts.\n* ssl_opts/1 sets partial_chain to \u0026partial_chain(Certs.cacerts(), \u00261), which always uses the bundled store.\n* When the server\u0027s chain doesn\u0027t lead to a certificate in cacerts, OTP calls partial_chain with the chain. partial_chain/2 returns {:trusted_ca, der} for the first certificate whose public key matches a bundled CA certificate, and OTP validates the chain with that certificate as the trust anchor.\n* Chain validation and the hostname check from customize_hostname_check/1 still run, so the attacker needs a certificate for the requested host that chains to a bundled CA."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-103503",
        "datePublished": "2026-10-12T15:17:45.545Z",
        "dateReserved": "2026-10-08T15:45:02.372Z",
        "dateUpdated": "2026-10-16T17:32:45.735Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-103502 (GCVE-0-2026-103502)

    Vulnerability from cvelistv5 – Published: 2026-10-12 13:15 – Updated: 2026-10-16 16:58
    VLAI
    Title
    hex_core's default HTTP adapter doesn't verify TLS certificates on OTP 24 and 25
    Summary
    Improper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core's default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system's CA certificates can't be loaded. hex_core:default_config/0 selects the built-in hex_http_httpc adapter. For an HTTPS request whose adapter config has no ssl option, hex_http_httpc:http_options/2 builds options that verify the server certificate against public_key:cacerts_get/0. When that call raises, the adapter prints a warning and sends the request without ssl options, so httpc uses the defaults of the ssl application, which before OTP 26 don't verify the server certificate. public_key:cacerts_get/0 doesn't exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set ssl options, so they don't verify the server certificate on OTP 25 and earlier whether or not a CA store is found. The fallback was intended and documented, but the documentation didn't make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter "has insecure default settings" and recommended other HTTP clients, and the adapter prints "using default ssl options which are insecure" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry "Use secure SSL defaults for OTP 25+". Neither the README nor the warning says that the server certificate isn't verified, the README doesn't say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren't authenticated. This issue affects hex_core: from 0.1.0 before 0.20.0.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 16:55 UTC
    CWE
    • CWE-295 - Improper Certificate Validation
    • CWE-636 - Not Failing Securely ('Failing Open')
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    hexpm hex_core Affected: 0.1.0 , < 0.20.0 (semver)
        cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*
    Create a notification for this product.
    hexpm hex_core Affected: fbb6d9de472e5edb8d6fb6b32a91cb266137892e , < b7b3a42defb4ed8e20a5c68a4412ce68a95b7938 (git)
        cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-103502",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T16:55:40.731433Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T16:58:36.248Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "hex_http_httpc"
              ],
              "packageName": "hex_core",
              "packageURL": "pkg:hex/hex_core",
              "product": "hex_core",
              "programFiles": [
                "src/hex_http_httpc.erl"
              ],
              "programRoutines": [
                {
                  "name": "hex_http_httpc:request/5"
                },
                {
                  "name": "hex_http_httpc:request_to_file/6"
                }
              ],
              "repo": "https://github.com/hexpm/hex_core",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "0.20.0",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "hex_http_httpc"
              ],
              "packageName": "hexpm/hex_core",
              "packageURL": "pkg:github/hexpm/hex_core",
              "product": "hex_core",
              "programFiles": [
                "src/hex_http_httpc.erl"
              ],
              "programRoutines": [
                {
                  "name": "hex_http_httpc:request/5"
                },
                {
                  "name": "hex_http_httpc:request_to_file/6"
                }
              ],
              "repo": "https://github.com/hexpm/hex_core",
              "vendor": "hexpm",
              "versions": [
                {
                  "lessThan": "b7b3a42defb4ed8e20a5c68a4412ce68a95b7938",
                  "status": "affected",
                  "version": "fbb6d9de472e5edb8d6fb6b32a91cb266137892e",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAffects programs that use the \u003ccode\u003ehex_http_httpc\u003c/code\u003e adapter, the default in \u003ccode\u003ehex_core:default_config/0\u003c/code\u003e, without an \u003ccode\u003essl\u003c/code\u003e option in the adapter\u0027s \u003ccode\u003ehttp_options\u003c/code\u003e, on OTP 24, or on OTP 25 when \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e fails. hex_core versions before 0.11.0 are affected on OTP 25 and earlier whether or not the CA certificates load. On OTP 26 and later the \u003ccode\u003essl\u003c/code\u003e defaults verify the server certificate. Hex and rebar3 vendor hex_core but configure their own HTTP adapters, so they aren\u0027t affected.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Affects programs that use the `hex_http_httpc` adapter, the default in `hex_core:default_config/0`, without an `ssl` option in the adapter\u0027s `http_options`, on OTP 24, or on OTP 25 when `public_key:cacerts_get/0` fails. hex_core versions before 0.11.0 are affected on OTP 25 and earlier whether or not the CA certificates load. On OTP 26 and later the `ssl` defaults verify the server certificate. Hex and rebar3 vendor hex_core but configure their own HTTP adapters, so they aren\u0027t affected."
                }
              ],
              "value": "Affects programs that use the hex_http_httpc adapter, the default in hex_core:default_config/0, without an ssl option in the adapter\u0027s http_options, on OTP 24, or on OTP 25 when public_key:cacerts_get/0 fails. hex_core versions before 0.11.0 are affected on OTP 25 and earlier whether or not the CA certificates load. On OTP 26 and later the ssl defaults verify the server certificate. Hex and rebar3 vendor hex_core but configure their own HTTP adapters, so they aren\u0027t affected."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:hexpm:hex_core:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.20.0",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "analyst",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-10T12:59:18.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core\u0027s default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system\u0027s CA certificates can\u0027t be loaded.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehex_core:default_config/0\u003c/code\u003e selects the built-in \u003ccode\u003ehex_http_httpc\u003c/code\u003e adapter. For an HTTPS request whose adapter config has no \u003ccode\u003essl\u003c/code\u003e option, \u003ccode\u003ehex_http_httpc:http_options/2\u003c/code\u003e builds options that verify the server certificate against \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e. When that call raises, the adapter prints a warning and sends the request without \u003ccode\u003essl\u003c/code\u003e options, so httpc uses the defaults of the \u003ccode\u003essl\u003c/code\u003e application, which before OTP 26 don\u0027t verify the server certificate. \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e doesn\u0027t exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set \u003ccode\u003essl\u003c/code\u003e options, so they don\u0027t verify the server certificate on OTP 25 and earlier whether or not a CA store is found.\u003c/p\u003e\n\u003cp\u003eThe fallback was intended and documented, but the documentation didn\u0027t make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter \"has insecure default settings\" and recommended other HTTP clients, and the adapter prints \"using default ssl options which are insecure\" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry \"Use secure SSL defaults for OTP 25+\". Neither the README nor the warning says that the server certificate isn\u0027t verified, the README doesn\u0027t say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren\u0027t authenticated.\u003c/p\u003e\n\u003cp\u003eThis issue affects hex_core: from 0.1.0 before 0.20.0.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core\u0027s default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system\u0027s CA certificates can\u0027t be loaded.\n\n`hex_core:default_config/0` selects the built-in `hex_http_httpc` adapter. For an HTTPS request whose adapter config has no `ssl` option, `hex_http_httpc:http_options/2` builds options that verify the server certificate against `public_key:cacerts_get/0`. When that call raises, the adapter prints a warning and sends the request without `ssl` options, so httpc uses the defaults of the `ssl` application, which before OTP 26 don\u0027t verify the server certificate. `public_key:cacerts_get/0` doesn\u0027t exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set `ssl` options, so they don\u0027t verify the server certificate on OTP 25 and earlier whether or not a CA store is found.\n\nThe fallback was intended and documented, but the documentation didn\u0027t make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter \"has insecure default settings\" and recommended other HTTP clients, and the adapter prints \"using default ssl options which are insecure\" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry \"Use secure SSL defaults for OTP 25+\". Neither the README nor the warning says that the server certificate isn\u0027t verified, the README doesn\u0027t say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren\u0027t authenticated.\n\nThis issue affects hex_core: from 0.1.0 before 0.20.0."
                }
              ],
              "value": "Improper Certificate Validation vulnerability in hexpm hex_core allows an attacker who can intercept HTTPS connections to impersonate hex.pm, repo.hex.pm or any other repository or API server to programs that use hex_core\u0027s default HTTP adapter on Erlang/OTP 24, or on OTP 25 when the operating system\u0027s CA certificates can\u0027t be loaded.\n\nhex_core:default_config/0 selects the built-in hex_http_httpc adapter. For an HTTPS request whose adapter config has no ssl option, hex_http_httpc:http_options/2 builds options that verify the server certificate against public_key:cacerts_get/0. When that call raises, the adapter prints a warning and sends the request without ssl options, so httpc uses the defaults of the ssl application, which before OTP 26 don\u0027t verify the server certificate. public_key:cacerts_get/0 doesn\u0027t exist on OTP 24, so every HTTPS request falls back there, and on OTP 25 it raises when no CA store is found. hex_core versions before 0.11.0 never set ssl options, so they don\u0027t verify the server certificate on OTP 25 and earlier whether or not a CA store is found.\n\nThe fallback was intended and documented, but the documentation didn\u0027t make clear that it meant no certificate verification. Since hex_core 0.8.2 the README has warned that the built-in httpc adapter \"has insecure default settings\" and recommended other HTTP clients, and the adapter prints \"using default ssl options which are insecure\" on every request that falls back. hex_core 0.11.0 added certificate verification with the changelog entry \"Use secure SSL defaults for OTP 25+\". Neither the README nor the warning says that the server certificate isn\u0027t verified, the README doesn\u0027t say which OTP versions the warning applies to, and on OTP 25 without CA certificates the warning tells users to upgrade to OTP 25 or later, which they already run. This advisory is published so that users who took those notices as general advice know their HTTPS requests weren\u0027t authenticated.\n\nThis issue affects hex_core: from 0.1.0 before 0.20.0."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-94",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn attacker on the network path between a program using hex_core\u0027s default adapter and a Hex repository or API server completes the TLS handshake with any certificate. It receives the API keys, OAuth tokens and repository keys the program sends, and can alter API requests and responses. Registry signatures, which hex_core verifies by default, still prevent changes to registry contents. Hex and rebar3 aren\u0027t affected because they configure their own HTTP adapters.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An attacker on the network path between a program using hex_core\u0027s default adapter and a Hex repository or API server completes the TLS handshake with any certificate. It receives the API keys, OAuth tokens and repository keys the program sends, and can alter API requests and responses. Registry signatures, which hex_core verifies by default, still prevent changes to registry contents. Hex and rebar3 aren\u0027t affected because they configure their own HTTP adapters."
                    }
                  ],
                  "value": "An attacker on the network path between a program using hex_core\u0027s default adapter and a Hex repository or API server completes the TLS handshake with any certificate. It receives the API keys, OAuth tokens and repository keys the program sends, and can alter API requests and responses. Registry signatures, which hex_core verifies by default, still prevent changes to registry contents. Hex and rebar3 aren\u0027t affected because they configure their own HTTP adapters."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 5.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "ACTIVE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "HIGH",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-295",
                  "description": "CWE-295 Improper Certificate Validation",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            },
            {
              "descriptions": [
                {
                  "cweId": "CWE-636",
                  "description": "CWE-636 Not Failing Securely (\u0027Failing Open\u0027)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T13:15:29.040Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/hexpm/hex_core/security/advisories/GHSA-p26c-h8c2-rwqj"
            },
            {
              "name": "EEF CNA record for CVE-2026-103502",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-103502.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-103502",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-103502"
            },
            {
              "name": "Introducing commit fbb6d9d in hexpm/hex_core",
              "tags": [
                "related"
              ],
              "url": "https://github.com/hexpm/hex_core/commit/fbb6d9de472e5edb8d6fb6b32a91cb266137892e"
            },
            {
              "name": "Fix commit b7b3a42 in hexpm/hex_core",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/hexpm/hex_core/commit/b7b3a42defb4ed8e20a5c68a4412ce68a95b7938"
            }
          ],
          "solutions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUpgrade hex_core to 0.20.0 or later. On OTP 24, or on OTP 25 without the operating system\u0027s CA certificates, HTTPS requests through \u003ccode\u003ehex_http_httpc\u003c/code\u003e then fail with \u003ccode\u003e{error, {cacerts_unavailable, Reason}}\u003c/code\u003e unless the adapter config has \u003ccode\u003essl\u003c/code\u003e options.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Upgrade hex_core to 0.20.0 or later. On OTP 24, or on OTP 25 without the operating system\u0027s CA certificates, HTTPS requests through `hex_http_httpc` then fail with `{error, {cacerts_unavailable, Reason}}` unless the adapter config has `ssl` options."
                }
              ],
              "value": "Upgrade hex_core to 0.20.0 or later. On OTP 24, or on OTP 25 without the operating system\u0027s CA certificates, HTTPS requests through hex_http_httpc then fail with {error, {cacerts_unavailable, Reason}} unless the adapter config has ssl options."
            }
          ],
          "source": {
            "discovery": "UNKNOWN"
          },
          "title": "hex_core\u0027s default HTTP adapter doesn\u0027t verify TLS certificates on OTP 24 and 25",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003ePass \u003ccode\u003essl\u003c/code\u003e options that verify the server certificate in the adapter config, for example \u003ccode\u003e{hex_http_httpc, #{http_options =\u0026gt; [{ssl, SslOpts}]}}\u003c/code\u003e, use another HTTP adapter, or run on OTP 26 or later.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Pass `ssl` options that verify the server certificate in the adapter config, for example `{hex_http_httpc, #{http_options =\u003e [{ssl, SslOpts}]}}`, use another HTTP adapter, or run on OTP 26 or later."
                }
              ],
              "value": "Pass ssl options that verify the server certificate in the adapter config, for example {hex_http_httpc, #{http_options =\u003e [{ssl, SslOpts}]}}, use another HTTP adapter, or run on OTP 26 or later."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eCompile \u003ccode\u003esrc/hex_http.erl\u003c/code\u003e and \u003ccode\u003esrc/hex_http_httpc.erl\u003c/code\u003e from hex_core main 4fbc5d1 in the \u003ccode\u003eerlang:24\u003c/code\u003e Docker image and run \u003ccode\u003ehex_http_httpc:request(get, \u0026lt;\u0026lt;\"https://self-signed.badssl.com/\"\u0026gt;\u0026gt;, #{}, undefined, #{profile =\u0026gt; default})\u003c/code\u003e after starting inets and ssl.\u003c/li\u003e\n\u003cli\u003eThe adapter prints \u003ccode\u003e[hex_http_httpc] using default ssl options which are insecure.\u003c/code\u003e and returns \u003ccode\u003e{ok, {200, Headers, Body}}\u003c/code\u003e from the server with the self-signed certificate.\u003c/li\u003e\n\u003cli\u003eIn the \u003ccode\u003eerlang:25\u003c/code\u003e image with an empty tmpfs mounted on \u003ccode\u003e/etc/ssl/certs\u003c/code\u003e, \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e raises and the request also returns \u003ccode\u003e{ok, {200, ...}}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eIn the \u003ccode\u003eerlang:25\u003c/code\u003e image with the CA store present (150 certificates), the same request fails with \u003ccode\u003e{error, {failed_connect, ...}}\u003c/code\u003e after a \u003ccode\u003eBad Certificate\u003c/code\u003e alert.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Compile `src/hex_http.erl` and `src/hex_http_httpc.erl` from hex_core main 4fbc5d1 in the `erlang:24` Docker image and run `hex_http_httpc:request(get, \u003c\u003c\"https://self-signed.badssl.com/\"\u003e\u003e, #{}, undefined, #{profile =\u003e default})` after starting inets and ssl.\n2. The adapter prints `[hex_http_httpc] using default ssl options which are insecure.` and returns `{ok, {200, Headers, Body}}` from the server with the self-signed certificate.\n3. In the `erlang:25` image with an empty tmpfs mounted on `/etc/ssl/certs`, `public_key:cacerts_get/0` raises and the request also returns `{ok, {200, ...}}`.\n4. In the `erlang:25` image with the CA store present (150 certificates), the same request fails with `{error, {failed_connect, ...}}` after a `Bad Certificate` alert."
                }
              ],
              "value": "* Compile src/hex_http.erl and src/hex_http_httpc.erl from hex_core main 4fbc5d1 in the erlang:24 Docker image and run hex_http_httpc:request(get, \u003c\u003c\"https://self-signed.badssl.com/\"\u003e\u003e, #{}, undefined, #{profile =\u003e default}) after starting inets and ssl.\n* The adapter prints [hex_http_httpc] using default ssl options which are insecure. and returns {ok, {200, Headers, Body}} from the server with the self-signed certificate.\n* In the erlang:25 image with an empty tmpfs mounted on /etc/ssl/certs, public_key:cacerts_get/0 raises and the request also returns {ok, {200, ...}}.\n* In the erlang:25 image with the CA store present (150 certificates), the same request fails with {error, {failed_connect, ...}} after a Bad Certificate alert."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003e\u003ccode\u003ehex_core:default_config/0\u003c/code\u003e sets \u003ccode\u003ehttp_adapter\u003c/code\u003e to \u003ccode\u003e{hex_http_httpc, #{profile =\u0026gt; default}}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehex_http_httpc:request/5\u003c/code\u003e and \u003ccode\u003ehex_http_httpc:request_to_file/6\u003c/code\u003e build their httpc options with \u003ccode\u003ehttp_options/2\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eFor an \u003ccode\u003ehttps\u003c/code\u003e URI whose adapter config has no \u003ccode\u003essl\u003c/code\u003e entry in \u003ccode\u003ehttp_options\u003c/code\u003e, \u003ccode\u003ehttp_options/2\u003c/code\u003e adds \u003ccode\u003e{ssl, [{verify, verify_peer}, {cacerts, public_key:cacerts_get()}, ...]}\u003c/code\u003e inside a \u003ccode\u003etry\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eWhen \u003ccode\u003epublic_key:cacerts_get/0\u003c/code\u003e raises (\u003ccode\u003eundef\u003c/code\u003e on OTP 24; an error on OTP 25 when no CA store is found), the \u003ccode\u003ecatch\u003c/code\u003e prints a warning with \u003ccode\u003eio:format/1\u003c/code\u003e and returns the options without \u003ccode\u003essl\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ehttpc then uses the \u003ccode\u003essl\u003c/code\u003e application\u0027s defaults, which are \u003ccode\u003everify_none\u003c/code\u003e before OTP 26, and completes the handshake with any certificate.\u003c/li\u003e\n\u003cli\u003ehex_core before 0.11.0 (e57b4fb) had no step 3: it never passed \u003ccode\u003essl\u003c/code\u003e options, so step 5 applied on every OTP version before 26.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. `hex_core:default_config/0` sets `http_adapter` to `{hex_http_httpc, #{profile =\u003e default}}`.\n2. `hex_http_httpc:request/5` and `hex_http_httpc:request_to_file/6` build their httpc options with `http_options/2`.\n3. For an `https` URI whose adapter config has no `ssl` entry in `http_options`, `http_options/2` adds `{ssl, [{verify, verify_peer}, {cacerts, public_key:cacerts_get()}, ...]}` inside a `try`.\n4. When `public_key:cacerts_get/0` raises (`undef` on OTP 24; an error on OTP 25 when no CA store is found), the `catch` prints a warning with `io:format/1` and returns the options without `ssl`.\n5. httpc then uses the `ssl` application\u0027s defaults, which are `verify_none` before OTP 26, and completes the handshake with any certificate.\n6. hex_core before 0.11.0 (e57b4fb) had no step 3: it never passed `ssl` options, so step 5 applied on every OTP version before 26."
                }
              ],
              "value": "* hex_core:default_config/0 sets http_adapter to {hex_http_httpc, #{profile =\u003e default}}.\n* hex_http_httpc:request/5 and hex_http_httpc:request_to_file/6 build their httpc options with http_options/2.\n* For an https URI whose adapter config has no ssl entry in http_options, http_options/2 adds {ssl, [{verify, verify_peer}, {cacerts, public_key:cacerts_get()}, ...]} inside a try.\n* When public_key:cacerts_get/0 raises (undef on OTP 24; an error on OTP 25 when no CA store is found), the catch prints a warning with io:format/1 and returns the options without ssl.\n* httpc then uses the ssl application\u0027s defaults, which are verify_none before OTP 26, and completes the handshake with any certificate.\n* hex_core before 0.11.0 (e57b4fb) had no step 3: it never passed ssl options, so step 5 applied on every OTP version before 26."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-103502",
        "datePublished": "2026-10-12T13:15:29.040Z",
        "dateReserved": "2026-10-09T16:45:02.344Z",
        "dateUpdated": "2026-10-16T16:58:36.248Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-105283 (GCVE-0-2026-105283)

    Vulnerability from cvelistv5 – Published: 2026-10-12 11:16 – Updated: 2026-10-16 16:53
    VLAI
    Title
    Internal error terms inspected into AshTypescript RPC error responses
    Summary
    Generation of Error Message Containing Sensitive Information vulnerability in ash-project ash_typescript allows an unauthenticated attacker to read internal Elixir terms from an RPC error response by provoking an error the error builder has no dedicated clause for. AshTypescript.Rpc.ErrorBuilder.build_error_response/1 in lib/ash_typescript/rpc/error_builder.ex renders a pipeline error term that matches no specific clause with inspect/1 and places the result in details.error of the client-facing error. Ash exceptions take a separate path in AshTypescript.Rpc.Errors where unhandled cases are redacted behind an errorId, but these fallbacks have no redaction, so whatever the term carries reaches the HTTP or channel response. Two request paths hand it such terms: a struct-typed action argument with an invalid nested value, where cast_map_to_struct/2 in lib/ash_typescript/rpc/input_formatter.ex throws the raw Ash.Type cast error into the builder, and field selection on a generic action that returns a primitive, where format_field_type/1 inspects the full Ash.Info.Manifest.Type struct into vars.returnType. Resource module names in vars.resource are not treated as sensitive and are out of scope. This issue affects ash_typescript: from 0.1.0 before 0.19.1.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-16 16:50 UTC
    CWE
    • CWE-209 - Generation of Error Message Containing Sensitive Information
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    ash-project ash_typescript Affected: 0.1.0 , < 0.19.1 (semver)
        cpe:2.3:a:ash-project:ash_typescript:*:*:*:*:*:*:*:*
    Create a notification for this product.
    ash-project ash_typescript Affected: 8e204ce34e24a158cf22a2dac4d53c44716c9e30 , < 4b3c65203ee78dc992d6d2e6559625c5460a01cd (git)
        cpe:2.3:a:ash-project:ash_typescript:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-105283",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-16T16:50:44.234803Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-16T16:53:18.072Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://github.com/ash-project/ash_typescript/security/advisories/GHSA-7gxc-w225-f7f3"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:ash-project:ash_typescript:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.AshTypescript.Rpc.ErrorBuilder\u0027",
                "\u0027Elixir.AshTypescript.Rpc.InputFormatter\u0027",
                "\u0027Elixir.AshTypescript.Rpc\u0027"
              ],
              "packageName": "ash_typescript",
              "packageURL": "pkg:hex/ash_typescript",
              "product": "ash_typescript",
              "programFiles": [
                "lib/ash_typescript/rpc/error_builder.ex",
                "lib/ash_typescript/rpc/input_formatter.ex",
                "lib/ash_typescript/rpc.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc.ErrorBuilder\u0027:build_error_response/1"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc.ErrorBuilder\u0027:format_field_type/1"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc.InputFormatter\u0027:cast_map_to_struct/2"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc\u0027:run_action/3"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc\u0027:validate_action/3"
                }
              ],
              "repo": "https://github.com/ash-project/ash_typescript",
              "vendor": "ash-project",
              "versions": [
                {
                  "lessThan": "0.19.1",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:ash-project:ash_typescript:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.AshTypescript.Rpc.ErrorBuilder\u0027",
                "\u0027Elixir.AshTypescript.Rpc.InputFormatter\u0027",
                "\u0027Elixir.AshTypescript.Rpc\u0027"
              ],
              "packageName": "ash-project/ash_typescript",
              "packageURL": "pkg:github/ash-project/ash_typescript",
              "product": "ash_typescript",
              "programFiles": [
                "lib/ash_typescript/rpc/error_builder.ex",
                "lib/ash_typescript/rpc/input_formatter.ex",
                "lib/ash_typescript/rpc.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc.ErrorBuilder\u0027:build_error_response/1"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc.ErrorBuilder\u0027:format_field_type/1"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc.InputFormatter\u0027:cast_map_to_struct/2"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc\u0027:run_action/3"
                },
                {
                  "name": "\u0027Elixir.AshTypescript.Rpc\u0027:validate_action/3"
                }
              ],
              "repo": "https://github.com/ash-project/ash_typescript",
              "vendor": "ash-project",
              "versions": [
                {
                  "lessThan": "4b3c65203ee78dc992d6d2e6559625c5460a01cd",
                  "status": "affected",
                  "version": "8e204ce34e24a158cf22a2dac4d53c44716c9e30",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eThe application exposes an AshTypescript RPC endpoint over HTTP or a channel and has an action with a struct-typed argument, or a generic action with a primitive return type that accepts field selection.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "The application exposes an AshTypescript RPC endpoint over HTTP or a channel and has an action with a struct-typed argument, or a generic action with a primitive return type that accepts field selection."
                }
              ],
              "value": "The application exposes an AshTypescript RPC endpoint over HTTP or a channel and has an action with a struct-typed argument, or a generic action with a primitive return type that accepts field selection."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:ash-project:ash_typescript:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.19.1",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Shoukry / Udin"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Peter Shoukry / Udin"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Torkild Gundersen Kjevik / Ash Project"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-12T09:59:25.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eGeneration of Error Message Containing Sensitive Information vulnerability in ash-project ash_typescript allows an unauthenticated attacker to read internal Elixir terms from an RPC error response by provoking an error the error builder has no dedicated clause for.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eAshTypescript.Rpc.ErrorBuilder.build_error_response/1\u003c/code\u003e in \u003ccode\u003elib/ash_typescript/rpc/error_builder.ex\u003c/code\u003e renders a pipeline error term that matches no specific clause with \u003ccode\u003einspect/1\u003c/code\u003e and places the result in \u003ccode\u003edetails.error\u003c/code\u003e of the client-facing error. Ash exceptions take a separate path in \u003ccode\u003eAshTypescript.Rpc.Errors\u003c/code\u003e where unhandled cases are redacted behind an \u003ccode\u003eerrorId\u003c/code\u003e, but these fallbacks have no redaction, so whatever the term carries reaches the HTTP or channel response. Two request paths hand it such terms: a struct-typed action argument with an invalid nested value, where \u003ccode\u003ecast_map_to_struct/2\u003c/code\u003e in \u003ccode\u003elib/ash_typescript/rpc/input_formatter.ex\u003c/code\u003e throws the raw \u003ccode\u003eAsh.Type\u003c/code\u003e cast error into the builder, and field selection on a generic action that returns a primitive, where \u003ccode\u003eformat_field_type/1\u003c/code\u003e inspects the full \u003ccode\u003eAsh.Info.Manifest.Type\u003c/code\u003e struct into \u003ccode\u003evars.returnType\u003c/code\u003e. Resource module names in \u003ccode\u003evars.resource\u003c/code\u003e are not treated as sensitive and are out of scope.\u003c/p\u003e\n\u003cp\u003eThis issue affects ash_typescript: from 0.1.0 before 0.19.1.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Generation of Error Message Containing Sensitive Information vulnerability in ash-project ash_typescript allows an unauthenticated attacker to read internal Elixir terms from an RPC error response by provoking an error the error builder has no dedicated clause for.\n\n`AshTypescript.Rpc.ErrorBuilder.build_error_response/1` in `lib/ash_typescript/rpc/error_builder.ex` renders a pipeline error term that matches no specific clause with `inspect/1` and places the result in `details.error` of the client-facing error. Ash exceptions take a separate path in `AshTypescript.Rpc.Errors` where unhandled cases are redacted behind an `errorId`, but these fallbacks have no redaction, so whatever the term carries reaches the HTTP or channel response. Two request paths hand it such terms: a struct-typed action argument with an invalid nested value, where `cast_map_to_struct/2` in `lib/ash_typescript/rpc/input_formatter.ex` throws the raw `Ash.Type` cast error into the builder, and field selection on a generic action that returns a primitive, where `format_field_type/1` inspects the full `Ash.Info.Manifest.Type` struct into `vars.returnType`. Resource module names in `vars.resource` are not treated as sensitive and are out of scope.\n\nThis issue affects ash_typescript: from 0.1.0 before 0.19.1."
                }
              ],
              "value": "Generation of Error Message Containing Sensitive Information vulnerability in ash-project ash_typescript allows an unauthenticated attacker to read internal Elixir terms from an RPC error response by provoking an error the error builder has no dedicated clause for.\n\nAshTypescript.Rpc.ErrorBuilder.build_error_response/1 in lib/ash_typescript/rpc/error_builder.ex renders a pipeline error term that matches no specific clause with inspect/1 and places the result in details.error of the client-facing error. Ash exceptions take a separate path in AshTypescript.Rpc.Errors where unhandled cases are redacted behind an errorId, but these fallbacks have no redaction, so whatever the term carries reaches the HTTP or channel response. Two request paths hand it such terms: a struct-typed action argument with an invalid nested value, where cast_map_to_struct/2 in lib/ash_typescript/rpc/input_formatter.ex throws the raw Ash.Type cast error into the builder, and field selection on a generic action that returns a primitive, where format_field_type/1 inspects the full Ash.Info.Manifest.Type struct into vars.returnType. Resource module names in vars.resource are not treated as sensitive and are out of scope.\n\nThis issue affects ash_typescript: from 0.1.0 before 0.19.1."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-54",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAny client able to call the RPC endpoint can submit malformed struct arguments or field selections and read internal Elixir terms, including any state the failing pipeline step carried, from the error response.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "Any client able to call the RPC endpoint can submit malformed struct arguments or field selections and read internal Elixir terms, including any state the failing pipeline step carried, from the error response."
                    }
                  ],
                  "value": "Any client able to call the RPC endpoint can submit malformed struct arguments or field selections and read internal Elixir terms, including any state the failing pipeline step carried, from the error response."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6.3,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "LOW",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-209",
                  "description": "CWE-209 Generation of Error Message Containing Sensitive Information",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-12T11:16:30.582Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/ash-project/ash_typescript/security/advisories/GHSA-7gxc-w225-f7f3"
            },
            {
              "name": "EEF CNA record for CVE-2026-105283",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-105283.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-105283",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-105283"
            },
            {
              "name": "Introducing commit 8e204ce in ash-project/ash_typescript",
              "tags": [
                "related"
              ],
              "url": "https://github.com/ash-project/ash_typescript/commit/8e204ce34e24a158cf22a2dac4d53c44716c9e30"
            },
            {
              "name": "Fix commit 4b3c652 in ash-project/ash_typescript",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ash-project/ash_typescript/commit/4b3c65203ee78dc992d6d2e6559625c5460a01cd"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Internal error terms inspected into AshTypescript RPC error responses",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eExpose an AshTypescript RPC action that takes a struct-typed argument, for example an \u003ccode\u003eassignee\u003c/code\u003e argument whose struct has a UUID \u003ccode\u003eid\u003c/code\u003e field.\u003c/li\u003e\n\u003cli\u003eSend the action request with an invalid nested value, such as \u003ccode\u003e{\"assignee\": {\"id\": \"not-a-uuid\", \"name\": \"x\", \"email\": \"e\"}}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRead the response: each pair of the raw \u003ccode\u003eAsh.Type\u003c/code\u003e cast error is returned as its own \u003ccode\u003efield_validation_error\u003c/code\u003e with the inspected term in \u003ccode\u003edetails.error\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eFor the second path, send a \u003ccode\u003efields\u003c/code\u003e list to a generic action that returns a primitive and read the inspected \u003ccode\u003eAsh.Info.Manifest.Type\u003c/code\u003e struct in \u003ccode\u003evars.returnType\u003c/code\u003e of the resulting error.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Expose an AshTypescript RPC action that takes a struct-typed argument, for example an `assignee` argument whose struct has a UUID `id` field.\n2. Send the action request with an invalid nested value, such as `{\"assignee\": {\"id\": \"not-a-uuid\", \"name\": \"x\", \"email\": \"e\"}}`.\n3. Read the response: each pair of the raw `Ash.Type` cast error is returned as its own `field_validation_error` with the inspected term in `details.error`.\n\nFor the second path, send a `fields` list to a generic action that returns a primitive and read the inspected `Ash.Info.Manifest.Type` struct in `vars.returnType` of the resulting error."
                }
              ],
              "value": "* Expose an AshTypescript RPC action that takes a struct-typed argument, for example an assignee argument whose struct has a UUID id field.\n* Send the action request with an invalid nested value, such as {\"assignee\": {\"id\": \"not-a-uuid\", \"name\": \"x\", \"email\": \"e\"}}.\n* Read the response: each pair of the raw Ash.Type cast error is returned as its own field_validation_error with the inspected term in details.error.\n\nFor the second path, send a fields list to a generic action that returns a primitive and read the inspected Ash.Info.Manifest.Type struct in vars.returnType of the resulting error."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Error funnel.\u003c/strong\u003e \u003ccode\u003eAshTypescript.Rpc.run_action/3\u003c/code\u003e runs the request through \u003ccode\u003ePipeline.parse_request/3\u003c/code\u003e, \u003ccode\u003eexecute_ash_action/1\u003c/code\u003e and \u003ccode\u003eprocess_result/2\u003c/code\u003e. Any \u003ccode\u003e{:error, reason}\u003c/code\u003e from these goes to \u003ccode\u003eerror_response/1\u003c/code\u003e, which calls \u003ccode\u003eAshTypescript.Rpc.ErrorBuilder.build_error_response/1\u003c/code\u003e with the raw reason term.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unredacted fallbacks.\u003c/strong\u003e \u003ccode\u003ebuild_error_response/1\u003c/code\u003e has clauses for the error tuples the pipeline is expected to emit. A two-tuple whose first element is an atom but matches no clause is rendered as \u003ccode\u003efield_validation_error\u003c/code\u003e with \u003ccode\u003edetails.error\u003c/code\u003e set to \u003ccode\u003einspect(error)\u003c/code\u003e, and any other term becomes \u003ccode\u003eunknown_error\u003c/code\u003e with \u003ccode\u003edetails.error\u003c/code\u003e set to \u003ccode\u003einspect(other)\u003c/code\u003e. Ash exceptions never reach these clauses because \u003ccode\u003eAshTypescript.Rpc.Errors\u003c/code\u003e handles them and redacts unhandled ones behind an \u003ccode\u003eerrorId\u003c/code\u003e, so only this path has no redaction.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Reachable triggers.\u003c/strong\u003e \u003ccode\u003eAshTypescript.Rpc.InputFormatter.cast_map_to_struct/2\u003c/code\u003e throws the raw \u003ccode\u003eAsh.Type.cast_input/3\u003c/code\u003e error, a keyword list, when a struct-typed argument fails to cast, and \u003ccode\u003eformat/6\u003c/code\u003e turns the throw into \u003ccode\u003e{:error, term}\u003c/code\u003e for the funnel. Separately, requesting \u003ccode\u003efields\u003c/code\u003e on a generic action whose return type is a primitive produces an \u003ccode\u003e{:invalid_field_selection, :primitive_type, return_type, ...}\u003c/code\u003e tuple whose \u003ccode\u003ereturn_type\u003c/code\u003e is formatted by \u003ccode\u003eformat_field_type/1\u003c/code\u003e, which inspects the whole \u003ccode\u003eAsh.Info.Manifest.Type\u003c/code\u003e struct, constraints and decoration included, into \u003ccode\u003evars.returnType\u003c/code\u003e.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Error funnel.** `AshTypescript.Rpc.run_action/3` runs the request through `Pipeline.parse_request/3`, `execute_ash_action/1` and `process_result/2`. Any `{:error, reason}` from these goes to `error_response/1`, which calls `AshTypescript.Rpc.ErrorBuilder.build_error_response/1` with the raw reason term.\n\n**2. Unredacted fallbacks.** `build_error_response/1` has clauses for the error tuples the pipeline is expected to emit. A two-tuple whose first element is an atom but matches no clause is rendered as `field_validation_error` with `details.error` set to `inspect(error)`, and any other term becomes `unknown_error` with `details.error` set to `inspect(other)`. Ash exceptions never reach these clauses because `AshTypescript.Rpc.Errors` handles them and redacts unhandled ones behind an `errorId`, so only this path has no redaction.\n\n**3. Reachable triggers.** `AshTypescript.Rpc.InputFormatter.cast_map_to_struct/2` throws the raw `Ash.Type.cast_input/3` error, a keyword list, when a struct-typed argument fails to cast, and `format/6` turns the throw into `{:error, term}` for the funnel. Separately, requesting `fields` on a generic action whose return type is a primitive produces an `{:invalid_field_selection, :primitive_type, return_type, ...}` tuple whose `return_type` is formatted by `format_field_type/1`, which inspects the whole `Ash.Info.Manifest.Type` struct, constraints and decoration included, into `vars.returnType`."
                }
              ],
              "value": "1. Error funnel. AshTypescript.Rpc.run_action/3 runs the request through Pipeline.parse_request/3, execute_ash_action/1 and process_result/2. Any {:error, reason} from these goes to error_response/1, which calls AshTypescript.Rpc.ErrorBuilder.build_error_response/1 with the raw reason term.\n\n2. Unredacted fallbacks. build_error_response/1 has clauses for the error tuples the pipeline is expected to emit. A two-tuple whose first element is an atom but matches no clause is rendered as field_validation_error with details.error set to inspect(error), and any other term becomes unknown_error with details.error set to inspect(other). Ash exceptions never reach these clauses because AshTypescript.Rpc.Errors handles them and redacts unhandled ones behind an errorId, so only this path has no redaction.\n\n3. Reachable triggers. AshTypescript.Rpc.InputFormatter.cast_map_to_struct/2 throws the raw Ash.Type.cast_input/3 error, a keyword list, when a struct-typed argument fails to cast, and format/6 turns the throw into {:error, term} for the funnel. Separately, requesting fields on a generic action whose return type is a primitive produces an {:invalid_field_selection, :primitive_type, return_type, ...} tuple whose return_type is formatted by format_field_type/1, which inspects the whole Ash.Info.Manifest.Type struct, constraints and decoration included, into vars.returnType."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-105283",
        "datePublished": "2026-10-12T11:16:30.582Z",
        "dateReserved": "2026-10-10T13:15:02.310Z",
        "dateUpdated": "2026-10-16T16:53:18.072Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-97853 (GCVE-0-2026-97853)

    Vulnerability from cvelistv5 – Published: 2026-10-10 19:43 – Updated: 2026-10-15 18:09
    VLAI
    Title
    Unbounded allocation in decimal Decimal.round/3 driven by the places argument enables DoS
    Summary
    Memory Allocation with Excessive Size Value vulnerability in ericmj decimal allows Denial of Service. Decimal.round/3 builds the full result for the requested number of decimal places before the context precision (34 digits by default) is applied, so its cost grows with the places argument instead of with the size of the result. For positive places it appends places zero digits to the coefficient as a charlist before converting it to an integer, and for negative places it builds a charlist of -places zero digits. A single call such as Decimal.round(Decimal.new("1.5"), -50_000_000) allocates about 5.5 GB of memory, which can exhaust available memory and get the BEAM VM killed. The oldest releases instead loop once per decimal place, consuming CPU in proportion to places. Any application that passes a user-supplied number of decimal places or scale to Decimal.round/2 or Decimal.round/3 without bounding it is exposed. The input limits added for CVE-2026-32686 do not cover the places argument. This issue affects decimal: from 0.1.0 before 3.1.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-15 17:51 UTC
    CWE
    • CWE-789 - Memory Allocation with Excessive Size Value
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    ericmj decimal Affected: 0.1.0 , < 3.1.2 (semver)
        cpe:2.3:a:ericmj:decimal:*:*:*:*:*:*:*:*
    Create a notification for this product.
    ericmj decimal Affected: 05bb73eb40ddef24eda782d905766f56a3660522 , < * (git)
        cpe:2.3:a:ericmj:decimal:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-10-10 00:00
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-97853",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-15T17:51:21.251991Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-15T18:09:50.720Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:ericmj:decimal:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Decimal\u0027"
              ],
              "packageName": "decimal",
              "packageURL": "pkg:hex/decimal",
              "product": "decimal",
              "programFiles": [
                "lib/decimal.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Decimal\u0027:round/2"
                },
                {
                  "name": "\u0027Elixir.Decimal\u0027:round/3"
                }
              ],
              "repo": "https://github.com/ericmj/decimal",
              "vendor": "ericmj",
              "versions": [
                {
                  "lessThan": "3.1.2",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:ericmj:decimal:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Decimal\u0027"
              ],
              "packageName": "ericmj/decimal",
              "packageURL": "pkg:github/ericmj/decimal",
              "product": "decimal",
              "programFiles": [
                "lib/decimal.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Decimal\u0027:round/2"
                },
                {
                  "name": "\u0027Elixir.Decimal\u0027:round/3"
                }
              ],
              "repo": "https://github.com/ericmj/decimal",
              "vendor": "ericmj",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "338f42c8cf6a9749ab70c5af04734c6050ca3dc6",
                      "status": "unaffected"
                    },
                    {
                      "at": "3c90af4c3c2dfa4bb4c138760a326dd0eb91822b",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "05bb73eb40ddef24eda782d905766f56a3660522",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:ericmj:decimal:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "3.1.2",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Eric Meadows-J\u00f6nsson"
            }
          ],
          "dateAssigned": "2026-10-09T16:40:13.000Z",
          "datePublic": "2026-10-10T00:00:00.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eMemory Allocation with Excessive Size Value vulnerability in ericmj decimal allows Denial of Service.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eDecimal.round/3\u003c/code\u003e builds the full result for the requested number of decimal places before the context precision (34 digits by default) is applied, so its cost grows with the \u003ccode\u003eplaces\u003c/code\u003e argument instead of with the size of the result. For positive \u003ccode\u003eplaces\u003c/code\u003e it appends \u003ccode\u003eplaces\u003c/code\u003e zero digits to the coefficient as a charlist before converting it to an integer, and for negative \u003ccode\u003eplaces\u003c/code\u003e it builds a charlist of \u003ccode\u003e-places\u003c/code\u003e zero digits. A single call such as \u003ccode\u003eDecimal.round(Decimal.new(\"1.5\"), -50_000_000)\u003c/code\u003e allocates about 5.5 GB of memory, which can exhaust available memory and get the BEAM VM killed. The oldest releases instead loop once per decimal place, consuming CPU in proportion to \u003ccode\u003eplaces\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eAny application that passes a user-supplied number of decimal places or scale to \u003ccode\u003eDecimal.round/2\u003c/code\u003e or \u003ccode\u003eDecimal.round/3\u003c/code\u003e without bounding it is exposed. The input limits added for CVE-2026-32686 do not cover the \u003ccode\u003eplaces\u003c/code\u003e argument.\u003c/p\u003e\n\u003cp\u003eThis issue affects decimal: from 0.1.0 before 3.1.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Memory Allocation with Excessive Size Value vulnerability in ericmj decimal allows Denial of Service.\n\n`Decimal.round/3` builds the full result for the requested number of decimal places before the context precision (34 digits by default) is applied, so its cost grows with the `places` argument instead of with the size of the result. For positive `places` it appends `places` zero digits to the coefficient as a charlist before converting it to an integer, and for negative `places` it builds a charlist of `-places` zero digits. A single call such as `Decimal.round(Decimal.new(\"1.5\"), -50_000_000)` allocates about 5.5 GB of memory, which can exhaust available memory and get the BEAM VM killed. The oldest releases instead loop once per decimal place, consuming CPU in proportion to `places`.\n\nAny application that passes a user-supplied number of decimal places or scale to `Decimal.round/2` or `Decimal.round/3` without bounding it is exposed. The input limits added for CVE-2026-32686 do not cover the `places` argument.\n\nThis issue affects decimal: from 0.1.0 before 3.1.2."
                }
              ],
              "value": "Memory Allocation with Excessive Size Value vulnerability in ericmj decimal allows Denial of Service.\n\nDecimal.round/3 builds the full result for the requested number of decimal places before the context precision (34 digits by default) is applied, so its cost grows with the places argument instead of with the size of the result. For positive places it appends places zero digits to the coefficient as a charlist before converting it to an integer, and for negative places it builds a charlist of -places zero digits. A single call such as Decimal.round(Decimal.new(\"1.5\"), -50_000_000) allocates about 5.5 GB of memory, which can exhaust available memory and get the BEAM VM killed. The oldest releases instead loop once per decimal place, consuming CPU in proportion to places.\n\nAny application that passes a user-supplied number of decimal places or scale to Decimal.round/2 or Decimal.round/3 without bounding it is exposed. The input limits added for CVE-2026-32686 do not cover the places argument.\n\nThis issue affects decimal: from 0.1.0 before 3.1.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn attacker who controls the number of decimal places an application rounds to makes one \u003ccode\u003eDecimal.round/2,3\u003c/code\u003e call allocate memory in proportion to that number. A value of 50 million needs about 5.5 GB, enough to get the BEAM VM killed on hosts with less memory and take down every request it serves.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An attacker who controls the number of decimal places an application rounds to makes one `Decimal.round/2,3` call allocate memory in proportion to that number. A value of 50 million needs about 5.5 GB, enough to get the BEAM VM killed on hosts with less memory and take down every request it serves."
                    }
                  ],
                  "value": "An attacker who controls the number of decimal places an application rounds to makes one Decimal.round/2,3 call allocate memory in proportion to that number. A value of 50 million needs about 5.5 GB, enough to get the BEAM VM killed on hosts with less memory and take down every request it serves."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "NONE",
                "attackVector": "LOCAL",
                "baseScore": 6.9,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-789",
                  "description": "CWE-789 Memory Allocation with Excessive Size Value",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-10T19:43:45.217Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GHSA-6c27-994x-c52f",
              "tags": [
                "vendor-advisory"
              ],
              "url": "https://github.com/ericmj/decimal/security/advisories/GHSA-6c27-994x-c52f"
            },
            {
              "name": "EEF CNA record for CVE-2026-97853",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-97853.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-97853",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-97853"
            },
            {
              "name": "Introducing commit 05bb73e in ericmj/decimal",
              "tags": [
                "related"
              ],
              "url": "https://github.com/ericmj/decimal/commit/05bb73eb40ddef24eda782d905766f56a3660522"
            },
            {
              "name": "Fix commit 338f42c in ericmj/decimal",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ericmj/decimal/commit/338f42c8cf6a9749ab70c5af04734c6050ca3dc6"
            },
            {
              "name": "Fix commit 3c90af4 in ericmj/decimal",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ericmj/decimal/commit/3c90af4c3c2dfa4bb4c138760a326dd0eb91822b"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Unbounded allocation in decimal Decimal.round/3 driven by the places argument enables DoS",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eBound \u003ccode\u003eplaces\u003c/code\u003e before calling \u003ccode\u003eDecimal.round/2,3\u003c/code\u003e, for example to \u003ccode\u003e-34..34\u003c/code\u003e or to the scales the application supports.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Bound `places` before calling `Decimal.round/2,3`, for example to `-34..34` or to the scales the application supports."
                }
              ],
              "value": "Bound places before calling Decimal.round/2,3, for example to -34..34 or to the scales the application supports."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cpre\u003e\u003ccode class=\"language-elixir\"\u003eMix.install([{:decimal, \"3.1.1\"}])\n\n# Allocates about 5.5 GB.\nDecimal.round(Decimal.new(\"1.5\"), -50_000_000)\n\n# Builds a 50 million element list, then raises SystemLimitError.\nDecimal.round(Decimal.new(\"1.5\"), 50_000_000)\n\u003c/code\u003e\u003c/pre\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "```elixir\nMix.install([{:decimal, \"3.1.1\"}])\n\n# Allocates about 5.5 GB.\nDecimal.round(Decimal.new(\"1.5\"), -50_000_000)\n\n# Builds a 50 million element list, then raises SystemLimitError.\nDecimal.round(Decimal.new(\"1.5\"), 50_000_000)\n```"
                }
              ],
              "value": "Mix.install([{:decimal, \"3.1.1\"}])\n\n# Allocates about 5.5 GB.\nDecimal.round(Decimal.new(\"1.5\"), -50_000_000)\n\n# Builds a 50 million element list, then raises SystemLimitError.\nDecimal.round(Decimal.new(\"1.5\"), 50_000_000)"
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003ccode\u003eDecimal.round/3\u003c/code\u003e sets the target exponent to \u003ccode\u003e-places\u003c/code\u003e and \u003ccode\u003edo_round/5\u003c/code\u003e materializes the coefficient at that exponent; \u003ccode\u003econtext/2\u003c/code\u003e only applies the precision afterwards.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePositive \u003ccode\u003eplaces\u003c/code\u003e (introduced in 1.3.0 by commit 7a83d27): \u003ccode\u003edigits ++ Enum.map(1..(exp - target_exp), fn _ -\u0026gt; ?0 end)\u003c/code\u003e followed by \u003ccode\u003e:erlang.list_to_integer/1\u003c/code\u003e (\u003ccode\u003elib/decimal.ex:2409\u003c/code\u003e in 3.1.1). The list is built before \u003ccode\u003elist_to_integer/1\u003c/code\u003e raises \u003ccode\u003eSystemLimitError\u003c/code\u003e for results over about 1.26 million digits, the BEAM integer size limit.\u003c/li\u003e\n\u003cli\u003eNegative \u003ccode\u003eplaces\u003c/code\u003e (since 1.4.0): \u003ccode\u003e:lists.duplicate(target_exp - exp, ?0) ++ digits\u003c/code\u003e (\u003ccode\u003elib/decimal.ex:2385-2386\u003c/code\u003e in 3.1.1).\u003c/li\u003e\n\u003cli\u003eReleases from 0.1.0 before 1.1.0: \u003ccode\u003edo_round\u003c/code\u003e (and in 1.0.1 \u003ccode\u003esplit_coef\u003c/code\u003e) recurses once per decimal place for negative \u003ccode\u003eplaces\u003c/code\u003e; 1.0.1 also multiplies a power of ten by 10 on every iteration. Established by reading the code; these releases do not compile on current Elixir.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMeasured on OTP 29 with 3.1.1, one call per fresh VM:\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eCall\u003c/th\u003e\n\u003cth\u003eTime\u003c/th\u003e\n\u003cth\u003ePeak VM memory\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eDecimal.round(Decimal.new(\"1.5\"), -10_000_000)\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e0.25 s\u003c/td\u003e\n\u003ctd\u003e0.8 GB\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eDecimal.round(Decimal.new(\"1.5\"), -50_000_000)\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e1.2 s\u003c/td\u003e\n\u003ctd\u003e5.5 GB\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eDecimal.round(Decimal.new(\"1.5\"), 50_000_000)\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e2.1 s, then \u003ccode\u003eSystemLimitError\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e2.4 GB\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eIn an \u003ccode\u003eelixir:1.20.4\u003c/code\u003e container started with \u003ccode\u003e--memory=2g\u003c/code\u003e, either of the last two calls got the VM killed (exit status 137). Over HTTP (Plug and Bandit) a 34-byte JSON body carrying \u003ccode\u003eplaces: -50_000_000\u003c/code\u003e returned after 1.25 s with server memory at 4.3 GB.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "`Decimal.round/3` sets the target exponent to `-places` and `do_round/5` materializes the coefficient at that exponent; `context/2` only applies the precision afterwards.\n\n- Positive `places` (introduced in 1.3.0 by commit 7a83d27): `digits ++ Enum.map(1..(exp - target_exp), fn _ -\u003e ?0 end)` followed by `:erlang.list_to_integer/1` (`lib/decimal.ex:2409` in 3.1.1). The list is built before `list_to_integer/1` raises `SystemLimitError` for results over about 1.26 million digits, the BEAM integer size limit.\n- Negative `places` (since 1.4.0): `:lists.duplicate(target_exp - exp, ?0) ++ digits` (`lib/decimal.ex:2385-2386` in 3.1.1).\n- Releases from 0.1.0 before 1.1.0: `do_round` (and in 1.0.1 `split_coef`) recurses once per decimal place for negative `places`; 1.0.1 also multiplies a power of ten by 10 on every iteration. Established by reading the code; these releases do not compile on current Elixir.\n\nMeasured on OTP 29 with 3.1.1, one call per fresh VM:\n\n| Call | Time | Peak VM memory |\n|---|---|---|\n| `Decimal.round(Decimal.new(\"1.5\"), -10_000_000)` | 0.25 s | 0.8 GB |\n| `Decimal.round(Decimal.new(\"1.5\"), -50_000_000)` | 1.2 s | 5.5 GB |\n| `Decimal.round(Decimal.new(\"1.5\"), 50_000_000)` | 2.1 s, then `SystemLimitError` | 2.4 GB |\n\nIn an `elixir:1.20.4` container started with `--memory=2g`, either of the last two calls got the VM killed (exit status 137). Over HTTP (Plug and Bandit) a 34-byte JSON body carrying `places: -50_000_000` returned after 1.25 s with server memory at 4.3 GB."
                }
              ],
              "value": "Decimal.round/3 sets the target exponent to -places and do_round/5 materializes the coefficient at that exponent; context/2 only applies the precision afterwards.\n\n* Positive places (introduced in 1.3.0 by commit 7a83d27): digits ++ Enum.map(1..(exp - target_exp), fn _ -\u003e ?0 end) followed by :erlang.list_to_integer/1 (lib/decimal.ex:2409 in 3.1.1). The list is built before list_to_integer/1 raises SystemLimitError for results over about 1.26 million digits, the BEAM integer size limit.\n* Negative places (since 1.4.0): :lists.duplicate(target_exp - exp, ?0) ++ digits (lib/decimal.ex:2385-2386 in 3.1.1).\n* Releases from 0.1.0 before 1.1.0: do_round (and in 1.0.1 split_coef) recurses once per decimal place for negative places; 1.0.1 also multiplies a power of ten by 10 on every iteration. Established by reading the code; these releases do not compile on current Elixir.\n\nMeasured on OTP 29 with 3.1.1, one call per fresh VM:\n\nCallTimePeak VM memoryDecimal.round(Decimal.new(\"1.5\"), -10_000_000)0.25 s0.8 GBDecimal.round(Decimal.new(\"1.5\"), -50_000_000)1.2 s5.5 GBDecimal.round(Decimal.new(\"1.5\"), 50_000_000)2.1 s, then SystemLimitError2.4 GB\n\nIn an elixir:1.20.4 container started with --memory=2g, either of the last two calls got the VM killed (exit status 137). Over HTTP (Plug and Bandit) a 34-byte JSON body carrying places: -50_000_000 returned after 1.25 s with server memory at 4.3 GB."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-97853",
        "datePublished": "2026-10-10T19:43:45.217Z",
        "dateReserved": "2026-10-09T00:30:01.645Z",
        "dateUpdated": "2026-10-15T18:09:50.720Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-104635 (GCVE-0-2026-104635)

    Vulnerability from cvelistv5 – Published: 2026-10-09 08:17 – Updated: 2026-10-15 15:57
    VLAI
    Title
    Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages
    Summary
    Uncontrolled Recursion vulnerability in Protobuf.JSON.Decode in elixir-protobuf protobuf allows an unauthenticated remote attacker to crash the decoding process via a deeply nested JSON document. Any application that decodes attacker-supplied JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a schema that contains a self-referential or cyclic message type is affected. In lib/protobuf/json/decode.ex, the embedded-message clause of decode_singular/3 recurses into internal_from_json_data/3 once per nesting level without incrementing or checking the decoder's depth counter. The depth guard increase_depth_and_maybe_throw/1 covers only the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, so the recursion_limit option has no effect on user-defined message types. Each nesting level allocates a stack frame and heap objects, and a sufficiently deep document exhausts the memory of the decoding process. Confidentiality and integrity are not affected. This issue affects protobuf: from 0.8.0 before 0.17.1.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-15 15:56 UTC
    CWE
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    elixir-protobuf protobuf Affected: 0.8.0 , < 0.17.1 (semver)
        cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*
    Create a notification for this product.
    elixir-protobuf protobuf Affected: b0a1d4eaffaf50012fa71a8e931a47cf252d0370 , < e9432ad1c4099511905353cebcececa3a1f7c3ff (git)
        cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-10-09 08:17
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-104635",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-15T15:56:39.227631Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-15T15:57:11.974Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://osv.dev/vulnerability/EEF-CVE-2026-104635"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Protobuf.JSON.Decode\u0027",
                "\u0027Elixir.Protobuf.JSON\u0027"
              ],
              "packageName": "protobuf",
              "packageURL": "pkg:hex/protobuf",
              "product": "protobuf",
              "programFiles": [
                "lib/protobuf/json/decode.ex",
                "lib/protobuf/json.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:from_json_data/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:decode_singular/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON\u0027:decode/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON\u0027:decode!/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON\u0027:from_decoded/3"
                }
              ],
              "repo": "https://github.com/elixir-protobuf/protobuf",
              "vendor": "elixir-protobuf",
              "versions": [
                {
                  "lessThan": "0.17.1",
                  "status": "affected",
                  "version": "0.8.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Protobuf.JSON.Decode\u0027",
                "\u0027Elixir.Protobuf.JSON\u0027"
              ],
              "packageName": "elixir-protobuf/protobuf",
              "packageURL": "pkg:github/elixir-protobuf/protobuf",
              "product": "protobuf",
              "programFiles": [
                "lib/protobuf/json/decode.ex",
                "lib/protobuf/json.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:from_json_data/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:decode_singular/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON\u0027:decode/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON\u0027:decode!/3"
                },
                {
                  "name": "\u0027Elixir.Protobuf.JSON\u0027:from_decoded/3"
                }
              ],
              "repo": "https://github.com/elixir-protobuf/protobuf",
              "vendor": "elixir-protobuf",
              "versions": [
                {
                  "lessThan": "e9432ad1c4099511905353cebcececa3a1f7c3ff",
                  "status": "affected",
                  "version": "b0a1d4eaffaf50012fa71a8e931a47cf252d0370",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eThe application decodes attacker-controlled JSON with \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e into a message type whose schema contains a self-referential or cyclic embedded message.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "The application decodes attacker-controlled JSON with `Protobuf.JSON.decode/3`, `Protobuf.JSON.decode!/3`, or `Protobuf.JSON.from_decoded/3` into a message type whose schema contains a self-referential or cyclic embedded message."
                }
              ],
              "value": "The application decodes attacker-controlled JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a message type whose schema contains a self-referential or cyclic embedded message."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.17.1",
                      "versionStartIncluding": "0.8.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "reporter",
              "value": "Daniel Coles"
            },
            {
              "lang": "en",
              "type": "finder",
              "value": "Daniel Coles"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Andrea Leopardi"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-08T15:40:42.000Z",
          "datePublic": "2026-10-09T08:17:52.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUncontrolled Recursion vulnerability in \u003ccode\u003eProtobuf.JSON.Decode\u003c/code\u003e in elixir-protobuf protobuf allows an unauthenticated remote attacker to crash the decoding process via a deeply nested JSON document. Any application that decodes attacker-supplied JSON with \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e into a schema that contains a self-referential or cyclic message type is affected.\u003c/p\u003e\n\u003cp\u003eIn \u003ccode\u003elib/protobuf/json/decode.ex\u003c/code\u003e, the embedded-message clause of \u003ccode\u003edecode_singular/3\u003c/code\u003e recurses into \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e once per nesting level without incrementing or checking the decoder\u0027s depth counter. The depth guard \u003ccode\u003eincrease_depth_and_maybe_throw/1\u003c/code\u003e covers only the \u003ccode\u003eGoogle.Protobuf.ListValue\u003c/code\u003e and \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e clauses, so the \u003ccode\u003erecursion_limit\u003c/code\u003e option has no effect on user-defined message types. Each nesting level allocates a stack frame and heap objects, and a sufficiently deep document exhausts the memory of the decoding process. Confidentiality and integrity are not affected.\u003c/p\u003e\n\u003cp\u003eThis issue affects protobuf: from 0.8.0 before 0.17.1.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Uncontrolled Recursion vulnerability in `Protobuf.JSON.Decode` in elixir-protobuf protobuf allows an unauthenticated remote attacker to crash the decoding process via a deeply nested JSON document. Any application that decodes attacker-supplied JSON with `Protobuf.JSON.decode/3`, `Protobuf.JSON.decode!/3`, or `Protobuf.JSON.from_decoded/3` into a schema that contains a self-referential or cyclic message type is affected.\n\nIn `lib/protobuf/json/decode.ex`, the embedded-message clause of `decode_singular/3` recurses into `internal_from_json_data/3` once per nesting level without incrementing or checking the decoder\u0027s depth counter. The depth guard `increase_depth_and_maybe_throw/1` covers only the `Google.Protobuf.ListValue` and `Google.Protobuf.Struct` clauses, so the `recursion_limit` option has no effect on user-defined message types. Each nesting level allocates a stack frame and heap objects, and a sufficiently deep document exhausts the memory of the decoding process. Confidentiality and integrity are not affected.\n\nThis issue affects protobuf: from 0.8.0 before 0.17.1."
                }
              ],
              "value": "Uncontrolled Recursion vulnerability in Protobuf.JSON.Decode in elixir-protobuf protobuf allows an unauthenticated remote attacker to crash the decoding process via a deeply nested JSON document. Any application that decodes attacker-supplied JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a schema that contains a self-referential or cyclic message type is affected.\n\nIn lib/protobuf/json/decode.ex, the embedded-message clause of decode_singular/3 recurses into internal_from_json_data/3 once per nesting level without incrementing or checking the decoder\u0027s depth counter. The depth guard increase_depth_and_maybe_throw/1 covers only the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, so the recursion_limit option has no effect on user-defined message types. Each nesting level allocates a stack frame and heap objects, and a sufficiently deep document exhausts the memory of the decoding process. Confidentiality and integrity are not affected.\n\nThis issue affects protobuf: from 0.8.0 before 0.17.1."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-230",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn unauthenticated client that can reach an endpoint decoding JSON into a self-referential message type can crash the decoding process through stack and memory exhaustion with a single request. Repeated or concurrent requests can exhaust the memory of the whole node and disrupt co-located workloads.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An unauthenticated client that can reach an endpoint decoding JSON into a self-referential message type can crash the decoding process through stack and memory exhaustion with a single request. Repeated or concurrent requests can exhaust the memory of the whole node and disrupt co-located workloads."
                    }
                  ],
                  "value": "An unauthenticated client that can reach an endpoint decoding JSON into a self-referential message type can crash the decoding process through stack and memory exhaustion with a single request. Repeated or concurrent requests can exhaust the memory of the whole node and disrupt co-located workloads."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 8.2,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-674",
                  "description": "CWE-674 Uncontrolled Recursion",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-09T09:24:37.025Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-m497-c2h9-rvw6"
            },
            {
              "name": "EEF CNA record for CVE-2026-104635",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-104635.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-104635",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-104635"
            },
            {
              "name": "Introducing commit b0a1d4e in elixir-protobuf/protobuf",
              "tags": [
                "related"
              ],
              "url": "https://github.com/elixir-protobuf/protobuf/commit/b0a1d4eaffaf50012fa71a8e931a47cf252d0370"
            },
            {
              "name": "Fix commit e9432ad in elixir-protobuf/protobuf",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-protobuf/protobuf/commit/e9432ad1c4099511905353cebcececa3a1f7c3ff"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReject JSON documents whose nesting depth exceeds a fixed bound at the web or middleware layer before passing them to the decoder. No decoder option limits recursion for user-defined message types, so the check must happen before \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e is called.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reject JSON documents whose nesting depth exceeds a fixed bound at the web or middleware layer before passing them to the decoder. No decoder option limits recursion for user-defined message types, so the check must happen before `Protobuf.JSON.decode/3`, `Protobuf.JSON.decode!/3`, or `Protobuf.JSON.from_decoded/3` is called."
                }
              ],
              "value": "Reject JSON documents whose nesting depth exceeds a fixed bound at the web or middleware layer before passing them to the decoder. No decoder option limits recursion for user-defined message types, so the check must happen before Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 is called."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eDefine and compile a self-referential proto3 message, for example a \u003ccode\u003eTreeNode\u003c/code\u003e message with a single embedded field \u003ccode\u003echild\u003c/code\u003e of type \u003ccode\u003eTreeNode\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eBuild a JSON document that nests the \u003ccode\u003echild\u003c/code\u003e field several thousand levels deep, for example by wrapping \u003ccode\u003e{}\u003c/code\u003e in \u003ccode\u003e{\"child\": ...}\u003c/code\u003e 5,000 times.\u003c/li\u003e\n\u003cli\u003eDecode it with \u003ccode\u003eProtobuf.JSON.decode(json, TreeNode, recursion_limit: 100)\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eObserve that no \u003ccode\u003eProtobuf.JSON.DecodeError\u003c/code\u003e for an exceeded recursion limit is raised and the decoder walks every level. The same depth in a \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e payload raises \u003ccode\u003e{:recursion_limit_exceeded, 100}\u003c/code\u003e. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Define and compile a self-referential proto3 message, for example a `TreeNode` message with a single embedded field `child` of type `TreeNode`.\n2. Build a JSON document that nests the `child` field several thousand levels deep, for example by wrapping `{}` in `{\"child\": ...}` 5,000 times.\n3. Decode it with `Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100)`.\n4. Observe that no `Protobuf.JSON.DecodeError` for an exceeded recursion limit is raised and the decoder walks every level. The same depth in a `Google.Protobuf.Struct` payload raises `{:recursion_limit_exceeded, 100}`. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion."
                }
              ],
              "value": "* Define and compile a self-referential proto3 message, for example a TreeNode message with a single embedded field child of type TreeNode.\n* Build a JSON document that nests the child field several thousand levels deep, for example by wrapping {} in {\"child\": ...} 5,000 times.\n* Decode it with Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100).\n* Observe that no Protobuf.JSON.DecodeError for an exceeded recursion limit is raised and the decoder walks every level. The same depth in a Google.Protobuf.Struct payload raises {:recursion_limit_exceeded, 100}. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Entry points.\u003c/strong\u003e \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, and \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e in \u003ccode\u003elib/protobuf/json.ex\u003c/code\u003e validate options and call \u003ccode\u003eProtobuf.JSON.Decode.from_json_data/3\u003c/code\u003e, which builds a \u003ccode\u003estate\u003c/code\u003e map carrying \u003ccode\u003edepth: 0\u003c/code\u003e and the \u003ccode\u003erecursion_limit\u003c/code\u003e (default 100). \u003ccode\u003efrom_decoded/3\u003c/code\u003e accepts already-parsed data, so the underlying JSON parser never bounds the depth either.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unguarded recursion.\u003c/strong\u003e For a user-defined message module, \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e calls \u003ccode\u003edecode_message/4\u003c/code\u003e, which calls \u003ccode\u003edecode_regular_fields/3\u003c/code\u003e and \u003ccode\u003edecode_oneof_fields/3\u003c/code\u003e. Each field value passes through \u003ccode\u003edecode_value/3\u003c/code\u003e and, for a singular embedded message, reaches the \u003ccode\u003edecode_singular/3\u003c/code\u003e clause matching \u003ccode\u003eembedded?: true\u003c/code\u003e. That clause calls \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e with \u003ccode\u003estate\u003c/code\u003e unchanged, so \u003ccode\u003estate.depth\u003c/code\u003e stays at 0 at every nesting level. Repeated fields and map values reach the same clause through \u003ccode\u003edecode_repeated/3\u003c/code\u003e and \u003ccode\u003edecode_map/3\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Guard coverage.\u003c/strong\u003e \u003ccode\u003eincrease_depth_and_maybe_throw/1\u003c/code\u003e increments \u003ccode\u003edepth\u003c/code\u003e and throws \u003ccode\u003e{:recursion_limit_exceeded, limit}\u003c/code\u003e when it exceeds the limit. It is called only from the \u003ccode\u003eGoogle.Protobuf.ListValue\u003c/code\u003e and \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e clauses, which the \u003ccode\u003eGoogle.Protobuf.Value\u003c/code\u003e clause delegates to. The \u003ccode\u003erecursion_limit\u003c/code\u003e documentation scopes the option to those wrappers, and no other path checks depth.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. Result.\u003c/strong\u003e A self-referential schema such as a \u003ccode\u003eTree\u003c/code\u003e message with a \u003ccode\u003eTree child\u003c/code\u003e field recurses once per JSON nesting level with no bound. Each level holds a live stack frame and allocates heap objects, so a sufficiently deep document exhausts the memory of the decoding process and crashes it. The BEAM \u003ccode\u003emax_heap_size\u003c/code\u003e process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Entry points.** `Protobuf.JSON.decode/3`, `Protobuf.JSON.decode!/3`, and `Protobuf.JSON.from_decoded/3` in `lib/protobuf/json.ex` validate options and call `Protobuf.JSON.Decode.from_json_data/3`, which builds a `state` map carrying `depth: 0` and the `recursion_limit` (default 100). `from_decoded/3` accepts already-parsed data, so the underlying JSON parser never bounds the depth either.\n\n**2. Unguarded recursion.** For a user-defined message module, `internal_from_json_data/3` calls `decode_message/4`, which calls `decode_regular_fields/3` and `decode_oneof_fields/3`. Each field value passes through `decode_value/3` and, for a singular embedded message, reaches the `decode_singular/3` clause matching `embedded?: true`. That clause calls `internal_from_json_data/3` with `state` unchanged, so `state.depth` stays at 0 at every nesting level. Repeated fields and map values reach the same clause through `decode_repeated/3` and `decode_map/3`.\n\n**3. Guard coverage.** `increase_depth_and_maybe_throw/1` increments `depth` and throws `{:recursion_limit_exceeded, limit}` when it exceeds the limit. It is called only from the `Google.Protobuf.ListValue` and `Google.Protobuf.Struct` clauses, which the `Google.Protobuf.Value` clause delegates to. The `recursion_limit` documentation scopes the option to those wrappers, and no other path checks depth.\n\n**4. Result.** A self-referential schema such as a `Tree` message with a `Tree child` field recurses once per JSON nesting level with no bound. Each level holds a live stack frame and allocates heap objects, so a sufficiently deep document exhausts the memory of the decoding process and crashes it. The BEAM `max_heap_size` process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node."
                }
              ],
              "value": "1. Entry points. Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, and Protobuf.JSON.from_decoded/3 in lib/protobuf/json.ex validate options and call Protobuf.JSON.Decode.from_json_data/3, which builds a state map carrying depth: 0 and the recursion_limit (default 100). from_decoded/3 accepts already-parsed data, so the underlying JSON parser never bounds the depth either.\n\n2. Unguarded recursion. For a user-defined message module, internal_from_json_data/3 calls decode_message/4, which calls decode_regular_fields/3 and decode_oneof_fields/3. Each field value passes through decode_value/3 and, for a singular embedded message, reaches the decode_singular/3 clause matching embedded?: true. That clause calls internal_from_json_data/3 with state unchanged, so state.depth stays at 0 at every nesting level. Repeated fields and map values reach the same clause through decode_repeated/3 and decode_map/3.\n\n3. Guard coverage. increase_depth_and_maybe_throw/1 increments depth and throws {:recursion_limit_exceeded, limit} when it exceeds the limit. It is called only from the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, which the Google.Protobuf.Value clause delegates to. The recursion_limit documentation scopes the option to those wrappers, and no other path checks depth.\n\n4. Result. A self-referential schema such as a Tree message with a Tree child field recurses once per JSON nesting level with no bound. Each level holds a live stack frame and allocates heap objects, so a sufficiently deep document exhausts the memory of the decoding process and crashes it. The BEAM max_heap_size process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-104635",
        "datePublished": "2026-10-09T08:17:52.372Z",
        "dateReserved": "2026-10-08T14:45:01.406Z",
        "dateUpdated": "2026-10-15T15:57:11.974Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-101028 (GCVE-0-2026-101028)

    Vulnerability from cvelistv5 – Published: 2026-10-09 07:01 – Updated: 2026-10-09 18:04
    VLAI
    Title
    Ash.count, Ash.exists and Ash.aggregate skip related resources' read policies in filters and sorts
    Summary
    Incorrect Authorization vulnerability in ash-project ash allows an actor to infer data in related records they cannot read via Ash.count/2, Ash.exists/2 and Ash.aggregate/3. Ash.Actions.Aggregate.run/4 (lib/ash/actions/aggregate.ex) applied only the root resource's read policy before running the aggregate query. The read path also applies each related resource's read policy to filter and sort references that cross a relationship, directly (for example comments.body) or through an aggregate over one, but the aggregate path skipped that step. A caller whose filter or sort reaches these functions, for example through Ash.Query.filter_input/2, an ash_lua script, or an AshAi tool offering count or exists results, can test conditions against related rows hidden from them and recover their existence and attribute values one query at a time. Ash.read/2 and its page counts are not affected. This issue affects ash: from 2.6.0 before 3.34.6.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-09 18:03 UTC
    CWE
    • CWE-863 - Incorrect Authorization
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    ash-project ash Affected: 2.6.0 , < 3.34.6 (semver)
        cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*
    Create a notification for this product.
    ash-project ash Affected: 30eaf1c6e8524527b703e3c4bfeff7967ee0b37c , < 80936187b27ee94f15cd875affd3141b5cb23185 (git)
        cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-101028",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-09T18:03:54.666261Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-09T18:04:24.729Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://github.com/ash-project/ash/security/advisories/GHSA-xj24-8f5c-pp5p"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Ash.Actions.Aggregate\u0027",
                "\u0027Elixir.Ash\u0027"
              ],
              "packageName": "ash",
              "packageURL": "pkg:hex/ash",
              "product": "ash",
              "programFiles": [
                "lib/ash/actions/aggregate.ex",
                "lib/ash.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Ash.Actions.Aggregate\u0027:run/4"
                },
                {
                  "name": "\u0027Elixir.Ash\u0027:count/2"
                },
                {
                  "name": "\u0027Elixir.Ash\u0027:exists/2"
                },
                {
                  "name": "\u0027Elixir.Ash\u0027:aggregate/3"
                }
              ],
              "repo": "https://github.com/ash-project/ash",
              "vendor": "ash-project",
              "versions": [
                {
                  "lessThan": "3.34.6",
                  "status": "affected",
                  "version": "2.6.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Ash.Actions.Aggregate\u0027",
                "\u0027Elixir.Ash\u0027"
              ],
              "packageName": "ash-project/ash",
              "packageURL": "pkg:github/ash-project/ash",
              "product": "ash",
              "programFiles": [
                "lib/ash/actions/aggregate.ex",
                "lib/ash.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Ash.Actions.Aggregate\u0027:run/4"
                },
                {
                  "name": "\u0027Elixir.Ash\u0027:count/2"
                },
                {
                  "name": "\u0027Elixir.Ash\u0027:exists/2"
                },
                {
                  "name": "\u0027Elixir.Ash\u0027:aggregate/3"
                }
              ],
              "repo": "https://github.com/ash-project/ash",
              "vendor": "ash-project",
              "versions": [
                {
                  "lessThan": "80936187b27ee94f15cd875affd3141b5cb23185",
                  "status": "affected",
                  "version": "30eaf1c6e8524527b703e3c4bfeff7967ee0b37c",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReachable only when an application passes caller-supplied filters or sorts that cross a relationship into \u003ccode\u003eAsh.count/2\u003c/code\u003e, \u003ccode\u003eAsh.exists/2\u003c/code\u003e or \u003ccode\u003eAsh.aggregate/3\u003c/code\u003e (directly, through ash_lua read operations, or through AshAi count, exists or aggregate result types), and a related resource\u0027s read policy is stricter than the root resource\u0027s.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reachable only when an application passes caller-supplied filters or sorts that cross a relationship into `Ash.count/2`, `Ash.exists/2` or `Ash.aggregate/3` (directly, through ash_lua read operations, or through AshAi count, exists or aggregate result types), and a related resource\u0027s read policy is stricter than the root resource\u0027s."
                }
              ],
              "value": "Reachable only when an application passes caller-supplied filters or sorts that cross a relationship into Ash.count/2, Ash.exists/2 or Ash.aggregate/3 (directly, through ash_lua read operations, or through AshAi count, exists or aggregate result types), and a related resource\u0027s read policy is stricter than the root resource\u0027s."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "3.34.6",
                      "versionStartIncluding": "2.6.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "reporter",
              "value": "Glenn Rempe"
            },
            {
              "lang": "en",
              "type": "finder",
              "value": "Glenn Rempe"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Zach Daniel / Ash Project"
            }
          ],
          "dateAssigned": "2026-10-09T00:19:31.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eIncorrect Authorization vulnerability in ash-project ash allows an actor to infer data in related records they cannot read via \u003ccode\u003eAsh.count/2\u003c/code\u003e, \u003ccode\u003eAsh.exists/2\u003c/code\u003e and \u003ccode\u003eAsh.aggregate/3\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eAsh.Actions.Aggregate.run/4\u003c/code\u003e (\u003ccode\u003elib/ash/actions/aggregate.ex\u003c/code\u003e) applied only the root resource\u0027s read policy before running the aggregate query. The read path also applies each related resource\u0027s read policy to filter and sort references that cross a relationship, directly (for example \u003ccode\u003ecomments.body\u003c/code\u003e) or through an aggregate over one, but the aggregate path skipped that step. A caller whose filter or sort reaches these functions, for example through \u003ccode\u003eAsh.Query.filter_input/2\u003c/code\u003e, an ash_lua script, or an AshAi tool offering count or exists results, can test conditions against related rows hidden from them and recover their existence and attribute values one query at a time. \u003ccode\u003eAsh.read/2\u003c/code\u003e and its page counts are not affected.\u003c/p\u003e\n\u003cp\u003eThis issue affects ash: from 2.6.0 before 3.34.6.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Incorrect Authorization vulnerability in ash-project ash allows an actor to infer data in related records they cannot read via `Ash.count/2`, `Ash.exists/2` and `Ash.aggregate/3`.\n\n`Ash.Actions.Aggregate.run/4` (`lib/ash/actions/aggregate.ex`) applied only the root resource\u0027s read policy before running the aggregate query. The read path also applies each related resource\u0027s read policy to filter and sort references that cross a relationship, directly (for example `comments.body`) or through an aggregate over one, but the aggregate path skipped that step. A caller whose filter or sort reaches these functions, for example through `Ash.Query.filter_input/2`, an ash_lua script, or an AshAi tool offering count or exists results, can test conditions against related rows hidden from them and recover their existence and attribute values one query at a time. `Ash.read/2` and its page counts are not affected.\n\nThis issue affects ash: from 2.6.0 before 3.34.6."
                }
              ],
              "value": "Incorrect Authorization vulnerability in ash-project ash allows an actor to infer data in related records they cannot read via Ash.count/2, Ash.exists/2 and Ash.aggregate/3.\n\nAsh.Actions.Aggregate.run/4 (lib/ash/actions/aggregate.ex) applied only the root resource\u0027s read policy before running the aggregate query. The read path also applies each related resource\u0027s read policy to filter and sort references that cross a relationship, directly (for example comments.body) or through an aggregate over one, but the aggregate path skipped that step. A caller whose filter or sort reaches these functions, for example through Ash.Query.filter_input/2, an ash_lua script, or an AshAi tool offering count or exists results, can test conditions against related rows hidden from them and recover their existence and attribute values one query at a time. Ash.read/2 and its page counts are not affected.\n\nThis issue affects ash: from 2.6.0 before 3.34.6."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-1",
              "descriptions": [
                {
                  "lang": "en",
                  "value": "CAPEC-1 Accessing Functionality Not Properly Constrained by ACLs"
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "LOW",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "HIGH",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-863",
                  "description": "CWE-863 Incorrect Authorization",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-09T07:01:06.470Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GHSA-xj24-8f5c-pp5p",
              "tags": [
                "vendor-advisory",
                "related"
              ],
              "url": "https://github.com/ash-project/ash/security/advisories/GHSA-xj24-8f5c-pp5p"
            },
            {
              "name": "EEF CNA record for CVE-2026-101028",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-101028.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-101028",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-101028"
            },
            {
              "name": "Introducing commit 30eaf1c in ash-project/ash",
              "tags": [
                "related"
              ],
              "url": "https://github.com/ash-project/ash/commit/30eaf1c6e8524527b703e3c4bfeff7967ee0b37c"
            },
            {
              "name": "Fix commit 8093618 in ash-project/ash",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ash-project/ash/commit/80936187b27ee94f15cd875affd3141b5cb23185"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Ash.count, Ash.exists and Ash.aggregate skip related resources\u0027 read policies in filters and sorts"
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-101028",
        "datePublished": "2026-10-09T07:01:06.470Z",
        "dateReserved": "2026-10-08T15:45:02.376Z",
        "dateUpdated": "2026-10-09T18:04:24.729Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-104634 (GCVE-0-2026-104634)

    Vulnerability from cvelistv5 – Published: 2026-10-08 13:41 – Updated: 2026-10-08 13:58
    VLAI
    Title
    beam_mcp: JSON boolean and null tool arguments reach dispatch as strings
    Summary
    Incorrect Type Conversion or Cast vulnerability in BeamMCP.Server in ScriptKittyOS beam_mcp allows an MCP client's JSON true, false and null tool arguments to reach the host's dispatch function as the strings "true", "false" and "nil". After BeamMCP.Schema.validate/2 accepted a value as a boolean, normalize_arguments/2 passed every argument through to_json_value/1, whose atom clause converts true, false and nil to strings. A string is truthy in Elixir, so a host that tests a boolean argument, for example if args.dry_run, takes the opposite branch for false, and a guard such as confirm: false reads as set. The client controls the argument and could send true directly, so the practical impact is limited to hosts whose behaviour on false differs from their behaviour on true, and to any policy layer in front of the server that permits false but refuses true. The same normalisation applies to prompts/get arguments, which exist from 0.5.0. This issue affects beam_mcp: from 0.1.0 before 0.10.1.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-08 13:58 UTC
    CWE
    • CWE-704 - Incorrect Type Conversion or Cast
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    ScriptKittyOS beam_mcp Affected: 0.1.0 , < 0.10.1 (semver)
        cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    ScriptKittyOS beam_mcp Affected: 083838eb8e17fe5f6fcaf761bdbe203110288b0b , < 289dbdbad641943b29a3b8d1eb36506cc8cec10a (git)
        cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-09-26 00:00
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-104634",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-08T13:58:26.222349Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-08T13:58:34.464Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.BeamMCP.Server\u0027"
              ],
              "packageName": "beam_mcp",
              "packageURL": "pkg:hex/beam_mcp",
              "product": "beam_mcp",
              "programFiles": [
                "lib/beam_mcp/server.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:handle_message/2"
                },
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:normalize_arguments/2"
                },
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:to_json_value/1"
                }
              ],
              "repo": "https://github.com/ScriptKittyOS/beam_mcp",
              "vendor": "ScriptKittyOS",
              "versions": [
                {
                  "lessThan": "0.10.1",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.BeamMCP.Server\u0027"
              ],
              "packageName": "scriptkittyos/beam_mcp",
              "packageURL": "pkg:github/scriptkittyos/beam_mcp",
              "product": "beam_mcp",
              "programFiles": [
                "lib/beam_mcp/server.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:handle_message/2"
                },
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:normalize_arguments/2"
                },
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:to_json_value/1"
                }
              ],
              "repo": "https://github.com/ScriptKittyOS/beam_mcp",
              "vendor": "ScriptKittyOS",
              "versions": [
                {
                  "lessThan": "289dbdbad641943b29a3b8d1eb36506cc8cec10a",
                  "status": "affected",
                  "version": "083838eb8e17fe5f6fcaf761bdbe203110288b0b",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.10.1",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Ayla Croft / Script Kitty OS"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Ayla Croft / Script Kitty OS"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Ayla Croft / Script Kitty OS"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-08T13:40:59.000Z",
          "datePublic": "2026-09-26T00:00:00.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eIncorrect Type Conversion or Cast vulnerability in \u003ccode\u003eBeamMCP.Server\u003c/code\u003e in ScriptKittyOS beam_mcp allows an MCP client\u0027s JSON \u003ccode\u003etrue\u003c/code\u003e, \u003ccode\u003efalse\u003c/code\u003e and \u003ccode\u003enull\u003c/code\u003e tool arguments to reach the host\u0027s dispatch function as the strings \"true\", \"false\" and \"nil\". After \u003ccode\u003eBeamMCP.Schema.validate/2\u003c/code\u003e accepted a value as a boolean, \u003ccode\u003enormalize_arguments/2\u003c/code\u003e passed every argument through \u003ccode\u003eto_json_value/1\u003c/code\u003e, whose atom clause converts \u003ccode\u003etrue\u003c/code\u003e, \u003ccode\u003efalse\u003c/code\u003e and \u003ccode\u003enil\u003c/code\u003e to strings. A string is truthy in Elixir, so a host that tests a boolean argument, for example \u003ccode\u003eif args.dry_run\u003c/code\u003e, takes the opposite branch for \u003ccode\u003efalse\u003c/code\u003e, and a guard such as \u003ccode\u003econfirm: false\u003c/code\u003e reads as set.\u003c/p\u003e\n\u003cp\u003eThe client controls the argument and could send \u003ccode\u003etrue\u003c/code\u003e directly, so the practical impact is limited to hosts whose behaviour on \u003ccode\u003efalse\u003c/code\u003e differs from their behaviour on \u003ccode\u003etrue\u003c/code\u003e, and to any policy layer in front of the server that permits \u003ccode\u003efalse\u003c/code\u003e but refuses \u003ccode\u003etrue\u003c/code\u003e. The same normalisation applies to \u003ccode\u003eprompts/get\u003c/code\u003e arguments, which exist from 0.5.0.\u003c/p\u003e\n\u003cp\u003eThis issue affects beam_mcp: from 0.1.0 before 0.10.1.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Incorrect Type Conversion or Cast vulnerability in `BeamMCP.Server` in ScriptKittyOS beam_mcp allows an MCP client\u0027s JSON `true`, `false` and `null` tool arguments to reach the host\u0027s dispatch function as the strings \"true\", \"false\" and \"nil\". After `BeamMCP.Schema.validate/2` accepted a value as a boolean, `normalize_arguments/2` passed every argument through `to_json_value/1`, whose atom clause converts `true`, `false` and `nil` to strings. A string is truthy in Elixir, so a host that tests a boolean argument, for example `if args.dry_run`, takes the opposite branch for `false`, and a guard such as `confirm: false` reads as set.\n\nThe client controls the argument and could send `true` directly, so the practical impact is limited to hosts whose behaviour on `false` differs from their behaviour on `true`, and to any policy layer in front of the server that permits `false` but refuses `true`. The same normalisation applies to `prompts/get` arguments, which exist from 0.5.0.\n\nThis issue affects beam_mcp: from 0.1.0 before 0.10.1."
                }
              ],
              "value": "Incorrect Type Conversion or Cast vulnerability in BeamMCP.Server in ScriptKittyOS beam_mcp allows an MCP client\u0027s JSON true, false and null tool arguments to reach the host\u0027s dispatch function as the strings \"true\", \"false\" and \"nil\". After BeamMCP.Schema.validate/2 accepted a value as a boolean, normalize_arguments/2 passed every argument through to_json_value/1, whose atom clause converts true, false and nil to strings. A string is truthy in Elixir, so a host that tests a boolean argument, for example if args.dry_run, takes the opposite branch for false, and a guard such as confirm: false reads as set.\n\nThe client controls the argument and could send true directly, so the practical impact is limited to hosts whose behaviour on false differs from their behaviour on true, and to any policy layer in front of the server that permits false but refuses true. The same normalisation applies to prompts/get arguments, which exist from 0.5.0.\n\nThis issue affects beam_mcp: from 0.1.0 before 0.10.1."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-153",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA host that branches on a boolean argument takes the opposite branch for \u003ccode\u003efalse\u003c/code\u003e, so a safety guard such as \u003ccode\u003econfirm: false\u003c/code\u003e or \u003ccode\u003edry_run: false\u003c/code\u003e is read as set. The client already controls the argument, so the gain over sending \u003ccode\u003etrue\u003c/code\u003e is limited to hosts and intermediaries that treat the two values differently.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A host that branches on a boolean argument takes the opposite branch for `false`, so a safety guard such as `confirm: false` or `dry_run: false` is read as set. The client already controls the argument, so the gain over sending `true` is limited to hosts and intermediaries that treat the two values differently."
                    }
                  ],
                  "value": "A host that branches on a boolean argument takes the opposite branch for false, so a safety guard such as confirm: false or dry_run: false is read as set. The client already controls the argument, so the gain over sending true is limited to hosts and intermediaries that treat the two values differently."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 2.3,
                "baseSeverity": "LOW",
                "privilegesRequired": "LOW",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-704",
                  "description": "CWE-704 Incorrect Type Conversion or Cast",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-08T13:41:05.745Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/ScriptKittyOS/beam_mcp/security/advisories/GHSA-wv7p-j6qh-3hj4"
            },
            {
              "name": "EEF CNA record for CVE-2026-104634",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-104634.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-104634",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-104634"
            },
            {
              "name": "Introducing commit 083838e in ScriptKittyOS/beam_mcp",
              "tags": [
                "related"
              ],
              "url": "https://github.com/ScriptKittyOS/beam_mcp/commit/083838eb8e17fe5f6fcaf761bdbe203110288b0b"
            },
            {
              "name": "Fix commit 289dbdb in ScriptKittyOS/beam_mcp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ScriptKittyOS/beam_mcp/commit/289dbdbad641943b29a3b8d1eb36506cc8cec10a"
            }
          ],
          "source": {
            "discovery": "INTERNAL"
          },
          "title": "beam_mcp: JSON boolean and null tool arguments reach dispatch as strings",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eIn the host\u0027s dispatch function, compare boolean arguments against both forms, for example \u003ccode\u003eargs.flag in [true, \"true\"]\u003c/code\u003e and \u003ccode\u003eargs.flag in [false, \"false\"]\u003c/code\u003e, and treat the string \"nil\" as \u003ccode\u003enull\u003c/code\u003e where the schema admits null.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "In the host\u0027s dispatch function, compare boolean arguments against both forms, for example `args.flag in [true, \"true\"]` and `args.flag in [false, \"false\"]`, and treat the string \"nil\" as `null` where the schema admits null."
                }
              ],
              "value": "In the host\u0027s dispatch function, compare boolean arguments against both forms, for example args.flag in [true, \"true\"] and args.flag in [false, \"false\"], and treat the string \"nil\" as null where the schema admits null."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eDeclare a tool whose \u003ccode\u003einput_schema\u003c/code\u003e has \u003ccode\u003e\"dry_run\": {\"type\": \"boolean\"}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003etools/call\u003c/code\u003e with \u003ccode\u003e\"arguments\": {\"dry_run\": false}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eOn beam_mcp 0.10.0 the dispatch function receives \u003ccode\u003e%{dry_run: \"false\"}\u003c/code\u003e and \u003ccode\u003eif args.dry_run\u003c/code\u003e evaluates the true branch. On 0.10.1 it receives \u003ccode\u003e%{dry_run: false}\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Declare a tool whose `input_schema` has `\"dry_run\": {\"type\": \"boolean\"}`.\n2. Send `tools/call` with `\"arguments\": {\"dry_run\": false}`.\n3. On beam_mcp 0.10.0 the dispatch function receives `%{dry_run: \"false\"}` and `if args.dry_run` evaluates the true branch. On 0.10.1 it receives `%{dry_run: false}`."
                }
              ],
              "value": "* Declare a tool whose input_schema has \"dry_run\": {\"type\": \"boolean\"}.\n* Send tools/call with \"arguments\": {\"dry_run\": false}.\n* On beam_mcp 0.10.0 the dispatch function receives %{dry_run: \"false\"} and if args.dry_run evaluates the true branch. On 0.10.1 it receives %{dry_run: false}."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Validation.\u003c/strong\u003e \u003ccode\u003eBeamMCP.Schema.check_type/3\u003c/code\u003e accepts a JSON \u003ccode\u003efalse\u003c/code\u003e for a \u003ccode\u003eboolean\u003c/code\u003e property because the decoded value is the atom \u003ccode\u003efalse\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Normalisation.\u003c/strong\u003e \u003ccode\u003eBeamMCP.Server.normalize_arguments/2\u003c/code\u003e in \u003ccode\u003elib/beam_mcp/server.ex\u003c/code\u003e maps each declared key to an atom and passes each value through \u003ccode\u003eto_json_value/1\u003c/code\u003e. That function was written to encode host results for the wire, and its clause \u003ccode\u003ewhen is_atom(value)\u003c/code\u003e converts the value with \u003ccode\u003eAtom.to_string/1\u003c/code\u003e. \u003ccode\u003etrue\u003c/code\u003e, \u003ccode\u003efalse\u003c/code\u003e and \u003ccode\u003enil\u003c/code\u003e are atoms, so they become the strings \"true\", \"false\" and \"nil\", at any depth of the argument map.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Dispatch.\u003c/strong\u003e The host receives \u003ccode\u003e%{dry_run: \"false\"}\u003c/code\u003e. Any string is truthy in Elixir, so \u003ccode\u003eif args.dry_run\u003c/code\u003e takes the true branch. A host result carrying booleans was stringified the same way on the way out. The fix in 0.10.1 adds a clause that passes booleans and \u003ccode\u003enil\u003c/code\u003e through unchanged.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Validation.** `BeamMCP.Schema.check_type/3` accepts a JSON `false` for a `boolean` property because the decoded value is the atom `false`.\n\n**2. Normalisation.** `BeamMCP.Server.normalize_arguments/2` in `lib/beam_mcp/server.ex` maps each declared key to an atom and passes each value through `to_json_value/1`. That function was written to encode host results for the wire, and its clause `when is_atom(value)` converts the value with `Atom.to_string/1`. `true`, `false` and `nil` are atoms, so they become the strings \"true\", \"false\" and \"nil\", at any depth of the argument map.\n\n**3. Dispatch.** The host receives `%{dry_run: \"false\"}`. Any string is truthy in Elixir, so `if args.dry_run` takes the true branch. A host result carrying booleans was stringified the same way on the way out. The fix in 0.10.1 adds a clause that passes booleans and `nil` through unchanged."
                }
              ],
              "value": "1. Validation. BeamMCP.Schema.check_type/3 accepts a JSON false for a boolean property because the decoded value is the atom false.\n\n2. Normalisation. BeamMCP.Server.normalize_arguments/2 in lib/beam_mcp/server.ex maps each declared key to an atom and passes each value through to_json_value/1. That function was written to encode host results for the wire, and its clause when is_atom(value) converts the value with Atom.to_string/1. true, false and nil are atoms, so they become the strings \"true\", \"false\" and \"nil\", at any depth of the argument map.\n\n3. Dispatch. The host receives %{dry_run: \"false\"}. Any string is truthy in Elixir, so if args.dry_run takes the true branch. A host result carrying booleans was stringified the same way on the way out. The fix in 0.10.1 adds a clause that passes booleans and nil through unchanged."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-104634",
        "datePublished": "2026-10-08T13:41:05.745Z",
        "dateReserved": "2026-10-04T16:00:02.420Z",
        "dateUpdated": "2026-10-08T13:58:34.464Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-88257 (GCVE-0-2026-88257)

    Vulnerability from cvelistv5 – Published: 2026-10-08 13:40 – Updated: 2026-10-08 14:09
    VLAI
    Title
    beam_mcp: nested tool argument constraints advertised but not enforced
    Summary
    Improper Input Validation vulnerability in BeamMCP.Schema in ScriptKittyOS beam_mcp allows an MCP client to reach a tool's dispatch function with arguments that violate the input schema the server advertised. BeamMCP.Schema.validate/2 checked type, required, additionalProperties, enum and numeric bounds on the top-level arguments object only. Constraints inside nested objects and on array items (items, minItems, maxItems, minLength, maxLength, pattern, nested required, enum and additionalProperties: false) were advertised by tools/list and never checked at tools/call or prompts/get, and keywords outside the enforced subset (oneOf, anyOf, $ref) were advertised and ignored. A host whose dispatch code relies on the schema it declared receives values the schema forbids, such as an out-of-range number or an undeclared key inside a nested object. What the host does with such a value decides the impact. This issue affects beam_mcp: from 0.1.0 before 0.10.1.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-08 13:59 UTC
    CWE
    • CWE-20 - Improper Input Validation
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    ScriptKittyOS beam_mcp Affected: 0.1.0 , < 0.10.1 (semver)
        cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    ScriptKittyOS beam_mcp Affected: 083838eb8e17fe5f6fcaf761bdbe203110288b0b , < 289dbdbad641943b29a3b8d1eb36506cc8cec10a (git)
        cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-09-26 00:00
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-88257",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-08T13:59:14.866448Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-08T13:59:33.374Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.BeamMCP.Schema\u0027",
                "\u0027Elixir.BeamMCP.Server\u0027"
              ],
              "packageName": "beam_mcp",
              "packageURL": "pkg:hex/beam_mcp",
              "product": "beam_mcp",
              "programFiles": [
                "lib/beam_mcp/schema.ex",
                "lib/beam_mcp/server.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.BeamMCP.Schema\u0027:validate/2"
                },
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:handle_message/2"
                }
              ],
              "repo": "https://github.com/ScriptKittyOS/beam_mcp",
              "vendor": "ScriptKittyOS",
              "versions": [
                {
                  "lessThan": "0.10.1",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.BeamMCP.Schema\u0027",
                "\u0027Elixir.BeamMCP.Server\u0027"
              ],
              "packageName": "scriptkittyos/beam_mcp",
              "packageURL": "pkg:github/scriptkittyos/beam_mcp",
              "product": "beam_mcp",
              "programFiles": [
                "lib/beam_mcp/schema.ex",
                "lib/beam_mcp/server.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.BeamMCP.Schema\u0027:validate/2"
                },
                {
                  "name": "\u0027Elixir.BeamMCP.Server\u0027:handle_message/2"
                }
              ],
              "repo": "https://github.com/ScriptKittyOS/beam_mcp",
              "vendor": "ScriptKittyOS",
              "versions": [
                {
                  "lessThan": "289dbdbad641943b29a3b8d1eb36506cc8cec10a",
                  "status": "affected",
                  "version": "083838eb8e17fe5f6fcaf761bdbe203110288b0b",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:scriptkittyos:beam_mcp:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "0.10.1",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Ayla Croft / Script Kitty OS"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Ayla Croft / Script Kitty OS"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Ayla Croft / Script Kitty OS"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-08T13:40:44.000Z",
          "datePublic": "2026-09-26T00:00:00.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eImproper Input Validation vulnerability in \u003ccode\u003eBeamMCP.Schema\u003c/code\u003e in ScriptKittyOS beam_mcp allows an MCP client to reach a tool\u0027s dispatch function with arguments that violate the input schema the server advertised. \u003ccode\u003eBeamMCP.Schema.validate/2\u003c/code\u003e checked \u003ccode\u003etype\u003c/code\u003e, \u003ccode\u003erequired\u003c/code\u003e, \u003ccode\u003eadditionalProperties\u003c/code\u003e, \u003ccode\u003eenum\u003c/code\u003e and numeric bounds on the top-level arguments object only. Constraints inside nested objects and on array items (\u003ccode\u003eitems\u003c/code\u003e, \u003ccode\u003eminItems\u003c/code\u003e, \u003ccode\u003emaxItems\u003c/code\u003e, \u003ccode\u003eminLength\u003c/code\u003e, \u003ccode\u003emaxLength\u003c/code\u003e, \u003ccode\u003epattern\u003c/code\u003e, nested \u003ccode\u003erequired\u003c/code\u003e, \u003ccode\u003eenum\u003c/code\u003e and \u003ccode\u003eadditionalProperties: false\u003c/code\u003e) were advertised by \u003ccode\u003etools/list\u003c/code\u003e and never checked at \u003ccode\u003etools/call\u003c/code\u003e or \u003ccode\u003eprompts/get\u003c/code\u003e, and keywords outside the enforced subset (\u003ccode\u003eoneOf\u003c/code\u003e, \u003ccode\u003eanyOf\u003c/code\u003e, \u003ccode\u003e$ref\u003c/code\u003e) were advertised and ignored.\u003c/p\u003e\n\u003cp\u003eA host whose dispatch code relies on the schema it declared receives values the schema forbids, such as an out-of-range number or an undeclared key inside a nested object. What the host does with such a value decides the impact.\u003c/p\u003e\n\u003cp\u003eThis issue affects beam_mcp: from 0.1.0 before 0.10.1.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Improper Input Validation vulnerability in `BeamMCP.Schema` in ScriptKittyOS beam_mcp allows an MCP client to reach a tool\u0027s dispatch function with arguments that violate the input schema the server advertised. `BeamMCP.Schema.validate/2` checked `type`, `required`, `additionalProperties`, `enum` and numeric bounds on the top-level arguments object only. Constraints inside nested objects and on array items (`items`, `minItems`, `maxItems`, `minLength`, `maxLength`, `pattern`, nested `required`, `enum` and `additionalProperties: false`) were advertised by `tools/list` and never checked at `tools/call` or `prompts/get`, and keywords outside the enforced subset (`oneOf`, `anyOf`, `$ref`) were advertised and ignored.\n\nA host whose dispatch code relies on the schema it declared receives values the schema forbids, such as an out-of-range number or an undeclared key inside a nested object. What the host does with such a value decides the impact.\n\nThis issue affects beam_mcp: from 0.1.0 before 0.10.1."
                }
              ],
              "value": "Improper Input Validation vulnerability in BeamMCP.Schema in ScriptKittyOS beam_mcp allows an MCP client to reach a tool\u0027s dispatch function with arguments that violate the input schema the server advertised. BeamMCP.Schema.validate/2 checked type, required, additionalProperties, enum and numeric bounds on the top-level arguments object only. Constraints inside nested objects and on array items (items, minItems, maxItems, minLength, maxLength, pattern, nested required, enum and additionalProperties: false) were advertised by tools/list and never checked at tools/call or prompts/get, and keywords outside the enforced subset (oneOf, anyOf, $ref) were advertised and ignored.\n\nA host whose dispatch code relies on the schema it declared receives values the schema forbids, such as an out-of-range number or an undeclared key inside a nested object. What the host does with such a value decides the impact.\n\nThis issue affects beam_mcp: from 0.1.0 before 0.10.1."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-153",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn MCP client can hand a host\u0027s tool values the host\u0027s declared schema forbids, such as an out-of-range number or an undeclared key inside a nested object. The consequence depends on what the host\u0027s dispatch code does with a value it never expected to receive.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An MCP client can hand a host\u0027s tool values the host\u0027s declared schema forbids, such as an out-of-range number or an undeclared key inside a nested object. The consequence depends on what the host\u0027s dispatch code does with a value it never expected to receive."
                    }
                  ],
                  "value": "An MCP client can hand a host\u0027s tool values the host\u0027s declared schema forbids, such as an out-of-range number or an undeclared key inside a nested object. The consequence depends on what the host\u0027s dispatch code does with a value it never expected to receive."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "NONE",
                "attackVector": "NETWORK",
                "baseScore": 5.3,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "LOW",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-20",
                  "description": "CWE-20 Improper Input Validation",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-08T14:09:41.540Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/ScriptKittyOS/beam_mcp/security/advisories/GHSA-mrg2-4747-fmpw"
            },
            {
              "name": "EEF CNA record for CVE-2026-88257",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-88257.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-88257",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-88257"
            },
            {
              "name": "Introducing commit 083838e in ScriptKittyOS/beam_mcp",
              "tags": [
                "related"
              ],
              "url": "https://github.com/ScriptKittyOS/beam_mcp/commit/083838eb8e17fe5f6fcaf761bdbe203110288b0b"
            },
            {
              "name": "Fix commit 289dbdb in ScriptKittyOS/beam_mcp",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ScriptKittyOS/beam_mcp/commit/289dbdbad641943b29a3b8d1eb36506cc8cec10a"
            }
          ],
          "source": {
            "discovery": "INTERNAL"
          },
          "title": "beam_mcp: nested tool argument constraints advertised but not enforced",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eRe-validate the arguments inside the host\u0027s dispatch function against every constraint the schema declares below the top level, or move each constraint to a top-level property, which the affected versions do enforce.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Re-validate the arguments inside the host\u0027s dispatch function against every constraint the schema declares below the top level, or move each constraint to a top-level property, which the affected versions do enforce."
                }
              ],
              "value": "Re-validate the arguments inside the host\u0027s dispatch function against every constraint the schema declares below the top level, or move each constraint to a top-level property, which the affected versions do enforce."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eDeclare a tool whose \u003ccode\u003einput_schema\u003c/code\u003e has a nested object property \u003ccode\u003eopts\u003c/code\u003e with \u003ccode\u003e\"properties\": {\"level\": {\"type\": \"integer\", \"maximum\": 3}}\u003c/code\u003e, \u003ccode\u003e\"required\": [\"level\"]\u003c/code\u003e and \u003ccode\u003e\"additionalProperties\": false\u003c/code\u003e, and an array property \u003ccode\u003etags\u003c/code\u003e with \u003ccode\u003e\"items\": {\"type\": \"string\"}\u003c/code\u003e and \u003ccode\u003e\"maxItems\": 2\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003etools/call\u003c/code\u003e with \u003ccode\u003e\"arguments\": {\"opts\": {\"level\": 99, \"extra\": \"x\"}}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eOn beam_mcp 0.10.0 the dispatch function receives \u003ccode\u003e%{opts: %{\"extra\" =\u0026gt; \"x\", \"level\" =\u0026gt; 99}}\u003c/code\u003e. On 0.10.1 the call is refused with \u003ccode\u003einvalid arguments: unknown property: opts.extra\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003e\"arguments\": {\"tags\": [1, 2, 3]}\u003c/code\u003e. On 0.10.0 the dispatch function receives \u003ccode\u003etags: [1, 2, 3]\u003c/code\u003e. On 0.10.1 the call is refused with \u003ccode\u003etags must have at most 2 items\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Declare a tool whose `input_schema` has a nested object property `opts` with `\"properties\": {\"level\": {\"type\": \"integer\", \"maximum\": 3}}`, `\"required\": [\"level\"]` and `\"additionalProperties\": false`, and an array property `tags` with `\"items\": {\"type\": \"string\"}` and `\"maxItems\": 2`.\n2. Send `tools/call` with `\"arguments\": {\"opts\": {\"level\": 99, \"extra\": \"x\"}}`.\n3. On beam_mcp 0.10.0 the dispatch function receives `%{opts: %{\"extra\" =\u003e \"x\", \"level\" =\u003e 99}}`. On 0.10.1 the call is refused with `invalid arguments: unknown property: opts.extra`.\n4. Send `\"arguments\": {\"tags\": [1, 2, 3]}`. On 0.10.0 the dispatch function receives `tags: [1, 2, 3]`. On 0.10.1 the call is refused with `tags must have at most 2 items`."
                }
              ],
              "value": "* Declare a tool whose input_schema has a nested object property opts with \"properties\": {\"level\": {\"type\": \"integer\", \"maximum\": 3}}, \"required\": [\"level\"] and \"additionalProperties\": false, and an array property tags with \"items\": {\"type\": \"string\"} and \"maxItems\": 2.\n* Send tools/call with \"arguments\": {\"opts\": {\"level\": 99, \"extra\": \"x\"}}.\n* On beam_mcp 0.10.0 the dispatch function receives %{opts: %{\"extra\" =\u003e \"x\", \"level\" =\u003e 99}}. On 0.10.1 the call is refused with invalid arguments: unknown property: opts.extra.\n* Send \"arguments\": {\"tags\": [1, 2, 3]}. On 0.10.0 the dispatch function receives tags: [1, 2, 3]. On 0.10.1 the call is refused with tags must have at most 2 items."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Advertising.\u003c/strong\u003e \u003ccode\u003etools/list\u003c/code\u003e returns each tool\u0027s \u003ccode\u003einput_schema\u003c/code\u003e verbatim, including nested \u003ccode\u003eproperties\u003c/code\u003e, \u003ccode\u003eitems\u003c/code\u003e and constraint keywords.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Validation.\u003c/strong\u003e \u003ccode\u003eBeamMCP.Schema.validate/2\u003c/code\u003e in \u003ccode\u003elib/beam_mcp/schema.ex\u003c/code\u003e walks only the first level: \u003ccode\u003echeck_required/2\u003c/code\u003e and \u003ccode\u003echeck_additional/3\u003c/code\u003e run on the top-level object, and \u003ccode\u003echeck_properties/2\u003c/code\u003e applies \u003ccode\u003echeck_type\u003c/code\u003e, \u003ccode\u003echeck_enum\u003c/code\u003e and \u003ccode\u003echeck_range\u003c/code\u003e to each top-level value. A value of type \u003ccode\u003eobject\u003c/code\u003e or \u003ccode\u003earray\u003c/code\u003e is accepted once its own type matches, and its contents are not visited. Keywords the validator does not know are ignored.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Dispatch.\u003c/strong\u003e \u003ccode\u003eBeamMCP.Server.validate_and_dispatch/3\u003c/code\u003e passes the accepted arguments to \u003ccode\u003enormalize_arguments/2\u003c/code\u003e and then to the host\u0027s dispatch function, so an undeclared key inside a nested object, an out-of-range nested number, or an array item of the wrong type reaches host code that believed the schema excluded it. The same validator runs on \u003ccode\u003eprompts/get\u003c/code\u003e arguments. The fix in 0.10.1 enforces every keyword of the documented subset at every depth and refuses a schema that uses any other keyword when the catalog is loaded.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Advertising.** `tools/list` returns each tool\u0027s `input_schema` verbatim, including nested `properties`, `items` and constraint keywords.\n\n**2. Validation.** `BeamMCP.Schema.validate/2` in `lib/beam_mcp/schema.ex` walks only the first level: `check_required/2` and `check_additional/3` run on the top-level object, and `check_properties/2` applies `check_type`, `check_enum` and `check_range` to each top-level value. A value of type `object` or `array` is accepted once its own type matches, and its contents are not visited. Keywords the validator does not know are ignored.\n\n**3. Dispatch.** `BeamMCP.Server.validate_and_dispatch/3` passes the accepted arguments to `normalize_arguments/2` and then to the host\u0027s dispatch function, so an undeclared key inside a nested object, an out-of-range nested number, or an array item of the wrong type reaches host code that believed the schema excluded it. The same validator runs on `prompts/get` arguments. The fix in 0.10.1 enforces every keyword of the documented subset at every depth and refuses a schema that uses any other keyword when the catalog is loaded."
                }
              ],
              "value": "1. Advertising. tools/list returns each tool\u0027s input_schema verbatim, including nested properties, items and constraint keywords.\n\n2. Validation. BeamMCP.Schema.validate/2 in lib/beam_mcp/schema.ex walks only the first level: check_required/2 and check_additional/3 run on the top-level object, and check_properties/2 applies check_type, check_enum and check_range to each top-level value. A value of type object or array is accepted once its own type matches, and its contents are not visited. Keywords the validator does not know are ignored.\n\n3. Dispatch. BeamMCP.Server.validate_and_dispatch/3 passes the accepted arguments to normalize_arguments/2 and then to the host\u0027s dispatch function, so an undeclared key inside a nested object, an out-of-range nested number, or an array item of the wrong type reaches host code that believed the schema excluded it. The same validator runs on prompts/get arguments. The fix in 0.10.1 enforces every keyword of the documented subset at every depth and refuses a schema that uses any other keyword when the catalog is loaded."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-88257",
        "datePublished": "2026-10-08T13:40:55.828Z",
        "dateReserved": "2026-10-07T22:15:01.878Z",
        "dateUpdated": "2026-10-08T14:09:41.540Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-94206 (GCVE-0-2026-94206)

    Vulnerability from cvelistv5 – Published: 2026-10-06 08:38 – Updated: 2026-10-06 11:36
    VLAI
    Title
    Cloak PBKDF2 field ignores the configured iteration count and runs only :size rounds
    Summary
    Use of Password Hash With Insufficient Computational Effort vulnerability in danielberkompas cloak_ecto and danielberkompas cloak allows an attacker who holds the hashed values and the configured secret to brute-force low-entropy plaintexts much faster than configured. The dump/1 callback that Cloak.Ecto.PBKDF2 (Cloak.Fields.PBKDF2 in cloak before the Ecto code moved to cloak_ecto) injects into a field module calls :pbkdf2.pbkdf2/4 with config[:size] in the iteration-count position. The :iterations setting is validated but never used. With the cloak_ecto defaults (iterations: 600_000, size: 32) each hash runs 32 PBKDF2 rounds instead of 600,000, so offline guessing of values such as email addresses costs about 18,750 times less than configured. This issue affects cloak_ecto: from 1.0.0-alpha.0 onward; cloak: from 0.7.0 before 1.0.0-alpha.0.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-06 11:34 UTC
    CWE
    • CWE-916 - Use of Password Hash With Insufficient Computational Effort
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    danielberkompas cloak_ecto Affected: 1.0.0-alpha.0 , < * (semver)
        cpe:2.3:a:danielberkompas:cloak_ecto:*:*:*:*:*:*:*:*
    Create a notification for this product.
    danielberkompas cloak_ecto Affected: a8fa1642b02f1c445a1ee8794c9095eb0921f8f3 , < * (git)
        cpe:2.3:a:danielberkompas:cloak_ecto:*:*:*:*:*:*:*:*
    Create a notification for this product.
    danielberkompas cloak Affected: 0.7.0 , < 1.0.0-alpha.0 (semver)
        cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*
    Create a notification for this product.
    danielberkompas cloak Affected: 8699e6417a162c39d9f0ef63511bd23d3f38d1d2 , < 681c9702b7cd9afe0e5a840751ab009f550c69a9 (git)
        cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-94206",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-06T11:34:00.942993Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-06T11:36:25.466Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:danielberkompas:cloak_ecto:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Cloak.Ecto.PBKDF2\u0027"
              ],
              "packageName": "cloak_ecto",
              "packageURL": "pkg:hex/cloak_ecto",
              "product": "cloak_ecto",
              "programFiles": [
                "lib/cloak_ecto/types/pbkdf2.ex"
              ],
              "repo": "https://github.com/danielberkompas/cloak_ecto",
              "vendor": "danielberkompas",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "1.0.0-alpha.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:danielberkompas:cloak_ecto:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Cloak.Ecto.PBKDF2\u0027"
              ],
              "packageName": "danielberkompas/cloak_ecto",
              "packageURL": "pkg:github/danielberkompas/cloak_ecto",
              "product": "cloak_ecto",
              "programFiles": [
                "lib/cloak_ecto/types/pbkdf2.ex"
              ],
              "repo": "https://github.com/danielberkompas/cloak_ecto",
              "vendor": "danielberkompas",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "a8fa1642b02f1c445a1ee8794c9095eb0921f8f3",
                  "versionType": "git"
                }
              ]
            },
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Cloak.Fields.PBKDF2\u0027"
              ],
              "packageName": "cloak",
              "packageURL": "pkg:hex/cloak",
              "product": "cloak",
              "programFiles": [
                "lib/cloak/fields/pbkdf2.ex"
              ],
              "repo": "https://github.com/danielberkompas/cloak",
              "vendor": "danielberkompas",
              "versions": [
                {
                  "lessThan": "1.0.0-alpha.0",
                  "status": "affected",
                  "version": "0.7.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Cloak.Fields.PBKDF2\u0027"
              ],
              "packageName": "danielberkompas/cloak",
              "packageURL": "pkg:github/danielberkompas/cloak",
              "product": "cloak",
              "programFiles": [
                "lib/cloak/fields/pbkdf2.ex"
              ],
              "repo": "https://github.com/danielberkompas/cloak",
              "vendor": "danielberkompas",
              "versions": [
                {
                  "lessThan": "681c9702b7cd9afe0e5a840751ab009f550c69a9",
                  "status": "affected",
                  "version": "8699e6417a162c39d9f0ef63511bd23d3f38d1d2",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:danielberkompas:cloak_ecto:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "1.0.0-alpha.0",
                      "vulnerable": true
                    },
                    {
                      "criteria": "cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.0.0-alpha.0",
                      "versionStartIncluding": "0.7.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-06T08:35:00.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eUse of Password Hash With Insufficient Computational Effort vulnerability in danielberkompas cloak_ecto and danielberkompas cloak allows an attacker who holds the hashed values and the configured secret to brute-force low-entropy plaintexts much faster than configured.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003edump/1\u003c/code\u003e callback that \u003ccode\u003eCloak.Ecto.PBKDF2\u003c/code\u003e (\u003ccode\u003eCloak.Fields.PBKDF2\u003c/code\u003e in cloak before the Ecto code moved to cloak_ecto) injects into a field module calls \u003ccode\u003e:pbkdf2.pbkdf2/4\u003c/code\u003e with \u003ccode\u003econfig[:size]\u003c/code\u003e in the iteration-count position. The \u003ccode\u003e:iterations\u003c/code\u003e setting is validated but never used. With the cloak_ecto defaults (\u003ccode\u003eiterations: 600_000\u003c/code\u003e, \u003ccode\u003esize: 32\u003c/code\u003e) each hash runs 32 PBKDF2 rounds instead of 600,000, so offline guessing of values such as email addresses costs about 18,750 times less than configured.\u003c/p\u003e\n\u003cp\u003eThis issue affects cloak_ecto: from 1.0.0-alpha.0 onward; cloak: from 0.7.0 before 1.0.0-alpha.0.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Use of Password Hash With Insufficient Computational Effort vulnerability in danielberkompas cloak_ecto and danielberkompas cloak allows an attacker who holds the hashed values and the configured secret to brute-force low-entropy plaintexts much faster than configured.\n\nThe `dump/1` callback that `Cloak.Ecto.PBKDF2` (`Cloak.Fields.PBKDF2` in cloak before the Ecto code moved to cloak_ecto) injects into a field module calls `:pbkdf2.pbkdf2/4` with `config[:size]` in the iteration-count position. The `:iterations` setting is validated but never used. With the cloak_ecto defaults (`iterations: 600_000`, `size: 32`) each hash runs 32 PBKDF2 rounds instead of 600,000, so offline guessing of values such as email addresses costs about 18,750 times less than configured.\n\nThis issue affects cloak_ecto: from 1.0.0-alpha.0 onward; cloak: from 0.7.0 before 1.0.0-alpha.0."
                }
              ],
              "value": "Use of Password Hash With Insufficient Computational Effort vulnerability in danielberkompas cloak_ecto and danielberkompas cloak allows an attacker who holds the hashed values and the configured secret to brute-force low-entropy plaintexts much faster than configured.\n\nThe dump/1 callback that Cloak.Ecto.PBKDF2 (Cloak.Fields.PBKDF2 in cloak before the Ecto code moved to cloak_ecto) injects into a field module calls :pbkdf2.pbkdf2/4 with config[:size] in the iteration-count position. The :iterations setting is validated but never used. With the cloak_ecto defaults (iterations: 600_000, size: 32) each hash runs 32 PBKDF2 rounds instead of 600,000, so offline guessing of values such as email addresses costs about 18,750 times less than configured.\n\nThis issue affects cloak_ecto: from 1.0.0-alpha.0 onward; cloak: from 0.7.0 before 1.0.0-alpha.0."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-55",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn attacker who obtains a database dump and the PBKDF2 secret can recover hashed values with low entropy, such as email addresses, by offline guessing at a cost far below what the configuration promises.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An attacker who obtains a database dump and the PBKDF2 secret can recover hashed values with low entropy, such as email addresses, by offline guessing at a cost far below what the configuration promises."
                    }
                  ],
                  "value": "An attacker who obtains a database dump and the PBKDF2 secret can recover hashed values with low entropy, such as email addresses, by offline guessing at a cost far below what the configuration promises."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6.3,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "LOW",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-916",
                  "description": "CWE-916 Use of Password Hash With Insufficient Computational Effort",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-06T08:38:04.584Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "EEF CNA record for CVE-2026-94206",
              "tags": [
                "related",
                "third-party-advisory"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-94206.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-94206",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-94206"
            },
            {
              "name": "Introducing commit a8fa164 in danielberkompas/cloak_ecto",
              "tags": [
                "related"
              ],
              "url": "https://github.com/danielberkompas/cloak_ecto/commit/a8fa1642b02f1c445a1ee8794c9095eb0921f8f3"
            },
            {
              "name": "Introducing commit 8699e64 in danielberkompas/cloak",
              "tags": [
                "related"
              ],
              "url": "https://github.com/danielberkompas/cloak/commit/8699e6417a162c39d9f0ef63511bd23d3f38d1d2"
            },
            {
              "name": "Fix commit 681c970 in danielberkompas/cloak",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/danielberkompas/cloak/commit/681c9702b7cd9afe0e5a840751ab009f550c69a9"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Cloak PBKDF2 field ignores the configured iteration count and runs only :size rounds",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eOverride \u003ccode\u003edump/1\u003c/code\u003e in the field module that uses \u003ccode\u003eCloak.Ecto.PBKDF2\u003c/code\u003e, so that it calls \u003ccode\u003e:pbkdf2.pbkdf2/5\u003c/code\u003e with the configured \u003ccode\u003e:iterations\u003c/code\u003e and \u003ccode\u003e:size\u003c/code\u003e. Then recompute all stored hashes, because the existing values no longer match.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Override `dump/1` in the field module that uses `Cloak.Ecto.PBKDF2`, so that it calls `:pbkdf2.pbkdf2/5` with the configured `:iterations` and `:size`. Then recompute all stored hashes, because the existing values no longer match."
                }
              ],
              "value": "Override dump/1 in the field module that uses Cloak.Ecto.PBKDF2, so that it calls :pbkdf2.pbkdf2/5 with the configured :iterations and :size. Then recompute all stored hashes, because the existing values no longer match."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eDefine a field module with \u003ccode\u003euse Cloak.Ecto.PBKDF2\u003c/code\u003e and configure only a \u003ccode\u003e:secret\u003c/code\u003e, so that the defaults \u003ccode\u003eiterations: 600_000\u003c/code\u003e and \u003ccode\u003esize: 32\u003c/code\u003e apply.\u003c/li\u003e\n\u003cli\u003eCall \u003ccode\u003edump/1\u003c/code\u003e on a value.\u003c/li\u003e\n\u003cli\u003eCompare the result with \u003ccode\u003e:pbkdf2.pbkdf2({:hmac, :sha256}, value, secret, 32)\u003c/code\u003e and with the same call at 600,000 rounds. It matches the 32-round hash, not the 600,000-round hash, and returns in microseconds instead of about one second.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Define a field module with `use Cloak.Ecto.PBKDF2` and configure only a `:secret`, so that the defaults `iterations: 600_000` and `size: 32` apply.\n2. Call `dump/1` on a value.\n3. Compare the result with `:pbkdf2.pbkdf2({:hmac, :sha256}, value, secret, 32)` and with the same call at 600,000 rounds. It matches the 32-round hash, not the 600,000-round hash, and returns in microseconds instead of about one second."
                }
              ],
              "value": "* Define a field module with use Cloak.Ecto.PBKDF2 and configure only a :secret, so that the defaults iterations: 600_000 and size: 32 apply.\n* Call dump/1 on a value.\n* Compare the result with :pbkdf2.pbkdf2({:hmac, :sha256}, value, secret, 32) and with the same call at 600,000 rounds. It matches the 32-round hash, not the 600,000-round hash, and returns in microseconds instead of about one second."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-94206",
        "datePublished": "2026-10-06T08:38:04.584Z",
        "dateReserved": "2026-09-24T09:30:02.102Z",
        "dateUpdated": "2026-10-06T11:36:25.466Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-95105 (GCVE-0-2026-95105)

    Vulnerability from cvelistv5 – Published: 2026-10-06 08:37 – Updated: 2026-10-06 11:40
    VLAI
    Title
    Cloak AES-CTR cipher lacks ciphertext authentication, allowing chosen-plaintext forgery by bit flipping
    Summary
    Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking vulnerability in danielberkompas cloak allows an attacker with write access to stored ciphertext to make it decrypt to a chosen value via bit flipping. Cloak.Ciphers.AES.CTR encrypts with AES-256 in CTR mode and stores the key tag, the IV and the ciphertext with no MAC. decrypt/2 checks only the key tag and the minimum length before it returns the plaintext, and Cloak.Ciphers.Deprecated.AES.CTR decrypts the legacy format the same way. CTR is a stream cipher, so a value XORed into the stored ciphertext is XORed into the plaintext at the same offset. An attacker who can write to the encrypted store (for example through SQL injection or a compromised replica) and who knows or can guess a stored plaintext can replace it with any value of the same length. The application receives that value with no error. This issue affects cloak: from 0.1.0-pre onward.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-06 11:37 UTC
    CWE
    • CWE-649 - Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    danielberkompas cloak Affected: 0.1.0-pre , < * (semver)
        cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*
    Create a notification for this product.
    danielberkompas cloak Affected: 2bd17019e285b55c5c218cc842537bf9280f24c3 , < * (git)
        cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-95105",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-06T11:37:58.212117Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-06T11:40:42.775Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://cna.erlef.org/cves/CVE-2026-95105.html"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Cloak.Ciphers.AES.CTR\u0027",
                "\u0027Elixir.Cloak.Ciphers.Deprecated.AES.CTR\u0027"
              ],
              "packageName": "cloak",
              "packageURL": "pkg:hex/cloak",
              "product": "cloak",
              "programFiles": [
                "lib/cloak/ciphers/aes_ctr.ex",
                "lib/cloak/ciphers/deprecated/aes_ctr.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Cloak.Ciphers.AES.CTR\u0027:encrypt/2"
                },
                {
                  "name": "\u0027Elixir.Cloak.Ciphers.AES.CTR\u0027:decrypt/2"
                },
                {
                  "name": "\u0027Elixir.Cloak.Ciphers.Deprecated.AES.CTR\u0027:decrypt/2"
                }
              ],
              "repo": "https://github.com/danielberkompas/cloak",
              "vendor": "danielberkompas",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "0.1.0-pre",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Cloak.Ciphers.AES.CTR\u0027",
                "\u0027Elixir.Cloak.Ciphers.Deprecated.AES.CTR\u0027"
              ],
              "packageName": "danielberkompas/cloak",
              "packageURL": "pkg:github/danielberkompas/cloak",
              "product": "cloak",
              "programFiles": [
                "lib/cloak/ciphers/aes_ctr.ex",
                "lib/cloak/ciphers/deprecated/aes_ctr.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Cloak.Ciphers.AES.CTR\u0027:encrypt/2"
                },
                {
                  "name": "\u0027Elixir.Cloak.Ciphers.AES.CTR\u0027:decrypt/2"
                },
                {
                  "name": "\u0027Elixir.Cloak.Ciphers.Deprecated.AES.CTR\u0027:decrypt/2"
                }
              ],
              "repo": "https://github.com/danielberkompas/cloak",
              "vendor": "danielberkompas",
              "versions": [
                {
                  "lessThan": "*",
                  "status": "affected",
                  "version": "2bd17019e285b55c5c218cc842537bf9280f24c3",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:danielberkompas:cloak:*:*:*:*:*:*:*:*",
                      "versionStartIncluding": "0.1.0-pre",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Peter Ullrich"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-06T08:37:34.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking vulnerability in danielberkompas cloak allows an attacker with write access to stored ciphertext to make it decrypt to a chosen value via bit flipping.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eCloak.Ciphers.AES.CTR\u003c/code\u003e encrypts with AES-256 in CTR mode and stores the key tag, the IV and the ciphertext with no MAC. \u003ccode\u003edecrypt/2\u003c/code\u003e checks only the key tag and the minimum length before it returns the plaintext, and \u003ccode\u003eCloak.Ciphers.Deprecated.AES.CTR\u003c/code\u003e decrypts the legacy format the same way. CTR is a stream cipher, so a value XORed into the stored ciphertext is XORed into the plaintext at the same offset. An attacker who can write to the encrypted store (for example through SQL injection or a compromised replica) and who knows or can guess a stored plaintext can replace it with any value of the same length. The application receives that value with no error.\u003c/p\u003e\n\u003cp\u003eThis issue affects cloak: from 0.1.0-pre onward.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking vulnerability in danielberkompas cloak allows an attacker with write access to stored ciphertext to make it decrypt to a chosen value via bit flipping.\n\n`Cloak.Ciphers.AES.CTR` encrypts with AES-256 in CTR mode and stores the key tag, the IV and the ciphertext with no MAC. `decrypt/2` checks only the key tag and the minimum length before it returns the plaintext, and `Cloak.Ciphers.Deprecated.AES.CTR` decrypts the legacy format the same way. CTR is a stream cipher, so a value XORed into the stored ciphertext is XORed into the plaintext at the same offset. An attacker who can write to the encrypted store (for example through SQL injection or a compromised replica) and who knows or can guess a stored plaintext can replace it with any value of the same length. The application receives that value with no error.\n\nThis issue affects cloak: from 0.1.0-pre onward."
                }
              ],
              "value": "Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking vulnerability in danielberkompas cloak allows an attacker with write access to stored ciphertext to make it decrypt to a chosen value via bit flipping.\n\nCloak.Ciphers.AES.CTR encrypts with AES-256 in CTR mode and stores the key tag, the IV and the ciphertext with no MAC. decrypt/2 checks only the key tag and the minimum length before it returns the plaintext, and Cloak.Ciphers.Deprecated.AES.CTR decrypts the legacy format the same way. CTR is a stream cipher, so a value XORed into the stored ciphertext is XORed into the plaintext at the same offset. An attacker who can write to the encrypted store (for example through SQL injection or a compromised replica) and who knows or can guess a stored plaintext can replace it with any value of the same length. The application receives that value with no error.\n\nThis issue affects cloak: from 0.1.0-pre onward."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-39",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eAn attacker who can write to the encrypted store can set encrypted fields with known or guessable contents, such as roles, flags, or fixed-format records, to values of their choice without the key. The application decrypts and trusts the forged value.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "An attacker who can write to the encrypted store can set encrypted fields with known or guessable contents, such as roles, flags, or fixed-format records, to values of their choice without the key. The application decrypts and trusts the forged value."
                    }
                  ],
                  "value": "An attacker who can write to the encrypted store can set encrypted fields with known or guessable contents, such as roles, flags, or fixed-format records, to values of their choice without the key. The application decrypts and trusts the forged value."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 8.2,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "HIGH",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-649",
                  "description": "CWE-649 Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-06T08:37:56.987Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "EEF CNA record for CVE-2026-95105",
              "tags": [
                "related",
                "third-party-advisory"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-95105.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-95105",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-95105"
            },
            {
              "name": "Introducing commit 2bd1701 in danielberkompas/cloak",
              "tags": [
                "related"
              ],
              "url": "https://github.com/danielberkompas/cloak/commit/2bd17019e285b55c5c218cc842537bf9280f24c3"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Cloak AES-CTR cipher lacks ciphertext authentication, allowing chosen-plaintext forgery by bit flipping",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eConfigure the vault with \u003ccode\u003eCloak.Ciphers.AES.GCM\u003c/code\u003e as the default cipher and re-encrypt existing values, for example with the \u003ccode\u003ecloak.migrate.ecto\u003c/code\u003e task from cloak_ecto. Then remove \u003ccode\u003eCloak.Ciphers.AES.CTR\u003c/code\u003e and \u003ccode\u003eCloak.Ciphers.Deprecated.AES.CTR\u003c/code\u003e from the vault configuration, so that ciphertext in the CTR format is no longer decrypted. AES-GCM authenticates the ciphertext and rejects modified values.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Configure the vault with `Cloak.Ciphers.AES.GCM` as the default cipher and re-encrypt existing values, for example with the `cloak.migrate.ecto` task from cloak_ecto. Then remove `Cloak.Ciphers.AES.CTR` and `Cloak.Ciphers.Deprecated.AES.CTR` from the vault configuration, so that ciphertext in the CTR format is no longer decrypted. AES-GCM authenticates the ciphertext and rejects modified values."
                }
              ],
              "value": "Configure the vault with Cloak.Ciphers.AES.GCM as the default cipher and re-encrypt existing values, for example with the cloak.migrate.ecto task from cloak_ecto. Then remove Cloak.Ciphers.AES.CTR and Cloak.Ciphers.Deprecated.AES.CTR from the vault configuration, so that ciphertext in the CTR format is no longer decrypted. AES-GCM authenticates the ciphertext and rejects modified values."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eEncrypt \u003ccode\u003e\"role=user \"\u003c/code\u003e with \u003ccode\u003eCloak.Ciphers.AES.CTR.encrypt/2\u003c/code\u003e and a random 32-byte key.\u003c/li\u003e\n\u003cli\u003eWithout the key, XOR the last 10 bytes of the result (the ciphertext body) with \u003ccode\u003e:crypto.exor(\"role=user \", \"role=admin\")\u003c/code\u003e. Leave the key tag and the IV unchanged.\u003c/li\u003e\n\u003cli\u003eCall \u003ccode\u003eCloak.Ciphers.AES.CTR.decrypt/2\u003c/code\u003e on the modified value. It returns \u003ccode\u003e{:ok, \"role=admin\"}\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Encrypt `\"role=user \"` with `Cloak.Ciphers.AES.CTR.encrypt/2` and a random 32-byte key.\n2. Without the key, XOR the last 10 bytes of the result (the ciphertext body) with `:crypto.exor(\"role=user \", \"role=admin\")`. Leave the key tag and the IV unchanged.\n3. Call `Cloak.Ciphers.AES.CTR.decrypt/2` on the modified value. It returns `{:ok, \"role=admin\"}`."
                }
              ],
              "value": "* Encrypt \"role=user \" with Cloak.Ciphers.AES.CTR.encrypt/2 and a random 32-byte key.\n* Without the key, XOR the last 10 bytes of the result (the ciphertext body) with :crypto.exor(\"role=user \", \"role=admin\"). Leave the key tag and the IV unchanged.\n* Call Cloak.Ciphers.AES.CTR.decrypt/2 on the modified value. It returns {:ok, \"role=admin\"}."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-95105",
        "datePublished": "2026-10-06T08:37:56.987Z",
        "dateReserved": "2026-09-25T07:00:01.880Z",
        "dateUpdated": "2026-10-06T11:40:42.775Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-94201 (GCVE-0-2026-94201)

    Vulnerability from cvelistv5 – Published: 2026-10-05 19:19 – Updated: 2026-10-06 13:41
    VLAI
    Title
    Filtering an :atom attribute with unsafe_to_atom? can exhaust the BEAM atom table in Ash
    Summary
    Ash stores :atom-typed attributes as strings and compares them as strings. When such an attribute is referenced in a filter, the comparison value is coerced through Ash.Type.Atom. Because the type defined no coerce/2 callback, coercion fell back to the default (cast_input/2), which calls String.to_atom/1 when the attribute is configured with the unsafe_to_atom?: true constraint. Filtering such an attribute with attacker-controlled strings therefore interned a new, permanent atom for every distinct value. Atoms are never garbage collected and the BEAM caps the atom table, so an actor who can supply filter values for a public, filterable :atom attribute declared with unsafe_to_atom?: true can exhaust the atom table and crash the node (denial of service). AshPaperTrail is a notable example: its version resources expose a public, filterable version_action_name atom attribute with unsafe_to_atom?: true by default. The fix adds a coerce/2 to Ash.Type.Atom that never interns atoms — a comparison value is left as a string, since the type is stored and compared as a string. Setting the attribute from action input (cast_input/2, which still honors unsafe_to_atom?) is unchanged. This issue affects ash: from 3.5.1 before 3.34.3.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-10-06 13:41 UTC
    CWE
    • CWE-770 - Allocation of Resources Without Limits or Throttling
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    ash-project ash Affected: 3.5.1 , < 3.34.3 (semver)
        cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*
    Create a notification for this product.
    ash-project ash Affected: 2970ba3bd5d36d6ad04c731ac87385375d457147 , < 5282f3f513053628cdd1bf2236ea22d975d934d4 (git)
        cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-94201",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-10-06T13:41:07.117450Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-10-06T13:41:22.142Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Ash.Type.Atom\u0027"
              ],
              "packageName": "ash",
              "packageURL": "pkg:hex/ash",
              "product": "ash",
              "programFiles": [
                "lib/ash/type/atom.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Ash.Type.Atom\u0027:cast_value/2"
                }
              ],
              "repo": "https://github.com/ash-project/ash",
              "vendor": "ash-project",
              "versions": [
                {
                  "lessThan": "3.34.3",
                  "status": "affected",
                  "version": "3.5.1",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Ash.Type.Atom\u0027"
              ],
              "packageName": "ash-project/ash",
              "packageURL": "pkg:github/ash-project/ash",
              "product": "ash",
              "programFiles": [
                "lib/ash/type/atom.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Ash.Type.Atom\u0027:cast_value/2"
                }
              ],
              "repo": "https://github.com/ash-project/ash",
              "vendor": "ash-project",
              "versions": [
                {
                  "lessThan": "5282f3f513053628cdd1bf2236ea22d975d934d4",
                  "status": "affected",
                  "version": "2970ba3bd5d36d6ad04c731ac87385375d457147",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eReachable only when an application exposes a public, filterable \u003ccode\u003e:atom\u003c/code\u003e attribute declared with the \u003ccode\u003eunsafe_to_atom?: true\u003c/code\u003e constraint, and an actor can supply filter values for it (for example through AshGraphql, AshJsonApi, or \u003ccode\u003eAsh.Query.filter_input/2\u003c/code\u003e). AshPaperTrail\u0027s version resources ship such an attribute (\u003ccode\u003eversion_action_name\u003c/code\u003e) by default.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Reachable only when an application exposes a public, filterable `:atom` attribute declared with the `unsafe_to_atom?: true` constraint, and an actor can supply filter values for it (for example through AshGraphql, AshJsonApi, or `Ash.Query.filter_input/2`). AshPaperTrail\u0027s version resources ship such an attribute (`version_action_name`) by default."
                }
              ],
              "value": "Reachable only when an application exposes a public, filterable :atom attribute declared with the unsafe_to_atom?: true constraint, and an actor can supply filter values for it (for example through AshGraphql, AshJsonApi, or Ash.Query.filter_input/2). AshPaperTrail\u0027s version resources ship such an attribute (version_action_name) by default."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:ash-project:ash:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "3.34.3",
                      "versionStartIncluding": "3.5.1",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "Quinn Wilton"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Zach Daniel / Ash Project"
            },
            {
              "lang": "en",
              "type": "coordinator",
              "value": "Jonatan M\u00e4nnchen / EEF"
            }
          ],
          "dateAssigned": "2026-10-04T15:49:18.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAsh stores \u003ccode\u003e:atom\u003c/code\u003e-typed attributes as strings and compares them as strings. When such an attribute is referenced in a filter, the comparison value is coerced through \u003ccode\u003eAsh.Type.Atom\u003c/code\u003e. Because the type defined no \u003ccode\u003ecoerce/2\u003c/code\u003e callback, coercion fell back to the default (\u003ccode\u003ecast_input/2\u003c/code\u003e), which calls \u003ccode\u003eString.to_atom/1\u003c/code\u003e when the attribute is configured with the \u003ccode\u003eunsafe_to_atom?: true\u003c/code\u003e constraint.\u003c/p\u003e\n\u003cp\u003eFiltering such an attribute with attacker-controlled strings therefore interned a new, permanent atom for every distinct value. Atoms are never garbage collected and the BEAM caps the atom table, so an actor who can supply filter values for a public, filterable \u003ccode\u003e:atom\u003c/code\u003e attribute declared with \u003ccode\u003eunsafe_to_atom?: true\u003c/code\u003e can exhaust the atom table and crash the node (denial of service). AshPaperTrail is a notable example: its version resources expose a public, filterable \u003ccode\u003eversion_action_name\u003c/code\u003e atom attribute with \u003ccode\u003eunsafe_to_atom?: true\u003c/code\u003e by default.\u003c/p\u003e\n\u003cp\u003eThe fix adds a \u003ccode\u003ecoerce/2\u003c/code\u003e to \u003ccode\u003eAsh.Type.Atom\u003c/code\u003e that never interns atoms \u2014 a comparison value is left as a string, since the type is stored and compared as a string. Setting the attribute from action input (\u003ccode\u003ecast_input/2\u003c/code\u003e, which still honors \u003ccode\u003eunsafe_to_atom?\u003c/code\u003e) is unchanged.\u003c/p\u003e\n\u003cp\u003eThis issue affects ash: from 3.5.1 before 3.34.3.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Ash stores `:atom`-typed attributes as strings and compares them as strings. When such an attribute is referenced in a filter, the comparison value is coerced through `Ash.Type.Atom`. Because the type defined no `coerce/2` callback, coercion fell back to the default (`cast_input/2`), which calls `String.to_atom/1` when the attribute is configured with the `unsafe_to_atom?: true` constraint.\n\nFiltering such an attribute with attacker-controlled strings therefore interned a new, permanent atom for every distinct value. Atoms are never garbage collected and the BEAM caps the atom table, so an actor who can supply filter values for a public, filterable `:atom` attribute declared with `unsafe_to_atom?: true` can exhaust the atom table and crash the node (denial of service). AshPaperTrail is a notable example: its version resources expose a public, filterable `version_action_name` atom attribute with `unsafe_to_atom?: true` by default.\n\nThe fix adds a `coerce/2` to `Ash.Type.Atom` that never interns atoms \u2014 a comparison value is left as a string, since the type is stored and compared as a string. Setting the attribute from action input (`cast_input/2`, which still honors `unsafe_to_atom?`) is unchanged.\n\nThis issue affects ash: from 3.5.1 before 3.34.3."
                }
              ],
              "value": "Ash stores :atom-typed attributes as strings and compares them as strings. When such an attribute is referenced in a filter, the comparison value is coerced through Ash.Type.Atom. Because the type defined no coerce/2 callback, coercion fell back to the default (cast_input/2), which calls String.to_atom/1 when the attribute is configured with the unsafe_to_atom?: true constraint.\n\nFiltering such an attribute with attacker-controlled strings therefore interned a new, permanent atom for every distinct value. Atoms are never garbage collected and the BEAM caps the atom table, so an actor who can supply filter values for a public, filterable :atom attribute declared with unsafe_to_atom?: true can exhaust the atom table and crash the node (denial of service). AshPaperTrail is a notable example: its version resources expose a public, filterable version_action_name atom attribute with unsafe_to_atom?: true by default.\n\nThe fix adds a coerce/2 to Ash.Type.Atom that never interns atoms \u2014 a comparison value is left as a string, since the type is stored and compared as a string. Setting the attribute from action input (cast_input/2, which still honors unsafe_to_atom?) is unchanged.\n\nThis issue affects ash: from 3.5.1 before 3.34.3."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "value": "CAPEC-130 Excessive Allocation"
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 8.2,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-770",
                  "description": "CWE-770 Allocation of Resources Without Limits or Throttling",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-10-05T19:19:51.326Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "EEF CNA record for CVE-2026-94201",
              "tags": [
                "related",
                "third-party-advisory"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-94201.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-94201",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-94201"
            },
            {
              "name": "Introducing commit 2970ba3 in ash-project/ash",
              "tags": [
                "related"
              ],
              "url": "https://github.com/ash-project/ash/commit/2970ba3bd5d36d6ad04c731ac87385375d457147"
            },
            {
              "name": "Fix commit 5282f3f in ash-project/ash",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/ash-project/ash/commit/5282f3f513053628cdd1bf2236ea22d975d934d4"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Filtering an :atom attribute with unsafe_to_atom? can exhaust the BEAM atom table in Ash"
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-94201",
        "datePublished": "2026-10-05T19:19:51.326Z",
        "dateReserved": "2026-09-24T09:15:02.216Z",
        "dateUpdated": "2026-10-06T13:41:22.142Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-94194 (GCVE-0-2026-94194)

    Vulnerability from cvelistv5 – Published: 2026-09-28 11:15 – Updated: 2026-09-30 13:17
    VLAI
    Title
    Mint HTTP/1 client applies chunked framing when chunked is not the final transfer coding, enabling response smuggling through intermediaries
    Summary
    Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling') vulnerability in elixir-mint mint allows a malicious HTTP/1 server to desynchronize an intermediary and the Mint client on a pooled connection, poisoning the responses to subsequent requests that share the connection. message_body/1 in lib/mint/http1.ex selects chunked framing when chunked is the first coding listed in a response's Transfer-Encoding fields. RFC 9112 section 6.3 applies chunked framing only when chunked is the final coding, and otherwise reads the body until the server closes the connection. For a response such as Transfer-Encoding: chunked, gzip, an intermediary that follows the RFC treats every byte up to the close as the body, while Mint ends the body at the zero-length chunk and parses the remaining bytes as the response to the next request on the connection. Mint also keeps the connection open after an HTTP/1.0 response, final or 1xx, that carries Transfer-Encoding and Connection: keep-alive. RFC 9112 section 6.1 requires treating the framing of such a message as faulty and closing the connection after it, so bytes after its chunked body are parsed as the response to the next request in the same way. This issue affects mint: from 0.1.0 before 1.10.2.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-09-30 13:16 UTC
    CWE
    • CWE-444 - Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    elixir-mint mint Affected: 0.1.0 , < 1.10.2 (semver)
        cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*
    Create a notification for this product.
    elixir-mint mint Affected: 60089586ec7adc9fddb09f69a2f5919ba9ac7f33 , < * (git)
        cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-09-28 11:15
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-94194",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-09-30T13:16:26.592339Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-09-30T13:17:02.563Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "references": [
              {
                "tags": [
                  "exploit"
                ],
                "url": "https://github.com/elixir-mint/mint/security/advisories/GHSA-gvrc-75rc-7gj9"
              }
            ],
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mint.HTTP1\u0027"
              ],
              "packageName": "mint",
              "packageURL": "pkg:hex/mint",
              "product": "mint",
              "programFiles": [
                "lib/mint/http1.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mint.HTTP1\u0027:message_body/1"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP1\u0027:stream/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP1\u0027:recv/3"
                }
              ],
              "repo": "https://github.com/elixir-mint/mint",
              "vendor": "elixir-mint",
              "versions": [
                {
                  "lessThan": "1.10.2",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mint.HTTP1\u0027"
              ],
              "packageName": "elixir-mint/mint",
              "packageURL": "pkg:github/elixir-mint/mint",
              "product": "mint",
              "programFiles": [
                "lib/mint/http1.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mint.HTTP1\u0027:message_body/1"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP1\u0027:stream/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP1\u0027:recv/3"
                }
              ],
              "repo": "https://github.com/elixir-mint/mint",
              "vendor": "elixir-mint",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "2ec8b696b5475ecbdaa87c0098957bca339e17c0",
                      "status": "unaffected"
                    },
                    {
                      "at": "8d1bbcfa566a8c1dc23d33f40d550c28250ac7b9",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "60089586ec7adc9fddb09f69a2f5919ba9ac7f33",
                  "versionType": "git"
                }
              ]
            }
          ],
          "configurations": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eExploitation requires an HTTP/1 intermediary (proxy, load balancer, or gateway) between the Mint client and the attacker-influenced origin that frames a response whose final transfer coding is not \u003ccode\u003echunked\u003c/code\u003e as ending at connection close and forwards its \u003ccode\u003eTransfer-Encoding\u003c/code\u003e header to the client unchanged, and HTTP/1 connections between the client and the intermediary that are reused across requests. Intermediaries that reject such a header or re-frame the body do not create the disagreement. Mint clients that connect to the origin directly, or that don\u0027t reuse connections, aren\u0027t exposed to response-queue poisoning.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Exploitation requires an HTTP/1 intermediary (proxy, load balancer, or gateway) between the Mint client and the attacker-influenced origin that frames a response whose final transfer coding is not `chunked` as ending at connection close and forwards its `Transfer-Encoding` header to the client unchanged, and HTTP/1 connections between the client and the intermediary that are reused across requests. Intermediaries that reject such a header or re-frame the body do not create the disagreement. Mint clients that connect to the origin directly, or that don\u0027t reuse connections, aren\u0027t exposed to response-queue poisoning."
                }
              ],
              "value": "Exploitation requires an HTTP/1 intermediary (proxy, load balancer, or gateway) between the Mint client and the attacker-influenced origin that frames a response whose final transfer coding is not chunked as ending at connection close and forwards its Transfer-Encoding header to the client unchanged, and HTTP/1 connections between the client and the intermediary that are reused across requests. Intermediaries that reject such a header or re-frame the body do not create the disagreement. Mint clients that connect to the origin directly, or that don\u0027t reuse connections, aren\u0027t exposed to response-queue poisoning."
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.10.2",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "zx"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "zx"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            }
          ],
          "dateAssigned": "2026-09-27T11:54:52.000Z",
          "datePublic": "2026-09-28T11:15:31.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eInconsistent Interpretation of HTTP Requests (\u0027HTTP Request/Response Smuggling\u0027) vulnerability in elixir-mint mint allows a malicious HTTP/1 server to desynchronize an intermediary and the Mint client on a pooled connection, poisoning the responses to subsequent requests that share the connection.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003emessage_body/1\u003c/code\u003e in \u003ccode\u003elib/mint/http1.ex\u003c/code\u003e selects chunked framing when \u003ccode\u003echunked\u003c/code\u003e is the first coding listed in a response\u0027s \u003ccode\u003eTransfer-Encoding\u003c/code\u003e fields. RFC 9112 section 6.3 applies chunked framing only when \u003ccode\u003echunked\u003c/code\u003e is the final coding, and otherwise reads the body until the server closes the connection. For a response such as \u003ccode\u003eTransfer-Encoding: chunked, gzip\u003c/code\u003e, an intermediary that follows the RFC treats every byte up to the close as the body, while Mint ends the body at the zero-length chunk and parses the remaining bytes as the response to the next request on the connection.\u003c/p\u003e\n\u003cp\u003eMint also keeps the connection open after an HTTP/1.0 response, final or 1xx, that carries \u003ccode\u003eTransfer-Encoding\u003c/code\u003e and \u003ccode\u003eConnection: keep-alive\u003c/code\u003e. RFC 9112 section 6.1 requires treating the framing of such a message as faulty and closing the connection after it, so bytes after its chunked body are parsed as the response to the next request in the same way.\u003c/p\u003e\n\u003cp\u003eThis issue affects mint: from 0.1.0 before 1.10.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Inconsistent Interpretation of HTTP Requests (\u0027HTTP Request/Response Smuggling\u0027) vulnerability in elixir-mint mint allows a malicious HTTP/1 server to desynchronize an intermediary and the Mint client on a pooled connection, poisoning the responses to subsequent requests that share the connection.\n\n`message_body/1` in `lib/mint/http1.ex` selects chunked framing when `chunked` is the first coding listed in a response\u0027s `Transfer-Encoding` fields. RFC 9112 section 6.3 applies chunked framing only when `chunked` is the final coding, and otherwise reads the body until the server closes the connection. For a response such as `Transfer-Encoding: chunked, gzip`, an intermediary that follows the RFC treats every byte up to the close as the body, while Mint ends the body at the zero-length chunk and parses the remaining bytes as the response to the next request on the connection.\n\nMint also keeps the connection open after an HTTP/1.0 response, final or 1xx, that carries `Transfer-Encoding` and `Connection: keep-alive`. RFC 9112 section 6.1 requires treating the framing of such a message as faulty and closing the connection after it, so bytes after its chunked body are parsed as the response to the next request in the same way.\n\nThis issue affects mint: from 0.1.0 before 1.10.2."
                }
              ],
              "value": "Inconsistent Interpretation of HTTP Requests (\u0027HTTP Request/Response Smuggling\u0027) vulnerability in elixir-mint mint allows a malicious HTTP/1 server to desynchronize an intermediary and the Mint client on a pooled connection, poisoning the responses to subsequent requests that share the connection.\n\nmessage_body/1 in lib/mint/http1.ex selects chunked framing when chunked is the first coding listed in a response\u0027s Transfer-Encoding fields. RFC 9112 section 6.3 applies chunked framing only when chunked is the final coding, and otherwise reads the body until the server closes the connection. For a response such as Transfer-Encoding: chunked, gzip, an intermediary that follows the RFC treats every byte up to the close as the body, while Mint ends the body at the zero-length chunk and parses the remaining bytes as the response to the next request on the connection.\n\nMint also keeps the connection open after an HTTP/1.0 response, final or 1xx, that carries Transfer-Encoding and Connection: keep-alive. RFC 9112 section 6.1 requires treating the framing of such a message as faulty and closing the connection after it, so bytes after its chunked body are parsed as the response to the next request in the same way.\n\nThis issue affects mint: from 0.1.0 before 1.10.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-273",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA malicious or attacker-influenced HTTP/1 origin behind an intermediary that follows RFC 9112 framing can make the intermediary and the Mint client disagree on where a response ends. On a pooled keep-alive connection, bytes the origin appends to one response become the response to the next request sharing the connection, and the real responses shift to later requests.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A malicious or attacker-influenced HTTP/1 origin behind an intermediary that follows RFC 9112 framing can make the intermediary and the Mint client disagree on where a response ends. On a pooled keep-alive connection, bytes the origin appends to one response become the response to the next request sharing the connection, and the real responses shift to later requests."
                    }
                  ],
                  "value": "A malicious or attacker-influenced HTTP/1 origin behind an intermediary that follows RFC 9112 framing can make the intermediary and the Mint client disagree on where a response ends. On a pooled keep-alive connection, bytes the origin appends to one response become the response to the next request sharing the connection, and the real responses shift to later requests."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6.3,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "NONE",
                "vulnConfidentialityImpact": "LOW",
                "vulnIntegrityImpact": "LOW",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-444",
                  "description": "CWE-444 Inconsistent Interpretation of HTTP Requests (\u0027HTTP Request/Response Smuggling\u0027)",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-09-29T10:16:39.032Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/elixir-mint/mint/security/advisories/GHSA-gvrc-75rc-7gj9"
            },
            {
              "name": "EEF CNA record for CVE-2026-94194",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-94194.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-94194",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-94194"
            },
            {
              "name": "Introducing commit 6008958 in elixir-mint/mint",
              "tags": [
                "related"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/60089586ec7adc9fddb09f69a2f5919ba9ac7f33"
            },
            {
              "name": "Fix commit 2ec8b69 in elixir-mint/mint",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/2ec8b696b5475ecbdaa87c0098957bca339e17c0"
            },
            {
              "name": "Fix commit 8d1bbcf in elixir-mint/mint",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/8d1bbcfa566a8c1dc23d33f40d550c28250ac7b9"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Mint HTTP/1 client applies chunked framing when chunked is not the final transfer coding, enabling response smuggling through intermediaries",
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eStart a loopback TCP server that answers the first request with \u003ccode\u003eHTTP/1.1 200 OK\u003c/code\u003e and \u003ccode\u003eTransfer-Encoding: chunked, gzip\u003c/code\u003e, the chunked body \u003ccode\u003e5\\r\\nLEGIT\\r\\n0\\r\\n\\r\\n\u003c/code\u003e, and directly after it the bytes \u003ccode\u003eHTTP/1.1 200 OK\\r\\nContent-Length: 8\\r\\n\\r\\nSMUGGLED\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eConnect with \u003ccode\u003eMint.HTTP1\u003c/code\u003e and send a request. Mint returns \u003ccode\u003eLEGIT\u003c/code\u003e as the complete body, keeps the connection open, and holds the second response in \u003ccode\u003econn.buffer\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend a second request on the same connection and have the server answer it with a real response. Mint returns \u003ccode\u003eSMUGGLED\u003c/code\u003e as the response to the second request and keeps the real one in the buffer.\u003c/li\u003e\n\u003cli\u003ePipelining both requests before the server answers gives the same result from a single \u003ccode\u003eMint.HTTP1.stream/2\u003c/code\u003e call.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Start a loopback TCP server that answers the first request with `HTTP/1.1 200 OK` and `Transfer-Encoding: chunked, gzip`, the chunked body `5\\r\\nLEGIT\\r\\n0\\r\\n\\r\\n`, and directly after it the bytes `HTTP/1.1 200 OK\\r\\nContent-Length: 8\\r\\n\\r\\nSMUGGLED`.\n2. Connect with `Mint.HTTP1` and send a request. Mint returns `LEGIT` as the complete body, keeps the connection open, and holds the second response in `conn.buffer`.\n3. Send a second request on the same connection and have the server answer it with a real response. Mint returns `SMUGGLED` as the response to the second request and keeps the real one in the buffer.\n4. Pipelining both requests before the server answers gives the same result from a single `Mint.HTTP1.stream/2` call."
                }
              ],
              "value": "* Start a loopback TCP server that answers the first request with HTTP/1.1 200 OK and Transfer-Encoding: chunked, gzip, the chunked body 5\\r\\nLEGIT\\r\\n0\\r\\n\\r\\n, and directly after it the bytes HTTP/1.1 200 OK\\r\\nContent-Length: 8\\r\\n\\r\\nSMUGGLED.\n* Connect with Mint.HTTP1 and send a request. Mint returns LEGIT as the complete body, keeps the connection open, and holds the second response in conn.buffer.\n* Send a second request on the same connection and have the server answer it with a real response. Mint returns SMUGGLED as the response to the second request and keeps the real one in the buffer.\n* Pipelining both requests before the server answers gives the same result from a single Mint.HTTP1.stream/2 call."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Coding list.\u003c/strong\u003e \u003ccode\u003estore_header/3\u003c/code\u003e in \u003ccode\u003elib/mint/http1.ex\u003c/code\u003e tokenizes each \u003ccode\u003eTransfer-Encoding\u003c/code\u003e field with \u003ccode\u003eMint.HTTP1.Parse.transfer_encoding_header/1\u003c/code\u003e and appends the codings to \u003ccode\u003erequest.transfer_encoding\u003c/code\u003e in the order received.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Framing decision.\u003c/strong\u003e \u003ccode\u003emessage_body/1\u003c/code\u003e returns chunked framing when \u003ccode\u003eList.first(request.transfer_encoding)\u003c/code\u003e is \u003ccode\u003e\"chunked\"\u003c/code\u003e. The only other \u003ccode\u003eTransfer-Encoding\u003c/code\u003e check rejects a response that also carries \u003ccode\u003eContent-Length\u003c/code\u003e, so \u003ccode\u003echunked, gzip\u003c/code\u003e on its own is framed as chunked.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Leftover bytes.\u003c/strong\u003e \u003ccode\u003edecode_body/5\u003c/code\u003e ends the body at the zero-length chunk and its trailer section, and \u003ccode\u003enext_request/3\u003c/code\u003e keeps the rest of the data in \u003ccode\u003econn.buffer\u003c/code\u003e or decodes it immediately as the next queued response. An RFC 9112 intermediary frames the same response as ending at connection close, so bytes it forwarded as body of the first response become the response to the next request on the Mint connection, and that request\u0027s real response is left in the buffer for the one after it.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. HTTP/1.0 keep-alive.\u003c/strong\u003e \u003ccode\u003erequest_done/2\u003c/code\u003e keeps an HTTP/1.0 connection open when the response has \u003ccode\u003eConnection: keep-alive\u003c/code\u003e, without checking for \u003ccode\u003eTransfer-Encoding\u003c/code\u003e, and \u003ccode\u003edecode_body(:informational, ...)\u003c/code\u003e resets the framing state of a 1xx response and keeps parsing. An HTTP/1.0 response with \u003ccode\u003eTransfer-Encoding: chunked\u003c/code\u003e and \u003ccode\u003eConnection: keep-alive\u003c/code\u003e therefore leaves the connection open after its chunked body, and the following bytes are decoded as the next queued response, although RFC 9112 section 6.1 requires closing the connection after such a message.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Coding list.** `store_header/3` in `lib/mint/http1.ex` tokenizes each `Transfer-Encoding` field with `Mint.HTTP1.Parse.transfer_encoding_header/1` and appends the codings to `request.transfer_encoding` in the order received.\n\n**2. Framing decision.** `message_body/1` returns chunked framing when `List.first(request.transfer_encoding)` is `\"chunked\"`. The only other `Transfer-Encoding` check rejects a response that also carries `Content-Length`, so `chunked, gzip` on its own is framed as chunked.\n\n**3. Leftover bytes.** `decode_body/5` ends the body at the zero-length chunk and its trailer section, and `next_request/3` keeps the rest of the data in `conn.buffer` or decodes it immediately as the next queued response. An RFC 9112 intermediary frames the same response as ending at connection close, so bytes it forwarded as body of the first response become the response to the next request on the Mint connection, and that request\u0027s real response is left in the buffer for the one after it.\n\n**4. HTTP/1.0 keep-alive.** `request_done/2` keeps an HTTP/1.0 connection open when the response has `Connection: keep-alive`, without checking for `Transfer-Encoding`, and `decode_body(:informational, ...)` resets the framing state of a 1xx response and keeps parsing. An HTTP/1.0 response with `Transfer-Encoding: chunked` and `Connection: keep-alive` therefore leaves the connection open after its chunked body, and the following bytes are decoded as the next queued response, although RFC 9112 section 6.1 requires closing the connection after such a message."
                }
              ],
              "value": "1. Coding list. store_header/3 in lib/mint/http1.ex tokenizes each Transfer-Encoding field with Mint.HTTP1.Parse.transfer_encoding_header/1 and appends the codings to request.transfer_encoding in the order received.\n\n2. Framing decision. message_body/1 returns chunked framing when List.first(request.transfer_encoding) is \"chunked\". The only other Transfer-Encoding check rejects a response that also carries Content-Length, so chunked, gzip on its own is framed as chunked.\n\n3. Leftover bytes. decode_body/5 ends the body at the zero-length chunk and its trailer section, and next_request/3 keeps the rest of the data in conn.buffer or decodes it immediately as the next queued response. An RFC 9112 intermediary frames the same response as ending at connection close, so bytes it forwarded as body of the first response become the response to the next request on the Mint connection, and that request\u0027s real response is left in the buffer for the one after it.\n\n4. HTTP/1.0 keep-alive. request_done/2 keeps an HTTP/1.0 connection open when the response has Connection: keep-alive, without checking for Transfer-Encoding, and decode_body(:informational, ...) resets the framing state of a 1xx response and keeps parsing. An HTTP/1.0 response with Transfer-Encoding: chunked and Connection: keep-alive therefore leaves the connection open after its chunked body, and the following bytes are decoded as the next queued response, although RFC 9112 section 6.1 requires closing the connection after such a message."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-94194",
        "datePublished": "2026-09-28T11:15:30.684Z",
        "dateReserved": "2026-09-24T13:45:02.496Z",
        "dateUpdated": "2026-09-30T13:17:02.563Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-92103 (GCVE-0-2026-92103)

    Vulnerability from cvelistv5 – Published: 2026-09-28 11:15 – Updated: 2026-09-30 15:00
    VLAI
    Title
    Mint HTTP/2 client buffers oversized frames up to 16 MiB before enforcing max_frame_size
    Summary
    Allocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to make the client hold up to about 16 MiB per connection in frames it should reject, consuming client memory. Mint.HTTP2.Frame.decode_next/2 in lib/mint/http2/frame.ex compares a frame with the client's max_frame_size (16,384 bytes by default) only once the whole declared payload has arrived. Until then it returns :more, and Mint.HTTP2 keeps every received byte in the connection buffer. A server can declare a frame length of up to 16,777,215 bytes and withhold the last byte, keeping roughly 1,024 times the advertised limit buffered for as long as the connection stays open. The server has to send every byte the client buffers, so there is no amplification, and the buffer stops at the 24-bit frame length limit. This issue affects mint: from 0.1.0 before 1.10.2.
    SSVC
    Exploitation: poc Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-09-30 15:00 UTC
    CWE
    • CWE-770 - Allocation of Resources Without Limits or Throttling
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    elixir-mint mint Affected: 0.1.0 , < 1.10.2 (semver)
        cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*
    Create a notification for this product.
    elixir-mint mint Affected: 596ca4304504be68939c4929e0831557097962b8 , < * (git)
        cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-09-28 11:15
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-92103",
                    "options": [
                      {
                        "Exploitation": "poc"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-09-30T15:00:25.491334Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-09-30T15:00:38.567Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mint.HTTP2.Frame\u0027",
                "\u0027Elixir.Mint.HTTP2\u0027"
              ],
              "packageName": "mint",
              "packageURL": "pkg:hex/mint",
              "product": "mint",
              "programFiles": [
                "lib/mint/http2/frame.ex",
                "lib/mint/http2.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mint.HTTP2.Frame\u0027:decode_next/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:stream/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:recv/3"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:handle_new_data/3"
                }
              ],
              "repo": "https://github.com/elixir-mint/mint",
              "vendor": "elixir-mint",
              "versions": [
                {
                  "lessThan": "1.10.2",
                  "status": "affected",
                  "version": "0.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mint.HTTP2.Frame\u0027",
                "\u0027Elixir.Mint.HTTP2\u0027"
              ],
              "packageName": "elixir-mint/mint",
              "packageURL": "pkg:github/elixir-mint/mint",
              "product": "mint",
              "programFiles": [
                "lib/mint/http2/frame.ex",
                "lib/mint/http2.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mint.HTTP2.Frame\u0027:decode_next/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:stream/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:recv/3"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:handle_new_data/3"
                }
              ],
              "repo": "https://github.com/elixir-mint/mint",
              "vendor": "elixir-mint",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "20252ca85065f4d1092aed9ee4ed21841a507dfe",
                      "status": "unaffected"
                    },
                    {
                      "at": "44d7ce4755d0444ee415cbcca7ce4942382da4dd",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "596ca4304504be68939c4929e0831557097962b8",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.10.2",
                      "versionStartIncluding": "0.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "zx"
            },
            {
              "lang": "en",
              "type": "reporter",
              "value": "zx"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Andrea Leopardi"
            },
            {
              "lang": "en",
              "type": "remediation reviewer",
              "value": "Eric Meadows-J\u00f6nsson"
            }
          ],
          "dateAssigned": "2026-09-24T09:19:17.000Z",
          "datePublic": "2026-09-28T11:15:20.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAllocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to make the client hold up to about 16 MiB per connection in frames it should reject, consuming client memory.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMint.HTTP2.Frame.decode_next/2\u003c/code\u003e in \u003ccode\u003elib/mint/http2/frame.ex\u003c/code\u003e compares a frame with the client\u0027s \u003ccode\u003emax_frame_size\u003c/code\u003e (16,384 bytes by default) only once the whole declared payload has arrived. Until then it returns \u003ccode\u003e:more\u003c/code\u003e, and \u003ccode\u003eMint.HTTP2\u003c/code\u003e keeps every received byte in the connection buffer. A server can declare a frame length of up to 16,777,215 bytes and withhold the last byte, keeping roughly 1,024 times the advertised limit buffered for as long as the connection stays open. The server has to send every byte the client buffers, so there is no amplification, and the buffer stops at the 24-bit frame length limit.\u003c/p\u003e\n\u003cp\u003eThis issue affects mint: from 0.1.0 before 1.10.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Allocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to make the client hold up to about 16 MiB per connection in frames it should reject, consuming client memory.\n\n`Mint.HTTP2.Frame.decode_next/2` in `lib/mint/http2/frame.ex` compares a frame with the client\u0027s `max_frame_size` (16,384 bytes by default) only once the whole declared payload has arrived. Until then it returns `:more`, and `Mint.HTTP2` keeps every received byte in the connection buffer. A server can declare a frame length of up to 16,777,215 bytes and withhold the last byte, keeping roughly 1,024 times the advertised limit buffered for as long as the connection stays open. The server has to send every byte the client buffers, so there is no amplification, and the buffer stops at the 24-bit frame length limit.\n\nThis issue affects mint: from 0.1.0 before 1.10.2."
                }
              ],
              "value": "Allocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to make the client hold up to about 16 MiB per connection in frames it should reject, consuming client memory.\n\nMint.HTTP2.Frame.decode_next/2 in lib/mint/http2/frame.ex compares a frame with the client\u0027s max_frame_size (16,384 bytes by default) only once the whole declared payload has arrived. Until then it returns :more, and Mint.HTTP2 keeps every received byte in the connection buffer. A server can declare a frame length of up to 16,777,215 bytes and withhold the last byte, keeping roughly 1,024 times the advertised limit buffered for as long as the connection stays open. The server has to send every byte the client buffers, so there is no amplification, and the buffer stops at the 24-bit frame length limit.\n\nThis issue affects mint: from 0.1.0 before 1.10.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA malicious or compromised HTTP/2 server can make a Mint client keep up to about 16 MiB per connection buffered, and keep it there while the connection stays open. Clients that hold many HTTP/2 connections to attacker-influenced origins, such as webhook senders, crawlers and proxies, can run out of memory.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A malicious or compromised HTTP/2 server can make a Mint client keep up to about 16 MiB per connection buffered, and keep it there while the connection stays open. Clients that hold many HTTP/2 connections to attacker-influenced origins, such as webhook senders, crawlers and proxies, can run out of memory."
                    }
                  ],
                  "value": "A malicious or compromised HTTP/2 server can make a Mint client keep up to about 16 MiB per connection buffered, and keep it there while the connection stays open. Clients that hold many HTTP/2 connections to attacker-influenced origins, such as webhook senders, crawlers and proxies, can run out of memory."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 6.3,
                "baseSeverity": "MEDIUM",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "LOW",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-770",
                  "description": "CWE-770 Allocation of Resources Without Limits or Throttling",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-09-29T10:15:35.604Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/elixir-mint/mint/security/advisories/GHSA-q95c-ccq6-j5j6"
            },
            {
              "name": "EEF CNA record for CVE-2026-92103",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-92103.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-92103",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-92103"
            },
            {
              "name": "Introducing commit 596ca43 in elixir-mint/mint",
              "tags": [
                "related"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/596ca4304504be68939c4929e0831557097962b8"
            },
            {
              "name": "Fix commit 20252ca in elixir-mint/mint",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/20252ca85065f4d1092aed9ee4ed21841a507dfe"
            },
            {
              "name": "Fix commit 44d7ce4 in elixir-mint/mint",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/44d7ce4755d0444ee415cbcca7ce4942382da4dd"
            }
          ],
          "source": {
            "discovery": "EXTERNAL"
          },
          "title": "Mint HTTP/2 client buffers oversized frames up to 16 MiB before enforcing max_frame_size",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eConnect to untrusted origins over HTTP/1 only, with \u003ccode\u003eprotocols: [:http1]\u003c/code\u003e in \u003ccode\u003eMint.HTTP.connect/4\u003c/code\u003e, which never runs the HTTP/2 frame decoder. Finch and Req pools use HTTP/1 only unless \u003ccode\u003e:http2\u003c/code\u003e is added to their \u003ccode\u003eprotocols\u003c/code\u003e option.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Connect to untrusted origins over HTTP/1 only, with `protocols: [:http1]` in `Mint.HTTP.connect/4`, which never runs the HTTP/2 frame decoder. Finch and Req pools use HTTP/1 only unless `:http2` is added to their `protocols` option."
                }
              ],
              "value": "Connect to untrusted origins over HTTP/1 only, with protocols: [:http1] in Mint.HTTP.connect/4, which never runs the HTTP/2 frame decoder. Finch and Req pools use HTTP/1 only unless :http2 is added to their protocols option."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eBuild an HTTP/2 frame header whose 24-bit length field declares 1,000,000 bytes, above the default \u003ccode\u003emax_frame_size\u003c/code\u003e of 16,384.\u003c/li\u003e\n\u003cli\u003ePass the header followed by 16,384 and then 512,000 payload bytes to \u003ccode\u003eMint.HTTP2.Frame.decode_next/2\u003c/code\u003e with a limit of 16,384. Each call returns \u003ccode\u003e:more\u003c/code\u003e, which makes \u003ccode\u003eMint.HTTP2\u003c/code\u003e keep the bytes in \u003ccode\u003econn.buffer\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ePass the header with the complete 1,000,000-byte payload. Only this call returns \u003ccode\u003e{:error, :payload_too_big}\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Build an HTTP/2 frame header whose 24-bit length field declares 1,000,000 bytes, above the default `max_frame_size` of 16,384.\n2. Pass the header followed by 16,384 and then 512,000 payload bytes to `Mint.HTTP2.Frame.decode_next/2` with a limit of 16,384. Each call returns `:more`, which makes `Mint.HTTP2` keep the bytes in `conn.buffer`.\n3. Pass the header with the complete 1,000,000-byte payload. Only this call returns `{:error, :payload_too_big}`."
                }
              ],
              "value": "* Build an HTTP/2 frame header whose 24-bit length field declares 1,000,000 bytes, above the default max_frame_size of 16,384.\n* Pass the header followed by 16,384 and then 512,000 payload bytes to Mint.HTTP2.Frame.decode_next/2 with a limit of 16,384. Each call returns :more, which makes Mint.HTTP2 keep the bytes in conn.buffer.\n* Pass the header with the complete 1,000,000-byte payload. Only this call returns {:error, :payload_too_big}."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Late size check.\u003c/strong\u003e \u003ccode\u003eMint.HTTP2.Frame.decode_next/2\u003c/code\u003e calls \u003ccode\u003edecode_next_raw/1\u003c/code\u003e, whose binary pattern only matches once the full declared payload is present. The \u003ccode\u003emax_frame_size\u003c/code\u003e guard runs on the matched payload, so a partial frame of any declared length returns \u003ccode\u003e:more\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unbounded buffering.\u003c/strong\u003e On \u003ccode\u003e:more\u003c/code\u003e, \u003ccode\u003eMint.HTTP2.handle_new_data/3\u003c/code\u003e stores the accumulated data in \u003ccode\u003econn.buffer\u003c/code\u003e, and \u003ccode\u003emaybe_concat_and_handle_new_data/2\u003c/code\u003e prepends it to every later socket read, in both \u003ccode\u003estream/2\u003c/code\u003e (active mode) and \u003ccode\u003erecv/3\u003c/code\u003e (passive mode). Nothing caps the buffer.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. No release.\u003c/strong\u003e Mint has no idle timer. In active mode the buffer is held until the caller closes the connection. In passive mode a \u003ccode\u003erecv/3\u003c/code\u003e timeout closes it, but a server that sends a byte within each timeout keeps it open. \u003ccode\u003emax_frame_size\u003c/code\u003e can\u0027t be set below the 16,384-byte protocol minimum, and no setting moves the check earlier.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Late size check.** `Mint.HTTP2.Frame.decode_next/2` calls `decode_next_raw/1`, whose binary pattern only matches once the full declared payload is present. The `max_frame_size` guard runs on the matched payload, so a partial frame of any declared length returns `:more`.\n\n**2. Unbounded buffering.** On `:more`, `Mint.HTTP2.handle_new_data/3` stores the accumulated data in `conn.buffer`, and `maybe_concat_and_handle_new_data/2` prepends it to every later socket read, in both `stream/2` (active mode) and `recv/3` (passive mode). Nothing caps the buffer.\n\n**3. No release.** Mint has no idle timer. In active mode the buffer is held until the caller closes the connection. In passive mode a `recv/3` timeout closes it, but a server that sends a byte within each timeout keeps it open. `max_frame_size` can\u0027t be set below the 16,384-byte protocol minimum, and no setting moves the check earlier."
                }
              ],
              "value": "1. Late size check. Mint.HTTP2.Frame.decode_next/2 calls decode_next_raw/1, whose binary pattern only matches once the full declared payload is present. The max_frame_size guard runs on the matched payload, so a partial frame of any declared length returns :more.\n\n2. Unbounded buffering. On :more, Mint.HTTP2.handle_new_data/3 stores the accumulated data in conn.buffer, and maybe_concat_and_handle_new_data/2 prepends it to every later socket read, in both stream/2 (active mode) and recv/3 (passive mode). Nothing caps the buffer.\n\n3. No release. Mint has no idle timer. In active mode the buffer is held until the caller closes the connection. In passive mode a recv/3 timeout closes it, but a server that sends a byte within each timeout keeps it open. max_frame_size can\u0027t be set below the 16,384-byte protocol minimum, and no setting moves the check earlier."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-92103",
        "datePublished": "2026-09-28T11:15:20.657Z",
        "dateReserved": "2026-09-15T16:00:01.513Z",
        "dateUpdated": "2026-09-30T15:00:38.567Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }

    CVE-2026-91043 (GCVE-0-2026-91043)

    Vulnerability from cvelistv5 – Published: 2026-09-28 11:15 – Updated: 2026-09-29 10:15
    VLAI
    Title
    HPACK-indexed cookie fields in Mint HTTP/2 responses bypass max_header_list_size and exhaust client memory
    Summary
    Allocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to exhaust memory on the client host and cause a denial of service. Mint.HTTP2 enforces the client's max_header_list_size setting only on the compressed size of an inbound header block, while RFC 9113 section 6.5.2 defines the limit on the decoded header list. An HPACK indexed field costs one byte on the wire and decodes to a dynamic table entry of up to 4 KB, and join_cookie_headers/1 in lib/mint/http2.ex copies every cookie value of a response into one new binary. A header block under the default 256 KB wire limit therefore makes the client allocate about 1 GB for a single response, and several such responses in one delivery exhaust the memory of the process that owns the connection or of the whole VM. This issue affects mint: from 1.1.0 before 1.10.2.
    SSVC
    Exploitation: none Automatable: no Technical Impact: partial
    CISA Coordinator · CISA-ADP (v2.0.3)
    Decision recorded 2026-09-28 13:20 UTC
    CWE
    • CWE-770 - Allocation of Resources Without Limits or Throttling
    Assigner
    EEF CNA under the mitre root
    CNA scorecard A 96/100 over 316 records in the last 180 days details
    Impacted products
    Vendor Product Version
    elixir-mint mint Affected: 1.1.0 , < 1.10.2 (semver)
        cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*
    Create a notification for this product.
    elixir-mint mint Affected: 8e0e04680476f90f9f68db4dfeabcbe66dabfc4d , < * (git)
        cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*
    Create a notification for this product.
    Date Public
    2026-09-28 11:15
    Show details on NVD website

    {
      "containers": {
        "adp": [
          {
            "metrics": [
              {
                "other": {
                  "content": {
                    "id": "CVE-2026-91043",
                    "options": [
                      {
                        "Exploitation": "none"
                      },
                      {
                        "Automatable": "no"
                      },
                      {
                        "Technical Impact": "partial"
                      }
                    ],
                    "role": "CISA Coordinator",
                    "timestamp": "2026-09-28T13:20:39.397915Z",
                    "version": "2.0.3"
                  },
                  "type": "ssvc"
                }
              }
            ],
            "providerMetadata": {
              "dateUpdated": "2026-09-28T13:31:29.037Z",
              "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
              "shortName": "CISA-ADP"
            },
            "title": "CISA ADP Vulnrichment"
          }
        ],
        "cna": {
          "affected": [
            {
              "collectionURL": "https://repo.hex.pm",
              "cpes": [
                "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mint.HTTP2\u0027"
              ],
              "packageName": "mint",
              "packageURL": "pkg:hex/mint",
              "product": "mint",
              "programFiles": [
                "lib/mint/http2.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:stream/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:decode_hbf_and_add_responses/5"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:join_cookie_headers/1"
                }
              ],
              "repo": "https://github.com/elixir-mint/mint",
              "vendor": "elixir-mint",
              "versions": [
                {
                  "lessThan": "1.10.2",
                  "status": "affected",
                  "version": "1.1.0",
                  "versionType": "semver"
                }
              ]
            },
            {
              "collectionURL": "https://github.com",
              "cpes": [
                "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*"
              ],
              "defaultStatus": "unaffected",
              "modules": [
                "\u0027Elixir.Mint.HTTP2\u0027"
              ],
              "packageName": "elixir-mint/mint",
              "packageURL": "pkg:github/elixir-mint/mint",
              "product": "mint",
              "programFiles": [
                "lib/mint/http2.ex"
              ],
              "programRoutines": [
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:stream/2"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:decode_hbf_and_add_responses/5"
                },
                {
                  "name": "\u0027Elixir.Mint.HTTP2\u0027:join_cookie_headers/1"
                }
              ],
              "repo": "https://github.com/elixir-mint/mint",
              "vendor": "elixir-mint",
              "versions": [
                {
                  "changes": [
                    {
                      "at": "c7895cb022196c77ec35570c8e873a84393ff4b8",
                      "status": "unaffected"
                    },
                    {
                      "at": "65a0d61c9eab6b0dedac809de7212515bf506250",
                      "status": "unaffected"
                    }
                  ],
                  "lessThan": "*",
                  "status": "affected",
                  "version": "8e0e04680476f90f9f68db4dfeabcbe66dabfc4d",
                  "versionType": "git"
                }
              ]
            }
          ],
          "cpeApplicability": [
            {
              "nodes": [
                {
                  "cpeMatch": [
                    {
                      "criteria": "cpe:2.3:a:elixir-mint:mint:*:*:*:*:*:*:*:*",
                      "versionEndExcluding": "1.10.2",
                      "versionStartIncluding": "1.1.0",
                      "vulnerable": true
                    }
                  ],
                  "negate": false,
                  "operator": "OR"
                }
              ],
              "operator": "AND"
            }
          ],
          "credits": [
            {
              "lang": "en",
              "type": "finder",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "remediation developer",
              "value": "Eric Meadows-J\u00f6nsson"
            },
            {
              "lang": "en",
              "type": "remediation reviewer",
              "value": "Andrea Leopardi"
            }
          ],
          "dateAssigned": "2026-09-21T12:06:31.000Z",
          "datePublic": "2026-09-28T11:15:09.000Z",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eAllocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to exhaust memory on the client host and cause a denial of service.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMint.HTTP2\u003c/code\u003e enforces the client\u0027s \u003ccode\u003emax_header_list_size\u003c/code\u003e setting only on the compressed size of an inbound header block, while RFC 9113 section 6.5.2 defines the limit on the decoded header list. An HPACK indexed field costs one byte on the wire and decodes to a dynamic table entry of up to 4 KB, and \u003ccode\u003ejoin_cookie_headers/1\u003c/code\u003e in \u003ccode\u003elib/mint/http2.ex\u003c/code\u003e copies every \u003ccode\u003ecookie\u003c/code\u003e value of a response into one new binary. A header block under the default 256 KB wire limit therefore makes the client allocate about 1 GB for a single response, and several such responses in one delivery exhaust the memory of the process that owns the connection or of the whole VM.\u003c/p\u003e\n\u003cp\u003eThis issue affects mint: from 1.1.0 before 1.10.2.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Allocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to exhaust memory on the client host and cause a denial of service.\n\n`Mint.HTTP2` enforces the client\u0027s `max_header_list_size` setting only on the compressed size of an inbound header block, while RFC 9113 section 6.5.2 defines the limit on the decoded header list. An HPACK indexed field costs one byte on the wire and decodes to a dynamic table entry of up to 4 KB, and `join_cookie_headers/1` in `lib/mint/http2.ex` copies every `cookie` value of a response into one new binary. A header block under the default 256 KB wire limit therefore makes the client allocate about 1 GB for a single response, and several such responses in one delivery exhaust the memory of the process that owns the connection or of the whole VM.\n\nThis issue affects mint: from 1.1.0 before 1.10.2."
                }
              ],
              "value": "Allocation of Resources Without Limits or Throttling vulnerability in elixir-mint mint allows a malicious HTTP/2 server to exhaust memory on the client host and cause a denial of service.\n\nMint.HTTP2 enforces the client\u0027s max_header_list_size setting only on the compressed size of an inbound header block, while RFC 9113 section 6.5.2 defines the limit on the decoded header list. An HPACK indexed field costs one byte on the wire and decodes to a dynamic table entry of up to 4 KB, and join_cookie_headers/1 in lib/mint/http2.ex copies every cookie value of a response into one new binary. A header block under the default 256 KB wire limit therefore makes the client allocate about 1 GB for a single response, and several such responses in one delivery exhaust the memory of the process that owns the connection or of the whole VM.\n\nThis issue affects mint: from 1.1.0 before 1.10.2."
            }
          ],
          "impacts": [
            {
              "capecId": "CAPEC-130",
              "descriptions": [
                {
                  "lang": "en",
                  "supportingMedia": [
                    {
                      "base64": false,
                      "type": "text/html",
                      "value": "\u003cp\u003eA malicious or compromised HTTP/2 server can make a Mint client allocate about 1 GB per response with default settings, exhausting host memory until the operating system kills the BEAM. HTTP/2 connection pools in Finch and Req, webhook and scraper clients, and reverse proxies fetching from untrusted upstreams are affected without any opt-in.\u003c/p\u003e"
                    },
                    {
                      "base64": false,
                      "type": "text/markdown",
                      "value": "A malicious or compromised HTTP/2 server can make a Mint client allocate about 1 GB per response with default settings, exhausting host memory until the operating system kills the BEAM. HTTP/2 connection pools in Finch and Req, webhook and scraper clients, and reverse proxies fetching from untrusted upstreams are affected without any opt-in."
                    }
                  ],
                  "value": "A malicious or compromised HTTP/2 server can make a Mint client allocate about 1 GB per response with default settings, exhausting host memory until the operating system kills the BEAM. HTTP/2 connection pools in Finch and Req, webhook and scraper clients, and reverse proxies fetching from untrusted upstreams are affected without any opt-in."
                }
              ]
            }
          ],
          "metrics": [
            {
              "cvssV4_0": {
                "Automatable": "NOT_DEFINED",
                "Recovery": "NOT_DEFINED",
                "Safety": "NOT_DEFINED",
                "attackComplexity": "LOW",
                "attackRequirements": "PRESENT",
                "attackVector": "NETWORK",
                "baseScore": 8.2,
                "baseSeverity": "HIGH",
                "privilegesRequired": "NONE",
                "providerUrgency": "NOT_DEFINED",
                "subAvailabilityImpact": "NONE",
                "subConfidentialityImpact": "NONE",
                "subIntegrityImpact": "NONE",
                "userInteraction": "NONE",
                "valueDensity": "NOT_DEFINED",
                "vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
                "version": "4.0",
                "vulnAvailabilityImpact": "HIGH",
                "vulnConfidentialityImpact": "NONE",
                "vulnIntegrityImpact": "NONE",
                "vulnerabilityResponseEffort": "NOT_DEFINED"
              },
              "format": "CVSS",
              "scenarios": [
                {
                  "lang": "en",
                  "value": "GENERAL"
                }
              ]
            }
          ],
          "problemTypes": [
            {
              "descriptions": [
                {
                  "cweId": "CWE-770",
                  "description": "CWE-770 Allocation of Resources Without Limits or Throttling",
                  "lang": "en",
                  "type": "CWE"
                }
              ]
            }
          ],
          "providerMetadata": {
            "dateUpdated": "2026-09-29T10:15:15.619Z",
            "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
            "shortName": "EEF"
          },
          "references": [
            {
              "name": "GitHub Advisory",
              "tags": [
                "related",
                "vendor-advisory"
              ],
              "url": "https://github.com/elixir-mint/mint/security/advisories/GHSA-9x8p-qrf4-jq7g"
            },
            {
              "name": "EEF CNA record for CVE-2026-91043",
              "tags": [
                "related"
              ],
              "url": "https://cna.erlef.org/cves/CVE-2026-91043.html"
            },
            {
              "name": "OSV record EEF-CVE-2026-91043",
              "tags": [
                "related"
              ],
              "url": "https://osv.dev/vulnerability/EEF-CVE-2026-91043"
            },
            {
              "name": "Introducing commit 8e0e046 in elixir-mint/mint",
              "tags": [
                "related"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/8e0e04680476f90f9f68db4dfeabcbe66dabfc4d"
            },
            {
              "name": "Fix commit c7895cb in elixir-mint/mint",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/c7895cb022196c77ec35570c8e873a84393ff4b8"
            },
            {
              "name": "Fix commit 65a0d61 in elixir-mint/mint",
              "tags": [
                "patch"
              ],
              "url": "https://github.com/elixir-mint/mint/commit/65a0d61c9eab6b0dedac809de7212515bf506250"
            }
          ],
          "source": {
            "discovery": "INTERNAL"
          },
          "title": "HPACK-indexed cookie fields in Mint HTTP/2 responses bypass max_header_list_size and exhaust client memory",
          "workarounds": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eLower \u003ccode\u003eclient_settings: [max_header_list_size: n]\u003c/code\u003e in \u003ccode\u003eMint.HTTP.connect/4\u003c/code\u003e. The compressed check still applies, and each byte of the block can expand to at most one 4 KB table entry, so a limit of 4,096 bounds a response to roughly 16 MB. Alternatively, restrict connections to untrusted servers to HTTP/1 with \u003ccode\u003eprotocols: [:http1]\u003c/code\u003e, which avoids the HTTP/2 receive path entirely. Do not set \u003ccode\u003emax_header_list_size: :infinity\u003c/code\u003e, which disables the compressed check as well.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "Lower `client_settings: [max_header_list_size: n]` in `Mint.HTTP.connect/4`. The compressed check still applies, and each byte of the block can expand to at most one 4 KB table entry, so a limit of 4,096 bounds a response to roughly 16 MB. Alternatively, restrict connections to untrusted servers to HTTP/1 with `protocols: [:http1]`, which avoids the HTTP/2 receive path entirely. Do not set `max_header_list_size: :infinity`, which disables the compressed check as well."
                }
              ],
              "value": "Lower client_settings: [max_header_list_size: n] in Mint.HTTP.connect/4. The compressed check still applies, and each byte of the block can expand to at most one 4 KB table entry, so a limit of 4,096 bounds a response to roughly 16 MB. Alternatively, restrict connections to untrusted servers to HTTP/1 with protocols: [:http1], which avoids the HTTP/2 receive path entirely. Do not set max_header_list_size: :infinity, which disables the compressed check as well."
            }
          ],
          "x_proofOfConcept": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003col\u003e\n\u003cli\u003eConnect a \u003ccode\u003eMint.HTTP2\u003c/code\u003e client with default \u003ccode\u003eclient_settings\u003c/code\u003e to a server that writes raw HTTP/2 frames, and open a request.\u003c/li\u003e\n\u003cli\u003eAnswer with a header block that stores one large \u003ccode\u003ecookie\u003c/code\u003e value in the HPACK dynamic table and then references it many times with indexed fields, keeping the compressed block under the 262,144-byte limit.\u003c/li\u003e\n\u003cli\u003eMint accepts the block and returns one \u003ccode\u003ecookie\u003c/code\u003e header thousands of times larger than the wire bytes; the reporter measured a 1,047,808,858-byte value from 262,296 wire bytes on mint 1.10.1.\u003c/li\u003e\n\u003cli\u003eAnswering several open requests in one delivery adds up: four responses held 4,240,710,592 bytes of cookie values at once.\u003c/li\u003e\n\u003cli\u003eA block of 60,000 references costs about 240 MB under the \u003ccode\u003ecookie\u003c/code\u003e field name and about 5 MB under a non-cookie name, which isolates \u003ccode\u003ejoin_cookie_headers/1\u003c/code\u003e as the amplifier.\u003c/li\u003e\n\u003c/ol\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "1. Connect a `Mint.HTTP2` client with default `client_settings` to a server that writes raw HTTP/2 frames, and open a request.\n2. Answer with a header block that stores one large `cookie` value in the HPACK dynamic table and then references it many times with indexed fields, keeping the compressed block under the 262,144-byte limit.\n3. Mint accepts the block and returns one `cookie` header thousands of times larger than the wire bytes; the reporter measured a 1,047,808,858-byte value from 262,296 wire bytes on mint 1.10.1.\n4. Answering several open requests in one delivery adds up: four responses held 4,240,710,592 bytes of cookie values at once.\n5. A block of 60,000 references costs about 240 MB under the `cookie` field name and about 5 MB under a non-cookie name, which isolates `join_cookie_headers/1` as the amplifier."
                }
              ],
              "value": "* Connect a Mint.HTTP2 client with default client_settings to a server that writes raw HTTP/2 frames, and open a request.\n* Answer with a header block that stores one large cookie value in the HPACK dynamic table and then references it many times with indexed fields, keeping the compressed block under the 262,144-byte limit.\n* Mint accepts the block and returns one cookie header thousands of times larger than the wire bytes; the reporter measured a 1,047,808,858-byte value from 262,296 wire bytes on mint 1.10.1.\n* Answering several open requests in one delivery adds up: four responses held 4,240,710,592 bytes of cookie values at once.\n* A block of 60,000 references costs about 240 MB under the cookie field name and about 5 MB under a non-cookie name, which isolates join_cookie_headers/1 as the amplifier."
            }
          ],
          "x_technicalAnalysis": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003e\u003cstrong\u003e1. Compressed-size check only.\u003c/strong\u003e \u003ccode\u003eMint.HTTP2.assert_header_block_within_max_size/2\u003c/code\u003e compares the accumulated \u003ccode\u003eHEADERS\u003c/code\u003e plus \u003ccode\u003eCONTINUATION\u003c/code\u003e bytes with \u003ccode\u003eclient_settings.max_header_list_size\u003c/code\u003e, which bounds a \u003ccode\u003eCONTINUATION\u003c/code\u003e chain but not the decoded size. After \u003ccode\u003eHPAX.decode/2\u003c/code\u003e returns, \u003ccode\u003edecode_hbf_and_add_responses/5\u003c/code\u003e passes the header list to \u003ccode\u003ehandle_decoded_headers_for_stream/5\u003c/code\u003e without measuring it. The only implementation of the RFC 9113 section 6.5.2 formula, \u003ccode\u003eassert_headers_smaller_than_max_header_list_size/2\u003c/code\u003e, applies the server\u0027s setting to headers Mint sends.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Indexed-field expansion.\u003c/strong\u003e An HPACK indexed field (RFC 7541 section 6.1) is one byte for small table indexes and refers to a dynamic table entry of up to 4 KB. HPAX returns one shared binary per reference, so the decoded list itself stays small.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Cookie join.\u003c/strong\u003e \u003ccode\u003ejoin_cookie_headers/1\u003c/code\u003e runs \u003ccode\u003eEnum.map_join/3\u003c/code\u003e over every \u003ccode\u003ecookie\u003c/code\u003e value, copying each shared value into one contiguous binary. The dynamic table entry persists for the connection, so the same block can answer every open stream, and \u003ccode\u003eMint.HTTP.stream/2\u003c/code\u003e returns all responses parsed from one delivery together.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "**1. Compressed-size check only.** `Mint.HTTP2.assert_header_block_within_max_size/2` compares the accumulated `HEADERS` plus `CONTINUATION` bytes with `client_settings.max_header_list_size`, which bounds a `CONTINUATION` chain but not the decoded size. After `HPAX.decode/2` returns, `decode_hbf_and_add_responses/5` passes the header list to `handle_decoded_headers_for_stream/5` without measuring it. The only implementation of the RFC 9113 section 6.5.2 formula, `assert_headers_smaller_than_max_header_list_size/2`, applies the server\u0027s setting to headers Mint sends.\n\n**2. Indexed-field expansion.** An HPACK indexed field (RFC 7541 section 6.1) is one byte for small table indexes and refers to a dynamic table entry of up to 4 KB. HPAX returns one shared binary per reference, so the decoded list itself stays small.\n\n**3. Cookie join.** `join_cookie_headers/1` runs `Enum.map_join/3` over every `cookie` value, copying each shared value into one contiguous binary. The dynamic table entry persists for the connection, so the same block can answer every open stream, and `Mint.HTTP.stream/2` returns all responses parsed from one delivery together."
                }
              ],
              "value": "1. Compressed-size check only. Mint.HTTP2.assert_header_block_within_max_size/2 compares the accumulated HEADERS plus CONTINUATION bytes with client_settings.max_header_list_size, which bounds a CONTINUATION chain but not the decoded size. After HPAX.decode/2 returns, decode_hbf_and_add_responses/5 passes the header list to handle_decoded_headers_for_stream/5 without measuring it. The only implementation of the RFC 9113 section 6.5.2 formula, assert_headers_smaller_than_max_header_list_size/2, applies the server\u0027s setting to headers Mint sends.\n\n2. Indexed-field expansion. An HPACK indexed field (RFC 7541 section 6.1) is one byte for small table indexes and refers to a dynamic table entry of up to 4 KB. HPAX returns one shared binary per reference, so the decoded list itself stays small.\n\n3. Cookie join. join_cookie_headers/1 runs Enum.map_join/3 over every cookie value, copying each shared value into one contiguous binary. The dynamic table entry persists for the connection, so the same block can answer every open stream, and Mint.HTTP.stream/2 returns all responses parsed from one delivery together."
            }
          ]
        }
      },
      "cveMetadata": {
        "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "assignerShortName": "EEF",
        "cveId": "CVE-2026-91043",
        "datePublished": "2026-09-28T11:15:09.158Z",
        "dateReserved": "2026-09-15T23:45:02.178Z",
        "dateUpdated": "2026-09-29T10:15:15.619Z",
        "state": "PUBLISHED"
      },
      "dataType": "CVE_RECORD",
      "dataVersion": "5.2"
    }