Releases and security
2.1.9 - 2026-05-28
Security — runique (admin, auth)
-
SQL injection in admin list filters (high): the column name from URL parameters (
?filter_<col>=val) was interpolated directly intoExpr::cust(format!("CAST({} AS TEXT) = '{}'", col, ...))without any validation. An authenticated staff user with minimal view rights could execute arbitrary SQL against the database. Fixed: the generator now emits two static whitelists (SORT_COLS,FILTER_COLS) built at code generation time from the declaredlist_displayandlist_filtercolumns. Any column name not in the whitelist is silently discarded before reaching the query. -
Session fixation on login (medium):
login()did not callsession.cycle_id()on privilege elevation (anonymous → authenticated). An attacker who planted a session ID in the victim's browser before login could reuse it after authentication. Fixed:session.cycle_id().awaitis now called on every privilege elevation (new session or user switch). Mitigated in practice bySameSite=Strict+HttpOnlycookie attributes, but the standard mitigation was absent. -
Admin write access granularity (medium):
check_write_accessreturnedtrueif any ofcan_create,can_update, orcan_deletewas set. A staff user granted onlycan_createcould also edit and delete any record. Fixed: three separate guards (check_can_create,check_can_update,check_can_delete) are now applied per operation and per HTTP method. Bulk POST actions are also gated per action type (delete→can_delete, others →can_update). -
IDOR —
can_update_own/can_delete_ownnot enforced (low): the "own" permission flags existed in the permission model and were injected into templates, but the CRUD closures(db, id)/(db, id, data)carried no user identity, making ownership verification structurally impossible. Edit and delete routes silently fell back to allowing any record. Fixed: a newown_field: "field_name"DSL option declares the JSON field used for ownership comparison. When a user hascan_update_own(withoutcan_update), the handler fetches the record viaget_fnand comparesrecord[own_field]againstcurrent_user.id. Ifown_fieldis not declared, "own" permissions are blocked by default (safe fallback).
Added — runique (forms, debug)
eprintln!debug output for the full form processing pipeline: whenDEBUG=trueand the correspondingFormTracingfield is configured, each stage now emits both atracingstructured event (filtered by subscriber level) and aneprintln!directly to stderr (bypasses the subscriber filter). Stages covered: field registration,set_valueper field (POST), checkbox normalization, validate per field, validate result, finalize per field, render per field.
Added — runique (admin DSL)
own_fieldinadmin!{}: new optional DSL key that declares the record ownership field forcan_update_own/can_delete_ownenforcement. Example:own_field: "user_id".
Security — runique (forms)
save()/save_as()guard against skipped validation (low): a developer could callform.save()without a prior successfulis_valid()call, bypassing field validation, CSRF token verification, andclean()business rules entirely. Fixed: both methods now returnErr(DbErr::Custom(...))immediately ifis_valid()was not called or returnedfalse. The check is performed via the internalis_save_allowed()method (!force_invalid && validated && !has_errors()). A#[doc(hidden)]Forms::mark_validated()helper is provided for tests that verify save/hook behavior in isolation.