{"schema":"threatline.record/2","record":{"id":"GHSA-6979-65wv-7pxq","aliases":["GHSA-6979-65wv-7pxq"],"summary":"Nested properties let a form update, reassign or delete another record's child rows","details":"### Impact\nWith `nestedProperties()`, the nested save looks up each posted associated record by the primary key submitted in the request, and does not check that the record belongs to the parent being saved. A user who can submit the form for their own parent record can therefore include the key of a record that belongs to someone else:\n\n- **`hasMany`**: the posted child row is **deleted** (when the association uses `allowDelete=true`, the usual setup for `hasManyCheckBox` join models), or **updated** with the posted values. Because the nested save then sets the child's foreign key to the current parent, the row is also **moved** under the attacker's parent.\n- **`hasOne`**: the same delete, update and move of another parent's record.\n- **nested `belongsTo`**: any record of the associated model can be **edited** by posting its key.\n\nFor composite primary keys that are not made up of foreign keys, the posted key values of new child rows are also kept, so a request can choose the identity of a new row.\n\n### Patches\nFixed in 4.2.0. A nested record found by a posted key is only used when it belongs to the parent being saved (scoped by the parent's persisted key, so posting the parent's own id or foreign key can't retarget it); otherwise it is treated as not found. Posted primary-key values on new child rows are cleared, except the join-identity foreign keys.\n\n### Workarounds\n- Don't use `allowDelete=true` on nested associations whose child keys come from untrusted input.\n- Before saving, verify in the controller that every posted nested child key belongs to the parent being edited, or rebuild the nested struct server-side from the parent's own children.\n","categories":["Unclassified"],"product":"wheels","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/wheels-dev/wheels/security/advisories/GHSA-6979-65wv-7pxq","source_modified_at":"2026-10-12T00:02:28Z","fetched_at":"2026-10-24T23:06:24.700873Z","package":{"ecosystem":"other","name":"wheels"},"range":">= 1.1, <= 4.1.2"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/wheels-dev/wheels/security/advisories/GHSA-6979-65wv-7pxq","source_modified_at":"2026-10-12T00:02:28Z","fetched_at":"2026-10-24T23:06:24.700873Z","package":{"ecosystem":"other","name":"wheels"},"version":"4.2.0"}],"cvss":8.1,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-12T00:02:27Z","modified_at":"2026-10-12T00:02:28Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/wheels-dev/wheels/security/advisories/GHSA-6979-65wv-7pxq","fetched_at":"2026-10-24T23:06:24.700873Z","source_modified_at":"2026-10-12T00:02:28Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-12T00:02:28Z","content_hash":"0040ef889b89a8122a0e3a2c9c946c30312c262ce61c74e3a3e853318796d9df"}}
