djust 1.1.4

StableSecurityReleased
Install
pip install djust==1.1.4

Before you upgrade, read Backwards incompatible changes in 1.1.4 below.

djust 1.1.4 is a security maintenance release for the 1.1 line. It was released on the same day as djust 1.2 and carries the same security fixes, without 1.2's new features or its template-engine changes.

Every project on 1.1.x should upgrade. Several of the fixes close a hole by changing a default, so read Backwards incompatible changes in 1.1.4 before you deploy. If you are ready to move to the 1.2 line, the 1.2 release notes describe the full upgrade, and 1.2.0 includes every fix listed here.

Python and Django support are unchanged from 1.1: Python 3.10 to 3.14 and Django 4.2 to 5.2.

Security

1.1.4 includes fixes for the following advisories. Each advisory has the details, the affected versions and the credit.

AdvisoryArea
GHSA-fccp-5h88-g34jDataTable sorting, filtering and grouping
GHSA-jv2m-fcq9-94xfdjust admin (admin_ext) permissions
GHSA-c44q-w252-mr67Observability endpoints
GHSA-hc2m-gvfj-x6r3Built-in components
GHSA-r372-rrpw-5cgjBuilt-in components
GHSA-j23m-jxwp-m3vqTheming
GHSA-6q7c-hvpc-ff2qPresence and channel groups
GHSA-5ffg-p52h-v2phBack-navigation state snapshots
GHSA-74vj-mpp4-45cgSticky {% live_render %} children
GHSA-7fcf-23mf-rhhmResumable uploads
GHSA-p9vp-rh5f-2cvqCustom template filters (fixed on the 1.2 line in 1.2.0rc2)

Backwards incompatible changes in 1.1.4

Each of these closes one of the advisories above. They are the same changes 1.2.0 makes, and the 1.2 notes explain each one in more detail.

  • djust admin enforces model permissions. DjustModelAdmin checks has_view_permission, has_add_permission, has_change_permission and has_delete_permission on every page, save and delete, and the defaults now call user.has_perm() with the model's view_ / add_ / change_ / delete_ permission, as Django's ModelAdmin does. Staff users who aren't superusers need those permissions; before, any active staff account had full access.
  • Built-in components HTML-escape the values they render. HTML passed to a content slot (a modal, card or tab body, a header, a footer, an icon) must be marked safe with mark_safe(), or it is shown as text. Link and image URLs with a javascript:, vbscript: or non-image data: scheme render as #. The rich-text editor's value is cleaned to an HTML allowlist instead of escaped.
  • Custom template filters registered with is_safe=True keep their output escaped unless their input was already safe, as in Django. A filter that produces markup must return mark_safe(...) or format_html(...) itself.
  • The observability endpoints need a token. Requests must come straight from a loopback client with no proxy headers and must send the X-Djust-Observability-Token header. manage.py djust_mcp sends it for you; print it for other tools with manage.py djust_observability_token.
  • DataTable sorts, filters and groups only on declared columns. A column is sortable unless it sets "sortable": False, and filterable only when it sets "filterable": True. Unknown column names are ignored.
  • Back-navigation state snapshots from earlier versions are rejected once and the view mounts fresh.
  • Resumable uploads started before the upgrade restart from the beginning, and resume only in the session that started them.
  • Theme cookies naming an unregistered pack or an invalid layout fall back to the defaults.

Bug fixes

  • A LiveView joins its channel groups (view, presence, db_notify) only after its permission checks and on_mount hooks pass. A refused mount leaves every group, and presence and push messages no longer reach a connection with no mounted view.
  • A reused sticky {% live_render %} child re-runs its view and object permission checks on every parent render, and live_redirect carry-over re-checks object permissions.
  • {% theme_css_link %} URL-encodes its query string.
  • AzureBlockBlobWriter raises RuntimeError when it is used before it is opened, instead of passing an unset blob name to the Azure client.

The full entry is in the [1.1.4] section of the 1.1 branch's CHANGELOG.

Upgrading

