Changelog
v2.1.9 — 2026/05/30
Fix IDOR
can_update_own / can_delete_own not 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 new own_field: "field_name" DSL option declares the JSON field used for ownership comparison. When a user has can_update_own (without can_update), the handler fetches the record via get_fn and compares record[own_field] against current_user.id. If own_field is not declared, "own" permissions are blocked by default (safe fallback)
Fix Admin write access granularity
check_write_access returned true if any of can_create, can_update, or can_delete was set. A staff user granted only can_create could 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)
Fix Session fixation on login
login() did not call session.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().await is now called on every privilege elevation (new session or user switch). Mitigated in practice by SameSite=Strict + HttpOnly cookie attributes, but the standard mitigation was absent
Fix SQL injection in admin list filters
the column name from URL parameters (?filter_=val) was interpolated directly into Expr::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 declared list_display and list_filter columns. Any column name not in the whitelist is silently discarded before reaching the query
Ajouté runique (forms, debug)
eprintln! debug output for the full form processing pipeline: when DEBUG=true and the corresponding FormTracing field is configured, each stage now emits both a tracing structured event (filtered by subscriber level) and an eprintln! directly to stderr (bypasses the subscriber filter). Stages covered: field registration, set_value per field (POST), checkbox normalization, validate per field, validate result, finalize per field, render per field.
Ajouté Admin
own_field in admin!{}: new optional DSL key that declares the record ownership field for can_update_own / can_delete_own enforcement. Example: own_field: "user_id"
Ajouté Security — runique (forms)
save() / save_as() guard against skipped validation (low): a developer could call form.save() without a prior successful is_valid() call, bypassing field validation, CSRF token verification, and clean() business rules entirely. Fixed: both methods now return Err(DbErr::Custom(...)) immediately if is_valid() was not called or returned false. The check is performed via the internal is_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.