A 1.1 patch release that includes security fixes; upgrading from 1.1.5 is recommended.
Security
- The service worker's state cache now expires entries and is cleared on logout or a change of user (#2948). With
enable_state_snapshot = True, the opt-in worker kept each view's snapshot in CacheStorage with no age limit, and nothing ever cleared it. After logout, the previous user's snapshot stayed readable in that browser profile. The worker now drops and deletes a state entry older thanDJUST_STATE_SNAPSHOT_MAX_AGE(default 3600s) on lookup. Each mount frame carriessw_identity, an HMAC digest of the session key and user id keyed onSECRET_KEY(never the raw values). When it changes or disappears, the client clears the state, VDOM and shell caches. - The PWA sync endpoint no longer returns exception text to the client (#2950).
sync_endpoint_viewreturnsSyncResult.errorsin its JSON response, and the batch loop and the create/update/delete helpers putstr(e)there. A handler or database exception can quote application data (anIntegrityErrorcan quote other rows' values). The errors now carry the exception class only, such asCreate failed for action a1: ValueError, and the detail is logged on the server. - A legacy component's view-level event alias now resolves only its own component type (#3078). Descriptor components (
Dropdown,Modal,Tabsand the others) register a view-level alias for theirMeta.event, such astoggle_dropdown. The alias looked up the client-suppliedcomponent_idwithgetattron the view, so an event for one component type could drive a component of another type, and could read any view attribute first. It now accepts only the view class's declared descriptors of its own type, and ignores any other id. LiveViewTestClient.send_eventnow enforces handler authorization (#3094). It called the handler directly, so a test that sent an event to a@permission_requiredhandler as an unprivileged user passed whether the decorator was there or not. It now runs the consumer's gates first:@permission_required(denied when the view has no request), then the per-eventhas_object_permissionre-check for a view that overridesget_object, failing closed. A refused event does not run the handler and returnssuccess=Falsewithcode="permission_denied". A test that relied on the bypass must mount as a user who holds the permission.- Debug panel and
client-dev.jsnow escape interpolated values. The in-browser debug panel (loaded only whensettings.DEBUGis true) built its Events, Network, Patches, Components, Handlers, Variables and Warnings tabs, the element badge, and the hot-reload error toast as HTML strings without escaping, so text from application data (for example another user's input in a shared view) was parsed as markup in the developer's browser. Every interpolated value is now escaped (including attribute positions, via a quote-escapingescapeHtml), warningdocs_urllinks are limited to http(s)/relative URLs, and the toast usestextContent. Production pages (DEBUG = False) never load the panel and were not affected. reauth_on_eventnow also covers frames the server sends on its own. WithLIVEVIEW_CONFIG["reauth_on_event"]on, a WebSocket whose user was logged out or lost a permission was refused on its next client event but kept receivingserver_push/push_to_view,db_notify,tick, presence and async-result frames for a view withlogin_requiredorpermission_required. Each such turn now runs the same fresh-principal check as an event, under the render lock and before the hook runs; on failure the socket gets navigate-to-login and close 4403 and nothing is rendered or sent. A passed check covers the socket forLIVEVIEW_CONFIG["reauth_server_turn_interval"]seconds (default 5; 0 re-checks every turn), so ticking views do not read the session store on every tick. A check that raises denies and logs only the exception type and view class at WARNING. Off by default; behaviour withreauth_on_eventoff is unchanged. Tests inpython/djust/tests/test_server_turn_reauth.py.- The
reauth_on_eventre-check now denies when it raises instead of allowing the event. WithLIVEVIEW_CONFIG["reauth_on_event"]on, an exception other thanPermissionDeniedfrom the per-event authorization check (for exampleHttp404orDoesNotExistfromcheck_permissions(), or a failing session or authentication backend) was logged at DEBUG and the event ran anyway. The WebSocket and SSE transports now log the exception type and view class at WARNING (never the message) and take the normal refusal path: navigate to login and close 4403 on WebSocket, an auth-error frame and end of stream on SSE. Clean allow and deny results are unchanged, andreauth_on_eventstays off by default. Regression cases inpython/djust/tests/test_reauth_fail_closed.py. - Saved view state and the signed
state_snapshottoken of aTenantMixinview are bound to the tenant. The state backend entry, the Django-session entry (liveview_<path>and its__private,_componentsand__sticky__siblings) and the back-navigation token are keyed bytenant:<id>, so one session that reaches two tenants on the same URL keeps separate state and a mount under another tenant mounts fresh. A tenant view with no resolved tenant saves, restores and mints nothing. Thetenantcontext key is render-only, so a restoredself.tenantis no longer a serialized string. Views withoutTenantMixinare unchanged. State and tokens that tenant views saved before the upgrade are not found once (one fresh mount).