{"id":"CVE-2026-61598","aliases":["EUVD-2026-80768","GHSA-cc7c-9jff-58wj","PYSEC-2026-4042"],"status":"published","title":"Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler","description":"djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. Prior to version 1.0.7, `djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the LiveView base MRO, so every LiveView exposes it. It `setattr`s a view attribute whose name is client-supplied (`field`), gated only by: reject `_`-prefixed names; reject a 14-entry denylist of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which defaults to None = allow all; and `hasattr` existence. As a result, a client can set any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker. This issue is fixed in djust 1.0.7. As a workaround, set `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.","severity":{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00429,"percentile":0.37402,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-915"],"published":"2026-09-16T13:55:48Z","modified":"2026-10-28T02:31:02Z","fixes":[{"ecosystem":"PyPI","package":"djust","fixed_in":["1.0.7"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"djust","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.0.7"}]}]},{"source":"osv","ecosystem":"PyPI","package":"djust","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.0.7"}]}],"versions":["0.1.0","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","0.2.0","0.2.1","0.2.2","0.3.0","0.3.1","0.3.2","0.3.4","0.3.5","0.3.6","0.3.7","0.3.8","0.4.0","0.4.1","0.4.2","0.4.3","0.4.4","0.9.0","0.9.1","0.9.2","0.9.4","0.9.5","0.9.6","0.9.7","1.0.0","1.0.0rc1","1.0.0rc10","1.0.0rc11","1.0.0rc12","1.0.0rc13","1.0.0rc14","1.0.0rc15","1.0.0rc16","1.0.0rc17","1.0.0rc18","1.0.0rc2","1.0.0rc3","1.0.0rc4","1.0.0rc6","1.0.0rc7","1.0.0rc8","1.0.0rc9","1.0.1","1.0.1rc1","1.0.2","1.0.2rc1","1.0.2rc2","1.0.2rc3","1.0.3","1.0.3rc1","1.0.3rc2","1.0.4","1.0.4rc1","1.0.5","1.0.5rc1","1.0.5rc2","1.0.5rc3","1.0.5rc4","1.0.5rc5","1.0.6","1.0.6rc1","1.0.6rc2","1.0.6rc3","1.0.7rc1"]},{"source":"osv","ecosystem":"PyPI","package":"djust","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.0.7"}]}],"versions":["0.1.0","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","0.2.0","0.2.1","0.2.2","0.3.0","0.3.1","0.3.2","0.3.4","0.3.5","0.3.6","0.3.7","0.3.8","0.4.0","0.4.1","0.4.2","0.4.3","0.4.4","0.9.0","0.9.1","0.9.2","0.9.4","0.9.5","0.9.6","0.9.7","1.0.0","1.0.0rc11","1.0.1","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6"]}],"products":[{"vendor":"djust-org","product":"djust"}],"references":[{"url":"https://github.com/djust-org/djust/security/advisories/GHSA-cc7c-9jff-58wj","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/djust-org/djust/releases/tag/v1.0.7","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-cc7c-9jff-58wj","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/61xxx/CVE-2026-61598.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-61598","tags":["advisory"]},{"url":"https://github.com/djust-org/djust","tags":["package"]},{"url":"https://pypi.org/project/djust","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-61598","title":"Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler","description":"djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. Prior to version 1.0.7, `djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the LiveView base MRO, so every LiveView exposes it. It `setattr`s a view attribute whose name is client-supplied (`field`), gated only by: reject `_`-prefixed names; reject a 14-entry denylist of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which defaults to None = allow all; and `hasattr` existence. As a result, a client can set any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker. This issue is fixed in djust 1.0.7. As a workaround, set `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.","status":"published","published":"2026-09-16T13:59:34.306Z","modified":"2026-09-16T15:27:34.282Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-915"],"updated_at":"2026-11-02T14:19:53.786088Z"}],"euvd":[{"id":"EUVD-2026-80768","description":"djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. Prior to version 1.0.7, `djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the LiveView base MRO, so every LiveView exposes it. It `setattr`s a view attribute whose name is client-supplied (`field`), gated only by: reject `_`-prefixed names; reject a 14-entry denylist of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which defaults to None = allow all; and `hasattr` existence. As a result, a client can set any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker. This issue is fixed in djust 1.0.7. As a workaround, set `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.","status":"published","published":"2026-09-16T13:59:34Z","modified":"2026-09-16T15:27:34Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T17:28:15.407859Z"}],"ghsa":[{"id":"GHSA-cc7c-9jff-58wj","title":"djust: Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler","description":"### Impact\n`djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the **LiveView base MRO**, so every LiveView exposes it. It `setattr`s a view attribute whose **name is client-supplied** (`field`), gated only by: reject `_`-prefixed names; reject a **14-entry denylist** of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which **defaults to None = allow all**; and `hasattr` existence.\n\nResult: a client can set **any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template**. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker.\n\n**Severity High** for apps that hold authorization/ownership/business state in public view attributes (the normal djust pattern) -\u003e state tampering / IDOR / authz-flag manipulation; Low otherwise. Default-on across every LiveView. For a public (no-login) view an anonymous client can mass-assign; for an authenticated view a logged-in user can tamper their own session's view state (the IDOR/authz vector when downstream handlers act on it without re-authorizing).\n\nReproduced: a view with `account_id/is_admin/total_price` (none bound with dj-model) had all three set via `update_model` calls.\n\n### Patches\nRestrict the default handler to fields actually exposed via `dj-model=`: have the template renderer record the bound-field set per render and reject any `field` outside it (preferred, secure + zero-config); or make `allowed_model_fields` fail-closed (required). Keep `FORBIDDEN_MODEL_FIELDS` only as defense-in-depth. Add a regression that a non-`dj-model` public attribute (e.g. `is_admin`) is rejected while a bound field still updates.\n\n### Workarounds\nSet `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.\n\n### References\nReproducer + finding writeup retained privately by the maintainer.","status":"published","published":"2026-09-16T13:55:48Z","modified":"2026-09-16T13:55:49Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-915"],"updated_at":"2026-11-02T14:41:17.136201Z"}],"nvd":[{"id":"CVE-2026-61598","description":"djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. Prior to version 1.0.7, `djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the LiveView base MRO, so every LiveView exposes it. It `setattr`s a view attribute whose name is client-supplied (`field`), gated only by: reject `_`-prefixed names; reject a 14-entry denylist of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which defaults to None = allow all; and `hasattr` existence. As a result, a client can set any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker. This issue is fixed in djust 1.0.7. As a workaround, set `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.","status":"published","published":"2026-09-16T14:17:06.827Z","modified":"2026-09-30T17:51:56.193Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"}],"cwes":["CWE-915"],"updated_at":"2026-11-02T14:12:48.630594Z"}],"osv":[{"id":"CVE-2026-61598","title":"Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler","description":"djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. Prior to version 1.0.7, `djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the LiveView base MRO, so every LiveView exposes it. It `setattr`s a view attribute whose name is client-supplied (`field`), gated only by: reject `_`-prefixed names; reject a 14-entry denylist of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which defaults to None = allow all; and `hasattr` existence. As a result, a client can set any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker. This issue is fixed in djust 1.0.7. As a workaround, set `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.","status":"published","published":"2026-09-16T13:59:34.306Z","modified":"2026-10-28T02:31:02.054838058Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-915"],"updated_at":"2026-11-02T15:45:12.933503Z"},{"id":"GHSA-cc7c-9jff-58wj","title":"djust: Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler","description":"### Impact\n`djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the **LiveView base MRO**, so every LiveView exposes it. It `setattr`s a view attribute whose **name is client-supplied** (`field`), gated only by: reject `_`-prefixed names; reject a **14-entry denylist** of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which **defaults to None = allow all**; and `hasattr` existence.\n\nResult: a client can set **any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template**. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker.\n\n**Severity High** for apps that hold authorization/ownership/business state in public view attributes (the normal djust pattern) -\u003e state tampering / IDOR / authz-flag manipulation; Low otherwise. Default-on across every LiveView. For a public (no-login) view an anonymous client can mass-assign; for an authenticated view a logged-in user can tamper their own session's view state (the IDOR/authz vector when downstream handlers act on it without re-authorizing).\n\nReproduced: a view with `account_id/is_admin/total_price` (none bound with dj-model) had all three set via `update_model` calls.\n\n### Patches\nRestrict the default handler to fields actually exposed via `dj-model=`: have the template renderer record the bound-field set per render and reject any `field` outside it (preferred, secure + zero-config); or make `allowed_model_fields` fail-closed (required). Keep `FORBIDDEN_MODEL_FIELDS` only as defense-in-depth. Add a regression that a non-`dj-model` public attribute (e.g. `is_admin`) is rejected while a bound field still updates.\n\n### Workarounds\nSet `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.\n\n### References\nReproducer + finding writeup retained privately by the maintainer.","status":"published","published":"2026-09-16T13:55:48Z","modified":"2026-10-01T17:55:38.938962299Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-915"],"updated_at":"2026-11-02T15:46:44.927642Z"},{"id":"PYSEC-2026-4042","title":"djust: Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler","description":"### Impact\n`djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the **LiveView base MRO**, so every LiveView exposes it. It `setattr`s a view attribute whose **name is client-supplied** (`field`), gated only by: reject `_`-prefixed names; reject a **14-entry denylist** of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which **defaults to None = allow all**; and `hasattr` existence.\n\nResult: a client can set **any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template**. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -\u003e bool True), aiding the attacker.\n\n**Severity High** for apps that hold authorization/ownership/business state in public view attributes (the normal djust pattern) -\u003e state tampering / IDOR / authz-flag manipulation; Low otherwise. Default-on across every LiveView. For a public (no-login) view an anonymous client can mass-assign; for an authenticated view a logged-in user can tamper their own session's view state (the IDOR/authz vector when downstream handlers act on it without re-authorizing).\n\nReproduced: a view with `account_id/is_admin/total_price` (none bound with dj-model) had all three set via `update_model` calls.\n\n### Patches\nRestrict the default handler to fields actually exposed via `dj-model=`: have the template renderer record the bound-field set per render and reject any `field` outside it (preferred, secure + zero-config); or make `allowed_model_fields` fail-closed (required). Keep `FORBIDDEN_MODEL_FIELDS` only as defense-in-depth. Add a regression that a non-`dj-model` public attribute (e.g. `is_admin`) is rejected while a bound field still updates.\n\n### Workarounds\nSet `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.\n\n### References\nReproducer + finding writeup retained privately by the maintainer.","status":"published","published":"2026-10-01T16:38:30.91571Z","modified":"2026-10-01T17:45:02.0574633Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T15:52:57.255153Z"}]}}
