Repository navigation
fix: ORM safety fixes [todo:S4,B1,S5] - #23
Conversation
Postgres and SQLite do not take ORDER BY, LIMIT or JOIN in UPDATE or DELETE. When a query has one, pick the rows by primary key in a sub query.
A model with no fillable and no guarded took every key of a map, so User().create(req.body) could set id or is_admin. Now guarded is ['*'] by default, like Laravel. Only fillable columns are taken from a map. Set guarded to [] to take every key again.
useSSL: true and ssl=true gave sslMode require, which takes any certificate, so a man in the middle could read the traffic. Now they give verify-full. sslmode=require in a url, or extra['sslMode'] set to require, still encrypts without the check.
Bot reviewVerdict: CHANGES NEEDED The three fixes are right and the tests prove them. One thing must change before merge: the new S4 default drops data with no error. Problems
Things I checked that are OK
Results
|
A model with no fillable dropped every key of a map. So create() saved an empty row and update() changed nothing, with no error. Now it throws MassAssignmentException with the model and the keys, like Laravel.
The owner wants no breaking changes. useSSL: true stays sslMode require. To check the certificate, set extra['sslMode'] to verify-full.
groupBy() and having() mean nothing in an update or delete, and the sub query with them fails on Postgres and MySQL 8, so they now throw an ArgumentError. MySQL with no join and no offset keeps ORDER BY and LIMIT as before, so a table with no primary key still works there.
Fixer replyThe owner decided: no breaking changes, except S4, which should throw and not save an empty row.
Checks: Generated by Claude Code |
Bot reviewOwner decision on point 1: throw an error. When a model has no |
Bot reviewVerdict: OK No blocking problems. The fixes follow the owner's decisions. Two small notes below. They can be done in this PR or later. Small notes (not blocking)
What I checked
Results
|
A model with no fillable now throws MassAssignmentException when it gets a map. See dartondox/dox-orm#23.
Fixes in dox_orm. One commit per item, plus follow-up commits for the first review.
S4: mass assignment is open by default
Problem. With
fillableandguardedboth empty (the old default),create(),insert(),insertMultiple(),upsert(),update(),firstOrNew(),firstOrCreate(),updateOrCreate()andincrement()extras took every key of a map.What it causes.
User().create(req.body)could setid,is_adminor any other column.Fix.
guardedis now['*']by default, like Laravel. A model with nofillablethrowsMassAssignmentExceptionwhen it gets a map with keys. The error names the model and the keys, and says how to fix it. It never saves an empty row, andupdate()never silently does nothing. A model withfillabledrops the other keys, as before.guarded => const []takes every key, as before. Setting fields in code andsave()are not changed. Soft delete, restore andsave()do not go through the guard.Tests: "a model is guarded by default" in
test/security_test.dart(create,update,firstOrCreate,insertMultiple). The test models setguarded => [], because the tests pass raw maps on purpose.B1: update() and delete() with ORDER BY, LIMIT or JOIN
Problem.
update()anddelete()usedgetCommonQuery(). It adds JOIN, GROUP BY, ORDER BY and LIMIT afterUPDATE t SET ...orDELETE FROM t.What it causes. Postgres and SQLite reject this SQL. For example
Blog().orderBy('id').limit(1).delete()fails. A JOIN in that place is also wrong on MySQL.Fix. New
helper.getWriteQuery():WHERE id IN (SELECT id FROM (SELECT t.id AS id FROM t JOIN ... WHERE ... ORDER BY ... LIMIT ...) AS dox_rows). The extra wrap lets MySQL read the table it changes and use LIMIT.ORDER BY ... LIMIT, the same SQL as before. So a table with no primary key still works there.groupBy()andhaving()withupdate()ordelete()throw anArgumentError. This SQL was never valid.Test:
test/write_order_limit_join_test.dart. It passes on Postgres, SQLite and MariaDB.S5 (dox-orm part): not changed
The owner wants no breaking change here. Turning on the certificate check for
useSSL: truewould break apps on databases with a private CA (for example AWS RDS) until they setsslRootCert. SouseSSL: truestaysrequire, andverify-fullstays opt-in withextra['sslMode']. The first commit changed it, and a later commit reverts it. The Rust part of S5 (an unknownverifyvalue fails closed) is in dartondox/dox-postgres-native#9. It does not break anything.Breaking change
S4 only (the owner accepted it). A model with no
fillablenow throwsMassAssignmentExceptionwhen it gets a map. To fix, addfillable:or keep the old behavior (not safe with user input):
The CHANGELOG has an "Unreleased" entry. Docs: dartondox/dox#286.
Checks
dart analyze: no issues (dox-orm and dox-migration).dart test --concurrency=1: all pass on SQLite, Postgres 16 and MariaDB.