pip install "djust==1.1.4"
python manage.py collectstatic --noinput
python manage.py check

Then work through the list above: grant model permissions to staff who use djust admin, mark component slot HTML safe, add "filterable": True to DataTable columns you filter on, and give any observability client the token.

All changes in 1.1.4

The CHANGELOG.md entry for djust 1.1.4.

A maintenance release for the 1.1 line with the fixes from 1.2.0. Review the behaviour changes below before upgrading.

Security

Changed

  • djust admin applies the DjustModelAdmin permission hooks to every page, save and delete, and the default hooks follow Django's model permissions. has_view_permission, has_add_permission, has_change_permission and has_delete_permission are checked before mount (list, add) or per object (change, delete), and again in save, form_valid, confirm_delete and the delete_selected action. The defaults now call user.has_perm() with the model's view_/add_/change_/delete_ codename, as Django's ModelAdmin does. Staff users who aren't superusers now need those permissions; previously any active staff account had full access. The admin index lists only models the user has some permission on.
  • Built-in components HTML-escape the values they render. Every component class, {% ... %} component tag and Rust component renderer now passes interpolated values through conditional_escape, in all CSS-framework variants. HTML passed to a content slot must be marked safe (mark_safe, or another component's rendered output), or it is shown as text: this affects modal/card/sheet/popover/tabs/accordion bodies, headers and footers, icons, and similar slots. href/src values using javascript:, vbscript: or non-image data: render as #. The rich-text editor is the exception: its value is cleaned to an HTML allowlist (formatting kept, scripts and event attributes removed) instead of escaped.
  • The observability endpoints require a project token. Besides DEBUG and a loopback client, a request must carry no proxy headers (X-Forwarded-For, Forwarded, X-Real-IP, X-Forwarded-Host, X-Forwarded-Proto) and must send X-Djust-Observability-Token. The token is derived from SECRET_KEY (override with the DJUST_OBSERVABILITY_TOKEN environment variable) and printed by manage.py djust_observability_token. manage.py djust_mcp sends it automatically; other tools and hand-written requests must add the header.
  • Back-navigation state snapshots are bound to a keyed digest of the session key instead of the key itself. Snapshots issued by earlier versions fail verification once, and the view mounts fresh.
  • A custom template filter registered with is_safe=True keeps its output escaped unless its input was already safe, as in Django (is_safe and isinstance(obj, SafeData)). Previously the flag alone left the output unescaped in the Rust renderer. A filter that produces markup must return mark_safe(...) / format_html(...) itself; {{ value|safe|myfilter }} and mark_safe context values behave as before.

Fixed

  • DataTable sorts, filters and groups only on declared columns. on_table_sort, on_table_filter, group-by and expression filters now accept only keys listed in table_columns, checked again where the queryset is built. Sorting is on unless a column sets "sortable": False; filtering needs "filterable": True; table_default_sort is always accepted. Unknown column names are ignored.
  • A LiveView joins its channel groups (view, presence, db_notify) only after its permission checks and on_mount hooks pass. A refused mount, including one inside mount_batch, leaves every group, and presence and client-push messages are no longer delivered to a connection with no mounted view. check_permissions and on_mount hooks now run before on_view_mounted.
  • Resumable uploads record the session that started them. Resume over WebSocket and the HTTP upload-status endpoint answer only that session; the status endpoint previously returned 404 even to the owner. Uploads started before the upgrade, or whose session key changed (for example at login), restart from the beginning.
  • A reused sticky {% live_render %} child re-runs its view and object permission checks on every parent render, and live_redirect carry-over re-checks object permissions too. A child the user may no longer see is unmounted and the render fails the same way a fresh mount would.
  • Theme cookies that name an unregistered pack or an invalid layout fall back to the defaults, and {% theme_css_link %} URL-encodes its query. An unknown pack now resolves to the session pack, then the configured default. Layout names must match [A-Za-z0-9_-]{1,64}.
  • AzureBlockBlobWriter raises RuntimeError when used before it is opened, instead of passing an unset blob name to the Azure client.

All releases · Atom feed