{"containers":{"cna":{"affected":[{"collectionURL":"https://repo.hex.pm","cpes":["cpe:2.3:a:deadtrickster:ssl_verify_fun:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["ssl_verify_hostname"],"packageName":"ssl_verify_fun","packageURL":"pkg:hex/ssl_verify_fun","product":"ssl_verify_fun","programFiles":["src/ssl_verify_hostname.erl"],"programRoutines":[{"name":"ssl_verify_hostname:try_match_wildcard/4"},{"name":"ssl_verify_hostname:try_match_hostname/2"},{"name":"ssl_verify_hostname:verify_cert_hostname/2"},{"name":"ssl_verify_hostname:verify_fun/3"}],"repo":"https://github.com/deadtrickster/ssl_verify_fun.erl","vendor":"deadtrickster","versions":[{"lessThan":"1.1.8","status":"affected","version":"1.0.1","versionType":"semver"}]},{"collectionURL":"https://github.com","cpes":["cpe:2.3:a:deadtrickster:ssl_verify_fun:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["ssl_verify_hostname"],"packageName":"deadtrickster/ssl_verify_fun.erl","packageURL":"pkg:github/deadtrickster/ssl_verify_fun.erl","product":"ssl_verify_fun","programFiles":["src/ssl_verify_hostname.erl"],"programRoutines":[{"name":"ssl_verify_hostname:try_match_wildcard/4"},{"name":"ssl_verify_hostname:try_match_hostname/2"},{"name":"ssl_verify_hostname:verify_cert_hostname/2"},{"name":"ssl_verify_hostname:verify_fun/3"}],"repo":"https://github.com/deadtrickster/ssl_verify_fun.erl","vendor":"deadtrickster","versions":[{"lessThan":"ca34031299cd2e0c98794c0da4ba556b94e39aad","status":"affected","version":"a49ca56923356c7a3981692b23c42ef32c2990e0","versionType":"git"}]}],"configurations":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>ssl_verify_fun decides the outcome only where it is the sole hostname check. Erlang/OTP 20.0 and later verify the hostname in <code>ssl</code> before the user <code>verify_fun</code> is called with <code>valid_peer</code>, as long as <code>server_name_indication</code> is a hostname or the connection was upgraded from a socket. A deployment is exposed when it sets <code>{server_name_indication, disable}</code>, when a TLS server uses <code>ssl_verify_hostname:verify_fun/3</code> to verify client certificates, when it calls <code>ssl_verify_hostname:verify_cert_hostname/2</code> directly, or when it runs Erlang/OTP older than 20.0. The attacker also needs a certificate with a partial-label wildcard from a CA the client trusts, which the CA/Browser Forum Baseline Requirements do not allow, so the realistic exposure is private PKI.</p>"},{"base64":false,"type":"text/markdown","value":"ssl_verify_fun decides the outcome only where it is the sole hostname check. Erlang/OTP 20.0 and later verify the hostname in `ssl` before the user `verify_fun` is called with `valid_peer`, as long as `server_name_indication` is a hostname or the connection was upgraded from a socket. A deployment is exposed when it sets `{server_name_indication, disable}`, when a TLS server uses `ssl_verify_hostname:verify_fun/3` to verify client certificates, when it calls `ssl_verify_hostname:verify_cert_hostname/2` directly, or when it runs Erlang/OTP older than 20.0. The attacker also needs a certificate with a partial-label wildcard from a CA the client trusts, which the CA/Browser Forum Baseline Requirements do not allow, so the realistic exposure is private PKI."}],"value":"ssl_verify_fun decides the outcome only where it is the sole hostname check. Erlang/OTP 20.0 and later verify the hostname in ssl before the user verify_fun is called with valid_peer, as long as server_name_indication is a hostname or the connection was upgraded from a socket. A deployment is exposed when it sets {server_name_indication, disable}, when a TLS server uses ssl_verify_hostname:verify_fun/3 to verify client certificates, when it calls ssl_verify_hostname:verify_cert_hostname/2 directly, or when it runs Erlang/OTP older than 20.0. The attacker also needs a certificate with a partial-label wildcard from a CA the client trusts, which the CA/Browser Forum Baseline Requirements do not allow, so the realistic exposure is private PKI."}],"cpeApplicability":[{"nodes":[{"cpeMatch":[{"criteria":"cpe:2.3:a:deadtrickster:ssl_verify_fun:*:*:*:*:*:*:*:*","versionEndExcluding":"1.1.8","versionStartIncluding":"1.0.1","vulnerable":true}],"negate":false,"operator":"OR"}],"operator":"AND"}],"credits":[{"lang":"en","type":"finder","value":"Andrew Moore"},{"lang":"en","type":"finder","value":"Martin Karrer"},{"lang":"en","type":"reporter","value":"Andrew Moore"},{"lang":"en","type":"remediation developer","value":"Martin Karrer"},{"lang":"en","type":"remediation developer","value":"deadtrickster"},{"lang":"en","type":"coordinator","value":"Jonatan Männchen / EEF"}],"dateAssigned":"2026-10-30T10:26:35.000Z","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Improper Validation of Certificate with Host Mismatch vulnerability in deadtrickster ssl_verify_fun allows an adversary in the middle to impersonate a TLS peer via a certificate that carries a partial-label wildcard. <code>try_match_wildcard/4</code> in <code>src/ssl_verify_hostname.erl</code> anchors only the text before and after the <code>*</code> of an identifier such as <code>f*.example.net</code>, so the wildcard also consumes dots and the certificate is accepted for <code>f.evil.example.net</code>. RFC 6125 confines a wildcard to the left-most label. The same matcher lets <code>*.168.1.1</code> match the IP literal <code>192.168.1.1</code>. Whole-label wildcards are matched correctly.</p>\n<p>Erlang/OTP 20.0 and later check the hostname in <code>ssl</code> before a user <code>verify_fun</code> sees <code>valid_peer</code>, so a client that keeps <code>server_name_indication</code> set rejects the mismatch regardless. Exposed are clients that disable SNI, servers that verify client certificate hostnames with this library, and direct callers of <code>verify_cert_hostname/2</code>.</p>\n<p>This issue affects ssl_verify_fun: from 1.0.1 before 1.1.8.</p>"},{"base64":false,"type":"text/markdown","value":"Improper Validation of Certificate with Host Mismatch vulnerability in deadtrickster ssl_verify_fun allows an adversary in the middle to impersonate a TLS peer via a certificate that carries a partial-label wildcard. `try_match_wildcard/4` in `src/ssl_verify_hostname.erl` anchors only the text before and after the `*` of an identifier such as `f*.example.net`, so the wildcard also consumes dots and the certificate is accepted for `f.evil.example.net`. RFC 6125 confines a wildcard to the left-most label. The same matcher lets `*.168.1.1` match the IP literal `192.168.1.1`. Whole-label wildcards are matched correctly.\n\nErlang/OTP 20.0 and later check the hostname in `ssl` before a user `verify_fun` sees `valid_peer`, so a client that keeps `server_name_indication` set rejects the mismatch regardless. Exposed are clients that disable SNI, servers that verify client certificate hostnames with this library, and direct callers of `verify_cert_hostname/2`.\n\nThis issue affects ssl_verify_fun: from 1.0.1 before 1.1.8."}],"value":"Improper Validation of Certificate with Host Mismatch vulnerability in deadtrickster ssl_verify_fun allows an adversary in the middle to impersonate a TLS peer via a certificate that carries a partial-label wildcard. try_match_wildcard/4 in src/ssl_verify_hostname.erl anchors only the text before and after the * of an identifier such as f*.example.net, so the wildcard also consumes dots and the certificate is accepted for f.evil.example.net. RFC 6125 confines a wildcard to the left-most label. The same matcher lets *.168.1.1 match the IP literal 192.168.1.1. Whole-label wildcards are matched correctly.\n\nErlang/OTP 20.0 and later check the hostname in ssl before a user verify_fun sees valid_peer, so a client that keeps server_name_indication set rejects the mismatch regardless. Exposed are clients that disable SNI, servers that verify client certificate hostnames with this library, and direct callers of verify_cert_hostname/2.\n\nThis issue affects ssl_verify_fun: from 1.0.1 before 1.1.8."}],"impacts":[{"capecId":"CAPEC-94","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>A holder of a certificate for <code>f*.example.net</code> from a CA the client trusts can impersonate any host named <code>f.&lt;label&gt;.example.net</code> to a client that relies on ssl_verify_fun alone for hostname verification, and read or alter the traffic of that connection.</p>"},{"base64":false,"type":"text/markdown","value":"A holder of a certificate for `f*.example.net` from a CA the client trusts can impersonate any host named `f.<label>.example.net` to a client that relies on ssl_verify_fun alone for hostname verification, and read or alter the traffic of that connection."}],"value":"A holder of a certificate for f*.example.net from a CA the client trusts can impersonate any host named f.<label>.example.net to a client that relies on ssl_verify_fun alone for hostname verification, and read or alter the traffic of that connection."}]}],"metrics":[{"cvssV4_0":{"Automatable":"NOT_DEFINED","Recovery":"NOT_DEFINED","Safety":"NOT_DEFINED","attackComplexity":"HIGH","attackRequirements":"PRESENT","attackVector":"NETWORK","baseScore":9.1,"baseSeverity":"CRITICAL","privilegesRequired":"NONE","providerUrgency":"NOT_DEFINED","subAvailabilityImpact":"NONE","subConfidentialityImpact":"NONE","subIntegrityImpact":"NONE","userInteraction":"NONE","valueDensity":"NOT_DEFINED","vectorString":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","version":"4.0","vulnAvailabilityImpact":"NONE","vulnConfidentialityImpact":"HIGH","vulnIntegrityImpact":"HIGH","vulnerabilityResponseEffort":"NOT_DEFINED"},"format":"CVSS","scenarios":[{"lang":"en","value":"GENERAL"}]}],"problemTypes":[{"descriptions":[{"cweId":"CWE-297","description":"CWE-297 Improper Validation of Certificate with Host Mismatch","lang":"en","type":"CWE"}]}],"providerMetadata":{"dateUpdated":"2026-10-30T20:22:16.466Z","orgId":"6b3ad84c-e1a6-4bf7-a703-f496b71e49db","shortName":"EEF"},"references":[{"name":"ssl_verify_fun: partial-label wildcard certificate matches hostnames across a label boundary","tags":["vendor-advisory","related"],"url":"https://github.com/deadtrickster/ssl_verify_fun.erl/security/advisories/GHSA-7gw2-hphm-c5px"},{"name":"EEF CNA record for CVE-2026-117666","tags":["related"],"url":"https://cna.erlef.org/cves/CVE-2026-117666.html"},{"name":"OSV record EEF-CVE-2026-117666","tags":["related"],"url":"https://osv.dev/vulnerability/EEF-CVE-2026-117666"},{"name":"Do not let partial-label wildcards match across label boundaries (ssl_verify_fun.erl pull request #33)","tags":["patch"],"url":"https://github.com/deadtrickster/ssl_verify_fun.erl/pull/33"},{"name":"Introducing commit a49ca56 in deadtrickster/ssl_verify_fun.erl","tags":["related"],"url":"https://github.com/deadtrickster/ssl_verify_fun.erl/commit/a49ca56923356c7a3981692b23c42ef32c2990e0"},{"name":"Fix commit ca34031 in deadtrickster/ssl_verify_fun.erl","tags":["patch"],"url":"https://github.com/deadtrickster/ssl_verify_fun.erl/commit/ca34031299cd2e0c98794c0da4ba556b94e39aad"}],"source":{"discovery":"EXTERNAL"},"title":"ssl_verify_fun: partial-label wildcard certificate matches hostnames across a label boundary","workarounds":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>On Erlang/OTP 21 or later, keep <code>server_name_indication</code> set to the target hostname and pass <code>{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}</code> in the <code>ssl</code> options. The hostname check in <code>ssl</code> then rejects the mismatch before ssl_verify_fun runs, and legitimate wildcard certificates continue to match. hackney and tls_certificate_check already set both options.</p>"},{"base64":false,"type":"text/markdown","value":"On Erlang/OTP 21 or later, keep `server_name_indication` set to the target hostname and pass `{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}` in the `ssl` options. The hostname check in `ssl` then rejects the mismatch before ssl_verify_fun runs, and legitimate wildcard certificates continue to match. hackney and tls_certificate_check already set both options."}],"value":"On Erlang/OTP 21 or later, keep server_name_indication set to the target hostname and pass {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} in the ssl options. The hostname check in ssl then rejects the mismatch before ssl_verify_fun runs, and legitimate wildcard certificates continue to match. hackney and tls_certificate_check already set both options."}],"x_proofOfConcept":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<ol>\n<li>Issue a leaf certificate whose only <code>subjectAltName</code> entry is <code>dNSName</code> <code>f*.example.net</code>, for example with <code>public_key:pkix_test_data/1</code>, and decode it with <code>public_key:pkix_decode_cert(Der, otp)</code>.</li>\n<li>Call <code>ssl_verify_hostname:verify_cert_hostname(Cert, \"foo.example.net\")</code>. It returns <code>{valid, \"foo.example.net\"}</code>, as expected.</li>\n<li>Call <code>ssl_verify_hostname:verify_cert_hostname(Cert, \"f.evil.example.net\")</code>. It returns <code>{valid, \"f.evil.example.net\"}</code> although the hostname spans two labels where the wildcard sits.</li>\n<li>Call <code>ssl_verify_hostname:verify_cert_hostname(Cert, \"a.b.example.net\")</code> as a control. It returns <code>{fail, unable_to_match_altnames}</code>.</li>\n<li>For the IP-literal variant, issue a leaf with <code>dNSName</code> <code>*.168.1.1</code> and call <code>verify_cert_hostname(Cert, \"192.168.1.1\")</code>. It returns <code>{valid, \"192.168.1.1\"}</code>.</li>\n<li>To observe the full TLS path, serve the first certificate from <code>ssl:listen/2</code> and connect with <code>{verify, verify_peer}</code>, the CA in <code>cacerts</code>, <code>{verify_fun, {fun ssl_verify_hostname:verify_fun/3, [{check_hostname, \"f.evil.example.net\"}]}}</code> and <code>{server_name_indication, disable}</code>. The handshake succeeds. With <code>{server_name_indication, \"f.evil.example.net\"}</code> instead, OTP 20.0 or later rejects the certificate with <code>hostname_check_failed</code> before ssl_verify_fun runs.</li>\n</ol>"},{"base64":false,"type":"text/markdown","value":"1. Issue a leaf certificate whose only `subjectAltName` entry is `dNSName` `f*.example.net`, for example with `public_key:pkix_test_data/1`, and decode it with `public_key:pkix_decode_cert(Der, otp)`.\n2. Call `ssl_verify_hostname:verify_cert_hostname(Cert, \"foo.example.net\")`. It returns `{valid, \"foo.example.net\"}`, as expected.\n3. Call `ssl_verify_hostname:verify_cert_hostname(Cert, \"f.evil.example.net\")`. It returns `{valid, \"f.evil.example.net\"}` although the hostname spans two labels where the wildcard sits.\n4. Call `ssl_verify_hostname:verify_cert_hostname(Cert, \"a.b.example.net\")` as a control. It returns `{fail, unable_to_match_altnames}`.\n5. For the IP-literal variant, issue a leaf with `dNSName` `*.168.1.1` and call `verify_cert_hostname(Cert, \"192.168.1.1\")`. It returns `{valid, \"192.168.1.1\"}`.\n6. To observe the full TLS path, serve the first certificate from `ssl:listen/2` and connect with `{verify, verify_peer}`, the CA in `cacerts`, `{verify_fun, {fun ssl_verify_hostname:verify_fun/3, [{check_hostname, \"f.evil.example.net\"}]}}` and `{server_name_indication, disable}`. The handshake succeeds. With `{server_name_indication, \"f.evil.example.net\"}` instead, OTP 20.0 or later rejects the certificate with `hostname_check_failed` before ssl_verify_fun runs."}],"value":"* Issue a leaf certificate whose only subjectAltName entry is dNSName f*.example.net, for example with public_key:pkix_test_data/1, and decode it with public_key:pkix_decode_cert(Der, otp).\n* Call ssl_verify_hostname:verify_cert_hostname(Cert, \"foo.example.net\"). It returns {valid, \"foo.example.net\"}, as expected.\n* Call ssl_verify_hostname:verify_cert_hostname(Cert, \"f.evil.example.net\"). It returns {valid, \"f.evil.example.net\"} although the hostname spans two labels where the wildcard sits.\n* Call ssl_verify_hostname:verify_cert_hostname(Cert, \"a.b.example.net\") as a control. It returns {fail, unable_to_match_altnames}.\n* For the IP-literal variant, issue a leaf with dNSName *.168.1.1 and call verify_cert_hostname(Cert, \"192.168.1.1\"). It returns {valid, \"192.168.1.1\"}.\n* To observe the full TLS path, serve the first certificate from ssl:listen/2 and connect with {verify, verify_peer}, the CA in cacerts, {verify_fun, {fun ssl_verify_hostname:verify_fun/3, [{check_hostname, \"f.evil.example.net\"}]}} and {server_name_indication, disable}. The handshake succeeds. With {server_name_indication, \"f.evil.example.net\"} instead, OTP 20.0 or later rejects the certificate with hostname_check_failed before ssl_verify_fun runs."}],"x_technicalAnalysis":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p><strong>1. Identifier parsing.</strong> <code>ssl_verify_hostname:try_match_hostname/2</code> strips trailing dots, tries an exact case-insensitive match, and otherwise calls <code>parse_and_validate_wildcard_identifier/2</code>. That function accepts an identifier with one <code>*</code> in the left-most label and at least two labels after it, and classifies it as <code>SingleCharW</code> when the label is exactly <code>*</code>.</p>\n<p><strong>2. Whole-label wildcard, correct.</strong> For <code>*.example.net</code> the matcher compares the text after the <code>*</code> with the hostname from its first dot onward, so <code>a.b.example.net</code> fails because <code>.b.example.net</code> differs from <code>.example.net</code>.</p>\n<p><strong>3. Partial-label wildcard, incorrect.</strong> For <code>f*.example.net</code> the <code>SingleCharW</code> branch is skipped. <code>try_match_wildcard/4</code> then checks only that the hostname ends with <code>.example.net</code> and starts with <code>f</code>. Nothing checks the span consumed by the <code>*</code>, so <code>f.evil.example.net</code> and <code>f.a.b.example.net</code> pass. RFC 6125 section 6.4.3 limits a wildcard to the left-most label. The <code>wildcard_not_in_a_label/2</code> guard only rejects internationalized <code>xn--</code> labels and does not help.</p>\n<p><strong>4. IP literals.</strong> The reference identifier is treated as a DNS name whatever its shape, so <code>verify_cert_hostname/2</code> with hostname <code>192.168.1.1</code> matches a <code>dNSName</code> of <code>*.168.1.1</code> through the whole-label branch. RFC 6125 does not apply wildcard matching to IP addresses, and OTP's <code>ssl</code> converts an IP-literal string to an <code>{ip, ...}</code> reference that only matches <code>iPAddress</code> entries.</p>\n<p><strong>5. Interaction with Erlang/OTP.</strong> When <code>ssl_verify_hostname:verify_fun/3</code> is used as <code>verify_fun</code>, OTP 20.0 and later call <code>ssl_certificate:validate/4</code> for <code>valid_peer</code> before the user fun. In the client role with <code>server_name_indication</code> set to a hostname, or with a peer IP from a socket upgrade, OTP runs <code>public_key:pkix_verify_hostname/3</code> and hands the user fun <code>{bad_cert, hostname_check_failed}</code> instead of <code>valid_peer</code>, which ssl_verify_fun turns into <code>{fail, ...}</code>. OTP's default match function allows no wildcards, and its <code>https</code> match function (<code>public_key:pkix_verify_hostname_match_fun/1</code>) splits both names on dots and matches the wildcard within the first label only. The ssl_verify_fun result therefore decides the outcome only with <code>{server_name_indication, disable}</code>, in the server role, on direct calls to <code>verify_cert_hostname/2</code>, or on OTP older than 20.0.</p>"},{"base64":false,"type":"text/markdown","value":"**1. Identifier parsing.** `ssl_verify_hostname:try_match_hostname/2` strips trailing dots, tries an exact case-insensitive match, and otherwise calls `parse_and_validate_wildcard_identifier/2`. That function accepts an identifier with one `*` in the left-most label and at least two labels after it, and classifies it as `SingleCharW` when the label is exactly `*`.\n\n**2. Whole-label wildcard, correct.** For `*.example.net` the matcher compares the text after the `*` with the hostname from its first dot onward, so `a.b.example.net` fails because `.b.example.net` differs from `.example.net`.\n\n**3. Partial-label wildcard, incorrect.** For `f*.example.net` the `SingleCharW` branch is skipped. `try_match_wildcard/4` then checks only that the hostname ends with `.example.net` and starts with `f`. Nothing checks the span consumed by the `*`, so `f.evil.example.net` and `f.a.b.example.net` pass. RFC 6125 section 6.4.3 limits a wildcard to the left-most label. The `wildcard_not_in_a_label/2` guard only rejects internationalized `xn--` labels and does not help.\n\n**4. IP literals.** The reference identifier is treated as a DNS name whatever its shape, so `verify_cert_hostname/2` with hostname `192.168.1.1` matches a `dNSName` of `*.168.1.1` through the whole-label branch. RFC 6125 does not apply wildcard matching to IP addresses, and OTP's `ssl` converts an IP-literal string to an `{ip, ...}` reference that only matches `iPAddress` entries.\n\n**5. Interaction with Erlang/OTP.** When `ssl_verify_hostname:verify_fun/3` is used as `verify_fun`, OTP 20.0 and later call `ssl_certificate:validate/4` for `valid_peer` before the user fun. In the client role with `server_name_indication` set to a hostname, or with a peer IP from a socket upgrade, OTP runs `public_key:pkix_verify_hostname/3` and hands the user fun `{bad_cert, hostname_check_failed}` instead of `valid_peer`, which ssl_verify_fun turns into `{fail, ...}`. OTP's default match function allows no wildcards, and its `https` match function (`public_key:pkix_verify_hostname_match_fun/1`) splits both names on dots and matches the wildcard within the first label only. The ssl_verify_fun result therefore decides the outcome only with `{server_name_indication, disable}`, in the server role, on direct calls to `verify_cert_hostname/2`, or on OTP older than 20.0."}],"value":"1. Identifier parsing. ssl_verify_hostname:try_match_hostname/2 strips trailing dots, tries an exact case-insensitive match, and otherwise calls parse_and_validate_wildcard_identifier/2. That function accepts an identifier with one * in the left-most label and at least two labels after it, and classifies it as SingleCharW when the label is exactly *.\n\n2. Whole-label wildcard, correct. For *.example.net the matcher compares the text after the * with the hostname from its first dot onward, so a.b.example.net fails because .b.example.net differs from .example.net.\n\n3. Partial-label wildcard, incorrect. For f*.example.net the SingleCharW branch is skipped. try_match_wildcard/4 then checks only that the hostname ends with .example.net and starts with f. Nothing checks the span consumed by the *, so f.evil.example.net and f.a.b.example.net pass. RFC 6125 section 6.4.3 limits a wildcard to the left-most label. The wildcard_not_in_a_label/2 guard only rejects internationalized xn-- labels and does not help.\n\n4. IP literals. The reference identifier is treated as a DNS name whatever its shape, so verify_cert_hostname/2 with hostname 192.168.1.1 matches a dNSName of *.168.1.1 through the whole-label branch. RFC 6125 does not apply wildcard matching to IP addresses, and OTP's ssl converts an IP-literal string to an {ip, ...} reference that only matches iPAddress entries.\n\n5. Interaction with Erlang/OTP. When ssl_verify_hostname:verify_fun/3 is used as verify_fun, OTP 20.0 and later call ssl_certificate:validate/4 for valid_peer before the user fun. In the client role with server_name_indication set to a hostname, or with a peer IP from a socket upgrade, OTP runs public_key:pkix_verify_hostname/3 and hands the user fun {bad_cert, hostname_check_failed} instead of valid_peer, which ssl_verify_fun turns into {fail, ...}. OTP's default match function allows no wildcards, and its https match function (public_key:pkix_verify_hostname_match_fun/1) splits both names on dots and matches the wildcard within the first label only. The ssl_verify_fun result therefore decides the outcome only with {server_name_indication, disable}, in the server role, on direct calls to verify_cert_hostname/2, or on OTP older than 20.0."}]}},"cveMetadata":{"assignerOrgId":"6b3ad84c-e1a6-4bf7-a703-f496b71e49db","assignerShortName":"EEF","cveId":"CVE-2026-117666","datePublished":"2026-10-30T20:22:16.466Z","dateReserved":"2026-10-30T10:00:01.681Z","dateUpdated":"2026-10-30T20:22:16.466Z","state":"PUBLISHED"},"dataType":"CVE_RECORD","dataVersion":"5.2"}