Skip to content

[Security] Remove ujson package (CVE-2022-31116, CVE-2022-31117, CVE-2021-45958)#1757

Open
kukgini wants to merge 5 commits into
hyperledger-indy:mainfrom
kukgini:security-fix-remove-ujson
Open

[Security] Remove ujson package (CVE-2022-31116, CVE-2022-31117, CVE-2021-45958)#1757
kukgini wants to merge 5 commits into
hyperledger-indy:mainfrom
kukgini:security-fix-remove-ujson

Conversation

@kukgini

@kukgini kukgini commented Jun 30, 2026

Copy link
Copy Markdown

Summary

Removes the ujson dependency (which has known vulnerabilities CVE-2022-31116, CVE-2022-31117, CVE-2021-45958) and replaces it with the Python standard library json module across serialization, recorder, and zstack code paths.

This continues the stalled work from #1676 by @PatStLouis:

  • Rebased onto current main (the previously requested rebase).
  • Fixes a logic bug that the original PR accidentally introduced.

Ref: https://security.snyk.io/package/pip/ujson

Changes

  • Drop ujson from setup.py, build scripts, and dev-setup package lists.
  • common/serializers/json_serializer.py: remove the try: import ujson branch; promote the existing OrderedJsonEncoder (stdlib json with sort_keys=True, separators=(',', ':'), ensure_ascii=False) to be the sole encoder, preserving sorted-key / compact output.
  • Replace try: import ujson as json / except ImportError: import json fallbacks in recorder.py, test_recorder.py, stp_zmq/zstack.py, and scripts/test_zmq/.../zstack.py with a plain import json.

Bug fix beyond #1676

In plenum/common/channel.py, the linting errors commit of #1676 changed:

-        if type(msg) != tuple:
+        if not isinstance(type(msg), tuple):

The intent was to silence flake8 E721 (do not compare types), which the original type(msg) != tuple triggers (E721 is not in this repo's .flake8 ignore list). However the replacement is always True: type(msg) is a class object, and a class object is never an instance of tuple. As a result, messages that were already tuples got wrapped again into a nested tuple — breaking handler routing in _process_sync — and it directly contradicts the adjacent comment explaining why isinstance must not be used here (it also matches NamedTuple).

This PR keeps the lint fix but does it correctly:

        if type(msg) is not tuple:

E721's own message recommends is / is not for exact type checks, so this form silences E721 and preserves the original semantics — it is not a revert of the linting fix. Verified with flake8 (--select=E721 reports no violation) and against tuple, NamedTuple, dict, str, and int inputs, where the result matches the original != behavior in every case:

variant E721 logic
type(msg) != tuple (pre-#1676) ❌ fails ✅ correct
not isinstance(type(msg), tuple) (#1676) ✅ passes ❌ always True
type(msg) is not tuple (this PR) ✅ passes ✅ correct

Notes

  • pysha3 remains out of scope (per discussion in [Security] remove ujson package (CVE-2022-31116, CVE-2022-31117, CVE-2021-45958) #1676).
  • ⚠️ Serialization compatibility: with ujson installed, nested bytes values inside a dict were previously encoded as UTF-8 strings; stdlib json raises TypeError for that case. This matches the pre-existing non-ujson fallback behavior, so it is not a new regression, but reviewers may want a byte-compatibility regression test if any signed/hashed payloads carry nested raw bytes.

🤖 Generated with Claude Code

PatStLouis and others added 4 commits June 30, 2026 14:02
Signed-off-by: pstlouis <patrick.st-louis@opsecid.ca>
Signed-off-by: pstlouis <patrick.st-louis@opsecid.ca>
Signed-off-by: pstlouis <patrick.st-louis@opsecid.ca>
The ujson-removal PR replaced `type(msg) != tuple` with
`not isinstance(type(msg), tuple)`, which is always True (a class
object is never a tuple instance). This caused already-tuple messages
to be wrapped again into a nested tuple, breaking handler routing, and
contradicted the adjacent comment explaining why isinstance must not be
used (it matches NamedTuple).

Restore the original semantics in a lint-clean form (`type(msg) is not
tuple`), which preserves NamedTuple wrapping behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: kukgini <kukgini@gmail.com>
@kukgini kukgini force-pushed the security-fix-remove-ujson branch from c018850 to 9362661 Compare June 30, 2026 05:20
@kukgini

kukgini commented Jun 30, 2026

Copy link
Copy Markdown
Author

cc @WadeBarnes @swcurran

This PR picks up the stalled #1676 (remove ujson for CVE-2022-31116 / CVE-2022-31117 / CVE-2021-45958), with @PatStLouis's commits preserved and rebased onto current main — addressing the rebase that was requested there.

It also fixes a logic bug the original PR introduced in plenum/common/channel.py: the linting errors commit changed type(msg) != tuple to not isinstance(type(msg), tuple) to silence flake8 E721, but that expression is always True (a class object is never a tuple instance), which double-wraps already-tuple messages and breaks handler routing. I kept the lint fix but did it the way E721 recommends — type(msg) is not tuple — which passes E721 and preserves the original semantics (verified against tuple / NamedTuple / dict / str / int). Details and a comparison table are in the PR description.

pysha3 is intentionally left out of scope, consistent with the discussion in #1676. Could a maintainer take a look / trigger CI when you have a chance? Happy to address any feedback.

Pin the exact byte output of JsonSerializer after dropping ujson, since it
feeds signing/hashing and must stay byte-stable: key ordering, compact
separators, raw-UTF-8 non-ASCII, float repr, int-key coercion, bool/None,
and top-level bytes base64 encoding.

Also characterise the one known behavioural difference vs ujson: bytes
nested inside a container now raise TypeError (the bytes special-case only
fires for a top-level value), whereas ujson silently encoded them as UTF-8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: kukgini <kukgini@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants