Releases and security

2.1.13 - 2026-06-01

Fix — runique (admin templates)

  • Admin sort links lost active filters: clicking a column header to sort reset all active list_filter values. The sort links in list_partial.html did not include {{ filter_qs }}, unlike the pagination links. Fixed: filter_qs is now appended to the href and hx-get of both the id column and dynamic column headers.

  • Admin filter sidebar values unordered: generated filter_fn closures retrieved distinct values then re-sorted them in Rust with sort_by (numeric-then-lexicographic ascending), overriding any DB-side ordering. Fixed: the in-memory sort_by is removed; the DISTINCT query now uses ORDER BY CAST(col AS TEXT) DESC so values arrive pre-sorted from the DB — dates show newest first, string values show highest alphabetically first.

Fix — runique (admin templates, admin CSS)

  • Double-escaping of string values in admin list and detail views: {{ value | escape }} was used in list_partial.html and detail.html while Tera's autoescape was already active. The escape filter converts / to /, then autoescape re-encodes & to &, so the browser rendered the literal text / instead of /. Same issue in the hidden filter inputs (value="{{ val | escape }}"), which would have caused filter comparisons to fail for values containing /. Fixed: all three occurrences replaced with plain {{ value }} / {{ val }} — autoescape alone is sufficient for XSS protection here.

  • Admin filter sidebar CSS used mismatched selectors: the CSS section for the list filter panel used hyphen-style selectors (.admin-filter-sidebar, .filter-group, .admin-list-layout, etc.) while the templates had already been refactored to BEM (.admin-filter__sidebar, .admin-filter__group, .admin-list__layout, etc.). The filter panel had no effective styling. Fixed: the entire section is rewritten with BEM selectors; mobile now uses an offcanvas pattern (position: fixed; right: -300px → .mobile-open { right: 0 }) with a backdrop overlay.

Fix — runique (templates)

  • Internal templates not autoescaped — XSS vector on admin form fields: Tera's autoescape is activated for logical keys ending in .html or .xml. Internal framework templates were registered without the .html suffix, so their variables were rendered raw. In particular, {{ form_fields.html }} (which holds Runique-generated form HTML) was not autoescaped and would have been misinterpreted as a missing variable. Fixed: all internal template keys now include the .html suffix; the template preprocessor (process_content) rewrites {{ form_fields.html }} to {{ form_fields.html | safe }} via ADMIN_FORM_HTML_REGEX — the only variable exempt from autoescaping because it is always Runique-generated HTML, never user input. The {% extends %} keys in doc examples were updated accordingly ("admin/admin_template.html", "admin_base.html").

  • resolve_og_image: CSS version hash applied to media URLs, and potential double slash: the function appended ?v=<hash> to the OG image URL unconditionally when a CSS build token was present. The hash is computed from static assets (CSS/JS) at build time and has no relation to the media file's content — applying it to an uploaded image is semantically wrong and breaks scraper cache-busting when the image changes without a redeploy. Additionally, if an allowed_hosts entry contained a trailing slash, the host prefix (https://host/) concatenated with an og_image starting with / produced a double slash (https://host//media/...). Fixed: the ?v= is removed entirely from OG image URLs; trim_end_matches('/') is applied to the host and trim_start_matches('/') to the image path before concatenation; absolute URLs (http:// / https://) are returned early without modification.