djust 1.0.0rc6

Pre-releaseSecurityReleased
Install
pip install djust==1.0.0rc6

This is a pre-release. djust 1.0.0 has shipped since: read the djust 1.0.0 release notes.

Before you upgrade, read the upgrade guide.

Security

  • Bumped idna 3.11 → 3.15 — patches CVE-2026-45409 (GHSA-65pc-fj4g-8rjx, Dependabot alert #101). Specially crafted inputs to idna.encode() ("٠" * N or "・" * N + "漢") hit the valid_contexto function prior to length rejection, so high values of N consumed significant resources — a ReDoS-style denial-of-service. Same class as CVE-2024-3651; the 2024 remediation was incomplete. idna 3.14 rejects long inputs early; 3.15 extends the early-reject to lesser-used per-label conversion and codec paths. idna is a transitive runtime dep (pulled in by anyio / httpx / httpcore / requests); the bump is a lockfile-only change via uv lock --upgrade-package idna, no direct-dep change in pyproject.toml. CVSS v4 6.9 / medium. Verified via full Python regression (7301 passed, 0 failed). Domain names cannot exceed 253 characters in normal usage, so the practical exposure surface was thin, but the fix removes the ReDoS class entirely.

Fixed

  • LiveView.request no longer triggers a "non-serializable ASGIRequest" warning on every mount/event (#1545). self.request was assigned by the HTTP post() path (mixins/request.py:489) and the WebSocket path (websocket.py:1940) AFTER __init__, so it sat OUTSIDE _framework_attrs and the state-snapshot machinery treated the ASGIRequest as user state — hitting the non-serializable fallback at serialization.py:557 and logging "LiveView state contains non-serializable value: ASGIRequest …" on every mount AND every event for every LiveView. The warning was cosmetic (the framework str()-stringifies the value and re-sets self.request to the live request on every request/event, so the stringified copy is never read back) but noisy enough to dilute the warning's signal for genuine app-author bugs. Fix: assign self.request: Any = None in LiveView.__init__ BEFORE the _framework_attrs = frozenset(self.__dict__.keys()) line at live_view.py:526request is now captured as framework state and excluded from the user-state snapshot. Matches the _framework_attrs snapshot-order invariant (#1393). The fix also adds "request" to the _FRAMEWORK_INTERNAL_ATTRS hard-coded frozenset used by _debug_state_sizes and the debug-toolbar observability path (discovered during regression-suite verification — 2 test_debug_state_sizes_* tests started reporting request as user state until both filters were updated). Covered by 5 regression cases in python/tests/test_liveview_request_framework_attr_1545.py, including a gate-off self-test (#254) confirming 4 of 5 tests fail without the fix.
  • crates/djust_live is now cargo test-able — extension-module gated behind a default-on Cargo feature (#1543). crates/djust_live carried PyO3's extension-module feature unconditionally, so cargo test -p djust_live failed at link time with ld: symbol(s) not found ... Py_True — the crate that holds djust._rust's entry point, the actor system, the RustLiveView backend, and (since #1541 / PR #1546) the PatchResponse round-trip regression tests had no fast Rust-native test feedback loop. make test worked around it with --exclude djust_live. Surfaced twice in the v1.0.0rc4 Phase-2 drain (PRs #1530, #1535) — standing structural constraint. The fix gates the feature behind a default-on Cargo feature ([features] default = ["extension-module"]; extension-module = ["pyo3/extension-module"]), so maturin develop / cargo build are unchanged but cargo test -p djust_live --no-default-features now links against libpython and runs. 37 djust_live tests now execute (including the 4 msgpack_round_trip_patch_response_* regression tests from PR #1546 / #1541 that previously compile-checked only). The Makefile test-rust target, the parallel test target, and the CI workflow (.github/workflows/test.yml) all gained a Phase 2 invocation that runs the djust_live tests with --no-default-features after the existing workspace-minus-djust_live pass. Maturin build path verified end-to-end (wheel build → import).
  • PatchResponse msgpack round-trip is now positionally-stable for every None/Some combination of patches and html (#1541). Sibling audit of #1538. PatchResponse is a plain #[derive(Serialize, Deserialize)] struct in crates/djust_live/src/actors/messages.rs, so under msgpack it serializes as a positional array — and its first two fields, patches: Option<Vec<Patch>> and html: Option<String>, carried #[serde(skip_serializing_if = "Option::is_none")] without #[serde(default)]. The fix that worked for #1538 (VNode.djust_id — add default) does not generalize: that fix only works for STRICTLY TRAILING optionals. For leading optionals like PatchResponse's, skip_serializing_if shifts later array elements into the wrong positional slot on deserialize — and default cannot repair this because the deserializer isn't running out of elements; it's reading wrong-typed values at the wrong positions (empirically witnessed in crates/djust_vdom/tests/wire_protocol_snapshot.rs :: msgpack_skip_with_default_works_for_trailing_optional_only). The correct fix for PatchResponse is to remove skip_serializing_if entirely — None is then serialized as msgpack nil (1 byte) and positional slots stay aligned. This is defense-in-depth: PatchResponse is not currently rmp_serde::to_vec'd on any production path (only the inner Vec<Patch> is at lib.rs:679), but future cross-process actor transport would have hit the same #1538 class. The #1448 wire-protocol snapshot suite now also carries 3 structural witness tests pinning the bug class so any future plain wire struct hitting the same pattern fails fast. Wire-format note: the JSON encoding of PatchResponse now always includes the patches and html keys (null rather than omitted); no current consumer parses PatchResponse JSON, but the existing inline serde_json test was updated to reflect the new always-present shape. Layer B regression tests for PatchResponse itself live inline in messages.rs and currently compile-check only (cargo test -p djust_live is blocked by #1543's unconditional extension-module feature); they will execute automatically once #1543 lands. 3 new msgpack_* cases in crates/djust_vdom/tests/wire_protocol_snapshot.rs and 4 new msgpack_round_trip_patch_response_* cases in crates/djust_live/src/actors/messages.rs.

Added

  • Audit: sync_to_async → native-async-ORM migration surface (#1434). A new audit, docs/audits/async-orm-2026-05.md, classifies every sync_to_async / async_to_sync call site in framework code — 126 sites across 14 files — and a companion benchmark, scripts/bench_sync_to_async_overhead.py, measures the per-crossing asgiref threadpool overhead empirically (~60 µs/crossing on the dev machine). The audit finds that issue #1434's premise does not hold: there are zero sync_to_async(Model.objects.X) call sites, only 3 ORM-category sites (all indirect auth/tenant helpers that fire once per connection at mount, never per event), and the ORM/cache-migratable fraction of per-event latency is 0% — below #1434's own 5% deprioritize gate. The audit recommends closing #1434. Internal/contributor documentation and tooling; no framework behavior change.

All releases · Atom feed