idna 2.10.post1: backport CVE-2024-3651 - #1
Merged
Merged
Conversation
GHSA-jjg7-2v4v-x38h. A specially crafted argument to idna.encode() could consume an exceptional amount of CPU: a short label of n CONTEXTJ joiners cost O(n^2) work, so a few kilobytes of input stalled the process. Measured on this branch before the fix: 250 joiners 0.04s, 500 0.16s, 1000 0.56s, 2000 2.09s, 4000 8.43s -- 4x per doubling. After: constant, 0.0001s. RFC 5892 Appendix A.1 requires a ZERO WIDTH NON-JOINER to match (Joining_Type:{L,D})(Joining_Type:T)*ZWNJ(Joining_Type:T)*(Joining_Type:{R,D}) so the scan on each side of the joiner must stop at the first codepoint that is neither Transparent nor a valid terminator. valid_contextj() instead fell through on anything that was not Transparent and not L/D (resp. R/D) and kept scanning to the end of the label. That was both the performance bug and a correctness bug: labels with a Non_Joining codepoint between the joiner and its context were wrongly accepted. Two changes are required together, mirroring upstream: 1. idna/core.py -- add the `else: break` arm to both scan loops in valid_contextj(), so a non-Transparent, non-terminating joining type ends the search. 2. idna/idnadata.py -- regenerate `joining_types`. This is not optional. The table was built from ArabicShaping.txt, which lists joining types only for the Arabic script, so it held just 4 Transparent entries out of 775. With only (1) applied, every combining mark would terminate the scan and valid IDNs would be rejected. The table is now derived from extracted/DerivedJoiningType.txt: 2722 entries (T=1997, D=586, R=130, L=5, C=4). Regenerated at Unicode 13.0.0, the version idna 2.10's other tables are built on, rather than lifting upstream 3.7's Unicode 15.1.0 table, so the release stays internally consistent. Verified to reproduce every one of 2.10's existing D/R/L/C entries exactly (586/130/5/4, no diffs). The 46 explicit Non_Joining (U) entries are dropped, matching upstream: with the `else: break` arm an absent entry and an explicit U now behave identically. 3. tools/idna-data -- point _load_arabicshaping() at extracted/DerivedJoiningType.txt and handle its range syntax, so the committed table is reproducible from the generator. Validation under Python 2.7.18: the full IdnaTest.txt conformance corpus runs 7787 tests with 1227 pre-existing skips, identical to the pristine 2.10 base -- no behavioural drift. Two regression tests added; both fail on the pristine base and pass with the fix. Upstream fix: kjd/idna commit 5beb28b (PR kjd#172), released in idna 3.7. Advisory: GHSA-jjg7-2v4v-x38h https://nvd.nist.gov/vuln/detail/CVE-2024-3651 Py2.7 notes: test literals use \uXXXX escapes so tests/test_idna.py stays pure ASCII and needs no PEP 263 coding declaration; assertIsNone/assertLess are both available in 2.7's unittest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ActiveState security release of idna 2.10, the last upstream release to support Python 2.7, carrying the backport of CVE-2024-3651 (GHSA-jjg7-2v4v-x38h), fixed upstream in idna 3.7. Bumps idna/package_data.py __version__ to '2.10.post1' (the sole version location in this tree; setup.py reads it from package_data) and adds a HISTORY.rst entry in the file's existing plain-prose style. Verified PEP 440: 2.10.post1 is a post-release, sorts after 2.10 and before 2.11. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Backports the upstream fix for CVE-2024-3651 to the Python-2.7-compatible idna 2.10 line by correcting valid_contextj()’s joining-type scan behavior and regenerating the joining-types data to preserve correctness while eliminating the O(n²) worst-case.
Changes:
- Fix
valid_contextj()to stop scanning at the first non-Transparent, non-terminating joining type (eliminating quadratic behavior and correcting acceptance rules). - Regenerate
joining_typesfromextracted/DerivedJoiningType.txtand update the generator to load that source (including range syntax handling). - Add regression tests for correctness and non-quadratic behavior; bump version to
2.10.post1and document the security release inHISTORY.rst.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tools/idna-data | Switch joining-type source to DerivedJoiningType.txt and support range parsing so regenerated data is reproducible. |
| tests/test_idna.py | Add regression coverage for joining-type scan termination and guard against quadratic regressions. |
| idna/package_data.py | Bump library version to 2.10.post1. |
| idna/idnadata.py | Regenerate joining_types table to include correct Transparent entries needed for ContextJ scanning. |
| idna/core.py | Implement early-termination (else: break) in ContextJ scan loops to match RFC 5892 expectations and avoid O(n²). |
| HISTORY.rst | Record the security fix and release notes for 2.10.post1. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
emanuelc-activestate
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ActiveState security release of idna 2.10 — the last upstream release supporting Python 2.7 (3.0 requires 3.5+).
The vulnerability
A crafted argument to
idna.encode()costs O(n²). Measured on the pristine 2.10 base with a label of n joiners:4× per doubling — a few kilobytes of input stalls the process. After the fix: 0.0001 s, flat.
RFC 5892 Appendix A.1 requires a ZWNJ to match
so the scan on each side of the joiner must stop at the first codepoint that is neither Transparent nor a valid terminator.
valid_contextj()instead fell through on anything that was not Transparent and not L/D (resp. R/D) and kept scanning to the end of the label. That was both the performance bug and a correctness bug: labels with a Non_Joining codepoint between the joiner and its context were wrongly accepted.Two changes are required together
1.
idna/core.py— add theelse: breakarm to both scan loops, as upstream did.2.
idna/idnadata.py— regeneratejoining_types. This is not optional. The table was built fromArabicShaping.txt, which lists joining types only for the Arabic script, so it held just 4 Transparent entries out of 775. With only change 1 applied, every combining mark would terminate the scan and valid IDNs would be rejected. The table now derives fromextracted/DerivedJoiningType.txt: 2722 entries (T=1997, D=586, R=130, L=5, C=4).Regenerated at Unicode 13.0.0 — the version 2.10's other tables are built on — rather than lifting upstream 3.7's Unicode 15.1.0 table, so the release stays internally consistent. Verified to reproduce every one of 2.10's existing D/R/L/C entries exactly (586/130/5/4, zero diffs). The 46 explicit Non_Joining (
U) entries are dropped, matching upstream: with theelse: breakarm, an absent entry and an explicitUnow behave identically.3.
tools/idna-data— point_load_arabicshaping()atextracted/DerivedJoiningType.txtand handle its range syntax, so the committed table is reproducible from the generator.Testing (Python 2.7.18)
The full
IdnaTest.txtconformance corpus runs 7787 tests, 1227 pre-existing skips — identical to the pristine 2.10 base, so no behavioural drift. Note this corpus is driven byload_tests, which pytest ignores; run it withpython -m unittest discover -s tests -t ..Two regression tests added (7789 total). Both fail on the pristine base and pass with the fix. Test literals use
\uXXXXescapes sotests/test_idna.pystays pure ASCII and needs no PEP 263 declaration. Bothidna/andtests/compile under Python 2 and Python 3.Upstream fix:
kjd/idnacommit5beb28b9dd77(PR kjd#172).🤖 Generated with Claude Code