Upgrading to 2.9.3? Required operator actions, the post-upgrade maintenance tasks (with copy-paste commands), behaviour changes, and notes for theme/plugin developers are collected in docs/upgrading-to-2.9.3.md. Breaking changes are flagged inline below.
-
Bug fix: The post editor could load with an empty body on a cold server when the edit form was opened in a background browser tab, and saving then overwrote the post with the empty body; the editor now restores the server-rendered content when it initializes empty. #1273.
-
Performance fix: On installs with many sites, the first request after a server (re)start could 404 or be served against a still-building route table. Route drawing no longer scales with the number of sites, it is drawn at boot in development so no request is served mid-draw, and admin responses now send
Cache-Control: no-store. #1272. -
Security fix: A role holding only the
mediapermission could set any same-site user's avatar — including an administrator's — viamedia#cropwithsaved_avatar, gated only by:manage, :media. Writing another user's avatar now requires:manage, :users; a caller may still set their own with the media permission alone. Completes the media-crop finding (cross-site half shipped in #1267). Reported by Guilherme Facini. #1271.- Breaking change: a role with
:manage, :mediabut not:manage, :userscan no longer set other users' avatars via crop. Grant:manage, :usersto a role that needs to manage other users' avatars.
- Breaking change: a role with
-
Security fix: The upload scan chose its ruleset from the filename, so identical bytes were refused as
x.svgyet stored asx.html. The ruleset now follows how the file renders: markup is parsed andon*attributes rejected by shape,.svgzdecompressed under a bounded read, and executable scripts requiremedia_unfiltered_upload. Reported by Guilherme Facini. #1269. Upgrade notes.- Breaking change: a role without
media_unfiltered_uploadcan no longer upload.js,.mjs,.cjs,.wasmor.swffiles. Grant that permission to a role that needs them.
- Breaking change: a role without
-
Release process: Releases are now cut by a manually-started Release GitHub Actions workflow that verifies, builds, and publishes the gem, then tags the commit and creates the GitHub release from this file's section;
lib/camaleon_cms/version.rbis the single source of truth. Procedure: docs/releasing.md. #1268. -
Security fix: Low-severity hardening bundle — the captcha challenge is drawn from a CSPRNG;
save_commentfails closed on crafted input and no longer follows an off-hostReferer; the media crop avatar target is resolved within the current site; andfront_cachekeys its cache on a lossless digest (no URL collisions) and skips malformed path patterns. #1267. Upgrade notes. -
Security fix: Four medium admin hardenings —
media#uploadno longer skips CSRF (the uploader now sends the token); draft custom-field options are confined to the post type's registered slugs; the nav-menu reorder resolves its destination through the current site; and the email-confirmation token is consumed on use. #1266. Upgrade notes. -
Security fix: The remaining destructive admin endpoints no longer answer GET (audit M6): nav-menu item delete requires DELETE, the legacy
appearances/widgetsdelete routes no longer admit GET, andmedia/cropaccepts only POST. A routing audit spec enforces that no mutation-named admin route answers GET/HEAD. #1265. Upgrade notes. -
Security fix: Destructive admin actions no longer ride GET links (which Rails' CSRF protection exempts): trashing/restoring posts, comment moderation, plugin toggle/upgrade, impersonation, test email, theme sample-data import, and logout now act only over PATCH/POST. #1264. Upgrade notes.
-
Security fix: Post content from untrusted authors is rejected on save when it contains disallowed HTML, instead of being silently sanitized: the save fails with an error naming the remedy, and stored content always equals authored content. Admins and roles holding
post_content_unfiltered_htmlare unaffected;unfiltered_content!opts server-side pipelines out. #1263. Upgrade notes. -
Security fix: Unlocking a password-protected post now happens over POST with a session-side unlock and a constant-time comparison. The prompt used to submit over GET as plain text (exposing the password in URLs, history, logs and
Referer) and compared with==; the query-string parameter no longer unlocks anything. #1263. Upgrade notes. -
Security fix: Password-protected posts no longer leak their body through excerpts.
the_excerpt(listings, search, RSS) was still derived from the body; a locked post's excerpt is now a neutral notice, and such posts are excluded from thefront_cachepage cache. #1263. -
Security fix: The admin auth cookie is now
HttpOnlyandSecure(over SSL), and logging out rotates the server-sideauth_token. The bearer token was previously readable by JavaScript and sent in the clear, and a cookie copied before logout stayed valid. #1263. Upgrade notes. -
Security fix:
field_attrscustom-field values are now gated at save like editor values and rendered verbatim, closing a second stored-XSS path. The gate scans the decoded members of any JSON shape, so markup hidden by unicode escaping is refused like literal markup; nothing is sanitized. #1263. -
Security fix: Dangerous custom-field values (rich-text
editor, URL types) are rejected on save instead of stored, closing a stored-XSS path. A value an untrusted author may not write (scripts, event handlers, embeds,javascript:URLs) is refused with an error naming the field; trusted authors are unaffected. #1263. Upgrade notes. -
Security fix: Passwords must now be at least 8 characters (NIST-aligned, length-only), applied whenever a password is set; a profile update that leaves the password untouched is unaffected. #1263.
-
Security fix: The forgot-password endpoint no longer reveals whether an email is registered, nor floods an inbox: it returns one neutral message either way and sends at most one reset email per account per 5-minute window. #1263.
-
Security fix: Admin login no longer leaks whether a username exists through response timing — a missing username now spends one bcrypt comparison too, closing a username-enumeration oracle. #1263.
-
Security fix: Two admin endpoints now enforce authorization.
AdminController#searchscoped nothing, so any admin-area user could enumerate every post title/slug in every status; it now scopes results to the caller's authorized post types, categories and tags.Posts::DraftsController#indexnow requires:postsauthorization. #1262. -
Security fix: The admin post index no longer leaks another post type's posts through a taxonomy filter.
?taxonomy=category|post_tagreplaced the scope with the taxonomy owner's site-wide posts; the filter is now resolved within the authorized post type (a foreign id 404s) and intersected with its posts. #1262. -
Security fix: Credential parameters are now filtered from the Rails logs. The engine set no
config.filter_parameters, so SMTP/S3 secrets, user and protected-post passwords, and token parameters were logged in cleartext on hosts without their own filter. #1262. -
Security fix: Private uploads to S3 are now stored with an owner-only (
private) ACL instead ofpublic-read, which left a private file world-readable at a guessables3://bucket/private/<name>URL. Public uploads are unchanged. #1262. Upgrade notes. -
Fix: The upload content scanner now accepts embedded raster images encoded as
data:image/*URIs (e.g. an Inkscape/Figma SVG with<image xlink:href="data:image/png;base64,…">), previously rejected as false positives. Dangerousdata:URIs andjavascript:/vbscript:remain blocked. #1261. -
Security fix: The uploaded-SVG scanner now rejects uppercase/mixed-case event-handler attributes (e.g.
ONCLICK), and no longer raises on an SVG declaring a non-UTF-8 encoding — such a file is scanned normally instead of failing the upload. #1261. -
Security fix: The uploaded-SVG scanner now catches blocked URI schemes hiding a TAB/LF/CR gap inside the scheme name (e.g.
java	script:), which a browser strips back before executing. Both scheme checks now shareContentSecurity's gap-tolerant pattern and entity normalization. #1261. -
Security fix:
sort_by_fieldno longer interpolates its sort-direction argument into the SQLORDER BY. It is a public API, so a user-controlled direction could 500 or append attacker-chosenORDER BYterms (a blind ordering oracle); the direction is now whitelisted toASC/DESCand the value column ordered as a quoted identifier. #1260. Upgrade notes. -
Fix:
sort_by_fieldandfilter_by_fieldno longer append phantom unsaved records to the collection — class discovery now uses the relation'sklassinstead ofbuild. #1260. -
Change: The
user_before_registerhook now fires during registration (previously a silent no-op), after the register captcha passes; a handler can veto a signup by settingr[:stop_process]. #1259. Upgrade notes. -
Security fix: Closed an open redirect in the admin session flows.
safe_redirect_urltreated a parsed-host-blank URL such asreturn_to=///evil.comas same-origin; it now follows a host-blank destination only when it is a genuine same-origin path, and a host-matching absolute URL only overhttp/https. #1258. Upgrade notes. -
Fix: The
user_before_registerhook now fires during account registration — it was dispatched in a form that resolved to a no-op, so a listening plugin never ran. #1258. -
Security fix: Only an admin may control an admin account. Holding
:manage, :userslet a non-admin mint or strip theadminrole, or reset an admin's password/email to hijack the account; granting/removingadminand changing an existing admin's password, email or username now require being an admin (audit H10). #1257. -
Security fix: Admin login is now brute-force throttled per client IP. The "under attack" decision was per-session, so dropping the session cookie each request evaded the captcha (H1); the bundled
attackplugin also keyed on the session id and inserted a row per request (H2). Failures are now counted per IP in the cache, with a captcha then an HTTP 429 lockout. #1256. Upgrade notes. -
Security fix: The captcha is now a single, single-use challenge of bounded length.
GET /captcha?len=fed its length straight into generation (a huge value tied up the worker;1shrank the answer to a letter), and every issued answer accumulated in the session and was accepted forever (H3/H4). The length is clamped and a solved captcha is consumed. #1255. Upgrade notes. -
Fix: Saving a frontend comment no longer 500s when the client sends no
User-Agentheader; it is recorded nil-safely without mutating the request string in place. #1255. -
Security fix: Ending admin impersonation now requires the impersonating admin's password. The Logout link previously restored the stashed admin cookie for whoever held the session, so an admin who walked away mid-impersonation on a shared browser let the next occupant become admin. #1254.
-
Security fix: The session is now reset on a genuine sign-in and on logout. The impersonation stash outlived the admin on a shared browser, so a later user inherited it and was handed the admin's cookie on logout (audit H6);
login_user/cama_logout_usernowreset_session. #1253. Upgrade notes. -
Security fix: The admin custom-fields "list" endpoint no longer writes a post's categories on a GET request — with
categoriesomitted it deleted every category relationship, reachable by CSRF. The write now runs only on a CSRF-verified POST, and the endpoint authorizes the caller against the resolved record (audit M6). #1252. -
Fix: The admin
confirm_emailroute now accepts PATCH — itsvia:list carriedpath(a typo forpatch), so a PATCH request fell through to the "invalid route" handler. #1252. -
Security fix: Foreign keys that carry a record's tenancy are no longer mass-assignable across sites. A post type's, category's or tag's
parent_id, and a widget assignment'ssidebar_id/widget_id, were reassignable via hidden fields; these are now dropped from the permits or validated against the current scope (audit H8/H9). #1251. -
Security fix: The admin user-list and account-creation actions now require
:manage, :userseven when the caller injects their own?user_id=. Thevalidate_roleself-exemption was short-circuiting the capability check on these collection actions, exposing the full user table and account creation (audit H7). #1250. -
Security fix: The breadcrumb, HTML sitemap, and default theme's taxonomy links now HTML-escape the post/category URL they interpolate into an
href, closing a stored-XSS via a slug that closes the attribute (audit H11). #1249. Upgrade notes. -
Fix: Administrator password reset works again. On Rails 7.1+
has_secure_passwordgenerated apassword_reset_tokenmethod that shadowed Camaleon's same-named column, so every reset dead-ended on "URL incorrect". Reset links now resolve, expire, are single-use, and confined to the account's own site. #1248. Upgrade notes. -
Security fix: The installer no longer publishes a default administrator password and can no longer be run by an anonymous visitor on a fresh deploy (audit C1). New sites mint
adminwith a random password shown once, and the first-run installer is gated by a setup token fromCAMALEON_SETUP_TOKEN. #1246. Upgrade notes. -
Fix: The captcha tag now honors a caller-supplied image style and string-keyed input attributes, and the admin "no categories/tags created" message shows a translated label instead of the raw slug. Regression audit L14/L15/L17. #1245. Upgrade notes.
-
Fix: A failed avatar crop now shows an error instead of silently blanking the saved avatar; the theme generator and the repair rake tasks print progress to the terminal (via a shared
CamaleonCms::TaskReporter). Regression audit L3/L16/L11/L18. #1244. Upgrade notes. -
Security fix: SVG uploads and
/media/SVG responses are now matched by extension case-insensitively, so an uppercase-extension SVG (image.SVG) is scanned by the SVG parser and served with thenosniff/script-src 'none'headers like a lowercase one. Regression audit L5/L6. #1243. -
Fix: The admin media browser paginates at the database again instead of loading a folder's entire media list into memory per page view; sites with large media folders load the manager far faster. Regression audit M27. #1242.
-
Security fix: An uploader without
media_unfiltered_uploadcould use abefore_uploadhook to substitute bytes the scanner never saw; the pipeline now re-scans substituted content for untrusted uploaders. Also: a blank/0"Max file size" no longer rejects every upload, and the content scan folds its event-handler patterns into one pass. Regression audit N2/M26/M24/M25. #1241. -
Security fix: The two admin theme-settings paths that still saved custom-field values raw now go through the same allowed-slugs filter as every other admin custom-field save (added in 2.9.2), closing a mass-assignment gap reachable by any role with the theme-settings capability. #1240. Upgrade notes.
-
Fix: An admin custom-field save whose submitted slugs were all unregistered under the target scope wiped the object's stored values; the filter now drops those empty groups, so the save is a no-op instead. #1240.
-
Fix: User custom fields work again for host apps configuring a namespaced
user_model(e.g.'Admin::User'): the settings form, edit page, and save filter now derive the demodulized scope name, so submitted user field values are no longer silently discarded (broken since 2.9.2). Regression audit N5. #1239. Upgrade notes. -
Fix: The
object_classscope naming for metas, custom fields, and field groups returns to the demodulized 2.9.2 contract ('Main','User'), reverting an unreleased rename that stranded existing rows; the bulk-delete cascade on definition associations is also removed. Regression audit M9/M8/N4. #1238. Upgrade notes. -
Fix: Three legacy model-API surfaces restored:
Media.find_by_key(alias ofby_key),Post#unassign_category(with reliable counter refresh), and the default ascending-id ordering onNavMenu/NavMenuItem. Regression audit M5/M6/M10/M4/M11. #1237. -
Removed: The
ActiveRecordExtrasmixin and itsupdate_or_create,update_or_create!andassign_or_newmethods. Use the Rails idiom (find_or_initialize_by(...).tap { … save }); the 2026-08 ecosystem sweep found no external caller. Regression audit M4. Upgrade notes. -
Fix: Three session-adjacent regressions: the admin login/register/forgot pages render in the site's language again (with
?locale=honored); same-hostreturn_tois followed regardless of host case; and malformed?user_id[]=/?locale[]=requests no longer 500. Regression audit M15/M17/M14. #1236. -
Security fix: Draft autosaves could be forged to parent a new draft under an arbitrary post, and an edit-own-only user was locked out of autosaving their own post.
post_parentis now create-only (derived from the validatedpost_id), and draft buffers are per-user, authorized against the post being edited. Regression audit M12/M13. #1235. Upgrade notes. -
Fix: Restores seven admin and frontend runtime-compatibility contracts broken by the
CurrentRequestand admin-menu refactors (2-argpost_type_list_taxonomy, signed-outcama_current_usermemoization, quote-safe admin menudata-*, and more). Regression audit M16/M18–M20/M22/M23. #1234. -
Fix: On multisite installs, background and cross-site email (password reset, email confirmation, admin notifications) raised
NameErrorand sent nothing —SiteHelper#current_sitestopped honoring the@current_sitethatHtmlMailersets. Single-site installs were unaffected. #1233. -
Docs: Codified the security-capability-gating rule — a security-sensitive action is admin-only by default and gated for non-admins by a dedicated, off-by-default, fail-closed permission — as an OpenSpec capability, a
docs/security/permissions.mdsection, and adocs/ai/criteria.mdcheckpoint. #1231. -
Docs: Removed the defunct Autocomplete plugin from the README plugin list and example Gemfile, and recorded the external plugin/theme binding surface as the
ecosystem-plugin-bindingsOpenSpec capability with adocs/ai/ecosystem.mdinventory. #1229. -
Security fix: A
media-only user could upload an SVG the ruleset accepted, then re-crop it under an.htmlname to have the identical bytes served astext/html, unscanned. Scanning is now gated on a newmedia_unfiltered_uploadrole permission. Also fixes seven defects found reviewing #1223–#1227. #1228. Upgrade notes. -
Fix: Validating a post without an explicit slug raised
FrozenErrorinstead of reportingSlug can't be blank, so creating posts programmatically was impossible.String#translationsno longer memoizes on a frozen receiver. Long-standing, not a regression. #1227. -
Fix: The upload hardening in #1198–#1211 rejected legitimate work: uploads staged outside
public//temp failed, animated SVGs and re-crops of stored files were refused, and prose with a word before a colon tripped the scheme detector. Trusted callers can widen roots per call, animation elements are accepted, and scheme detection matches the browser rule. #1226. Upgrade notes. -
Fix: Save-time post-content sanitization (#1206) destroyed legitimate markup for untrusted authors and sanitized every programmatic save; untrusted
Post#contentnow keeps structural, non-executable markup while stripping scripts and handlers. Also restores activation of the bundledcamaleon_firsttheme. (Superseded by the reject-on-save gate in #1263.) #1225. Upgrade notes. -
Fix: Fallout from the native-STI conversion (#1173): rows with custom
taxonomy/post_classraisedSubclassNotFound; deleting a user orphaned their comments and destroyed their widgets; and wronginverse_ofdeclarations made.ownerraise. All restored to 2.9.2 behavior. #1224. Upgrade notes. -
Fix: Six high-severity regressions introduced after 2.9.2, each pinned by a reproducing spec: engine boot on nginx/Apache static hosts, a plugin/theme config without a
helperskey,GET /search500, mixed-case PostgreSQL search,/sitemap.htmlcrashes, and case-sensitive login/reset. #1223. -
Fix: Slug-uniqueness validation was silently inert on Rails 7.0+ —
UniqValidator/PostUniqValidatorpushed ontoerrors[:base], which modern Rails discards, so duplicate slugs and recursive page hierarchies saved without complaint. Errors are registered witherrors.addagain. #1222. Upgrade notes. -
Tests: Feature specs now sign in by setting the auth cookie instead of driving the login form, and the sign-in actually verifies credentials; the form-driven flow lives on in
admin_form_sign_in. #1221. -
Tests: The suite installs a single shared Camaleon site per run instead of before nearly every example, cutting a local full run from ~18 minutes to ~5;
init_site(fresh: true)keeps a per-example site for multi-site UI specs. #1220. -
Fix: Admin headings and tooltips showed raw HTML entities instead of the characters they encode, so a site named
Ben & Jerry'sreadBen & Jerry's. Affected site settings, the post edit form, the sites form, and the categories/tags indexes. #1219. Upgrade notes. -
Security fix: Stored XSS in two server-generated fragments.
PostDecorator#the_statusandCustomFieldGroup#get_captionbuilt markup by interpolation and were rendered throughrawwithout escaping the spliced values; both now escape and return aSafeBuffer. Thanks, Enrik Mustafa, for pressing the re-check. #1218. Upgrade notes. -
Security fix: Cross-site custom field group injection. The custom-fields controller wrote the submitted
assign_groupplacement without checking ownership, so a manager on one site could stamp a group with another site's placement id and render it there, invisible to that site's admins (multi-site installs only). #1217. Upgrade notes. -
Fix: The site settings form rendered every custom field group in the site, not just the site's own, so required fields on other content types blocked submit and were discarded anyway.
Sitenow reads placement (object_class/objectid) instead of tenancy (parent_id). Reported as #1124. #1216. Upgrade notes. -
Security fix: HTML injection in
Hash#to_attr_formatand the bundledcama_contact_formplugin.to_attr_formatused a Ruby string escape (inert in HTML) and interpolated attribute names verbatim; the contact form built markup by raw interpolation, so visitor input and form fields could inject script into the same-origin admin. Requirescama_contact_form0.1.12. #1215 — thanks, Amir Aliu and Enrik Mustafa.Breaking changes
- The contact form refuses unsafe content instead of rewriting it — content is stored and delivered verbatim, or the save is refused and the author told which setting to fix. An author holding
contact_form_unfiltered_htmlis unaffected; a visitor's submission is refused whole. Ordinary writing (Tom & Jerry,<br/>,Fish & Chips <today>) still passes. to_attr_formatemits"where it emitted\", and drops pairs whose key is not a valid HTML attribute name. A plugin relying on the old output to inject markup will stop working.to_attr_url_formatemitsvalue.to_s.inspect; it did not escape backslashes before. No callers in this repo.- The role editor gains "Allow unfiltered HTML in contact forms" (off for every role but
admin), and now renders anadminrole's permissions as held and locked, matchingcan :manage, :all. - #1206's permission is renamed
allow_unfiltered_html→post_content_unfiltered_html. No migration: #1206 has not shipped in any release. - The
cama_contact_formdependency is raised to~> 0.1.12(dropping the vulnerable 0.1.0 from its range);bundle update camaleon_cmsis enough.
- The contact form refuses unsafe content instead of rewriting it — content is stored and delivered verbatim, or the save is refused and the author told which setting to fix. An author holding
-
Security bumps picked up while re-resolving
cama_contact_form: rails 8.1.3 → 8.1.3.1, concurrent-ruby, erb, json, net-imap; Bundler 2.7.2 → 4.0.17, with both CI workflows pinningrubygems: 4.0.17to match. #1215. -
Developer tooling: Document when an OpenSpec change is archived — on the branch as part of the PR, not post-merge.
AGENTS.mdanddocs/ai/workflows.mdPhase 4 now say so. #1214. -
Developer tooling: Correct the skip-ci guidance in
docs/ai/workflows.md— the marker is a per-push decision (omit until the PR has had one full check run), not an unconditional rule for the Phase 4 changelog commit. #1214. -
Fix: Report an unresolvable target in
Admin::UsersController#updated_ajaxin the action's own error format instead of an HTML error page, and switch the failure paths fromrender inline:(an ERB-injection sink on the422path) torender plain:. Not a security fix. #1214. -
Developer tooling: Capture the target-user resolution invariant for
Admin::UsersControlleras theuser-target-resolutionOpenSpec capability and close the regression-coverage gap it exposed (new request specs forupdate/destroy/impersonate). No behavior change. #1214. -
Security fix: Close a user-existence oracle left by the profile IDOR fix in #1197 —
profileauthorized against the loaded record, so a nonexistent id 500ed while an existing one 302ed. Authorization is now decided from the request parameter before loading, and any baduser_idredirects with "Not found user." instead of 500ing. #1213. -
Refactor: De-duplicate the two uploader entry points (
RuntimeUploaderConcernandUploaderHelper, 349 byte-identical lines) into sharedUploaderPipeline/UploaderImageProcessing/UploaderSupportmodules. Both entry points keep their full public API, so host apps, plugins and themes are unaffected. #1212. -
Security fix: Prevent rejected media uploads from persisting in the web-served staging directory — a rejected
data:payload was still written topublic/tmp/<site_id>/and served same-origin. Content is now scanned before staging, and staged files are removed on any failure. #1211 — thanks, Lukman Azri. Upgrade notes. -
Developer tooling: Restructure the
AGENTS.mddocs tree for progressive disclosure — remove superseded process docs, consolidate reference docs, and add aCLAUDE.mdimport shim. #1208. -
Developer tooling: Add OpenSpec skills for Claude. #1207.
-
Security fix: Fix stored XSS via unsanitized post content — add server-side sanitization to
Post#contentat save with role-based allowlisting, so untrusted contributors cannot inject scripts intoraw @post.the_content. Also fixesnormalize_attrsdata loss on plain-text fields (e.g.Name <user@domain.com>). (Later superseded by the reject-on-save gate in #1263.) #1206 — thanks, Theodosis Paidakis. -
Security fix: Add defense-in-depth URL validation to the crop action — validate user-supplied HTTP/HTTPS URLs before the temporary upload pipeline, via a shared
cama_upload_url_errorhelper. Same-site URLs are checked for path traversal only; remote URLs get full SSRF validation pinned to the validated IP to defeat DNS rebinding. #1203 — thanks, Theodosis Paidakis. -
Security fix: Fix path traversal bypass in file upload guards — canonicalize paths with
File.expand_pathbefore prefix validation at all four sinks, compare site URLs by host+port, and harden thedata:branch againstname-based traversal. Also bumpsloofahandrails-html-sanitizer. #1201 — thanks, Jose Rivas (Zero Trust Offsec). -
Security fix: Fix SVG stored XSS via Nokogiri parse-based detection — replace the regex denylist with an XML parser that resolves entities, blocks
script/on*/javascript:/data:/embed, and disables XXE. Add Rack middleware serving/media/SVGs withnosniffandscript-src 'none'. #1199 — thanks, Jose Rivas (Zero Trust Offsec). -
Security fix: Fix arbitrary server-side file read via media upload — validate the path prefix before
File.openinupload_fileandcama_tmp_upload, coerce nilformatsto'*', and handle crop errors gracefully. #1198 — thanks, Jose Rivas (Zero Trust Offsec). -
Security fix: Fix profile IDOR in
Admin::UsersController#profile— add an inlineauthorize!check when viewing another user, so low-privilege users can no longer enumerate arbitrary accounts. #1197 — thanks, Neo Andrew. -
Security fix: Fix improper authorization in the draft autosave endpoint — scope draft lookups to post type, add
authorize!checks before draft mutations, and validatepost_parentagainst a real post. #1196 — thanks, Enrik Mustafa and Óscar Uribe. -
Security fix: Prevent an SVG XSS bypass via missing animation event handlers (
onbegin/onend/onrepeat) in the upload content filter, and DRYUNSAFE_EVENT_PATTERNSinto the sharedCamaleonCms::ContentSecuritymodule. #1195 — thanks, Mohamed Almuhaya. -
Fix: Restore legacy widget assignments, configured navigation order, and frontend plugin controller helper compatibility. #1194.
-
Developer tooling: Add the OpenSpec planning workflow and agent guidance. #1193.
-
Security bumps: Bump json, puma to 8.0.2, rubocop to 1.88.1, zeitwerk to 2.8.2 and other development gems, fixing all new Rubocop offenses. #1186.
-
Security fix: Prevent account takeover in
Admin::UsersController#updated_ajaxby unifying target-user lookup and authorization. #1185 — thanks, Lukman Azri. -
Fix: Restore TinyMCE editor icons in development —
sprockets-rails>= 3.5's asset-url post-processor rewrote the skin's relative font urls to an invalid root path; aTinymceSkinSafeAssetUrlProcessornow expands them to full logical paths for the skin only. #1183. -
Fix: Restore
object_classscoping onCustomField#metasandCustomFieldGroup#metas— a #1173 regression dropped the scope, so a colliding numeric id read the wrong_defaultmeta (visible as the theme-settings editor field rendering as a plain text box). #1183. -
Refactor: Finalize the Phase 6G runtime-concern decomposition — runtime concerns now
includetheir helper modules (single source of truth) while keeping back-compat ivars for templates/plugins; restores several controller-context helpers, per-site theme view overrides, legacy-thumbnail fallbacks, plugin template lookup, Sprockets-4 asset precompilation, and localized-slug lookups. #1183. -
Fix & Refactor: Phase 5 — restore theme preview rendering (preview theme state, legacy ivars, preview hook dispatch, auto-created preview menus) and refactor the nav-menu helper onto request-scoped
CurrentRequeststate. #1181. -
Refactor: Replace Phase 4 session/shortcode/comment helper instance-variable state with request-scoped
CurrentRequeststate, preserving controller/view compatibility. #1179. -
Refactor: Replace Phase 3 admin/menu/taxonomy helper instance-variable state with
CurrentRequest-backed state, keeping legacy taxonomy-list compatibility. #1178. -
Refactor: Replace Phase 2 frontend helper context (content, SEO, site helpers) with
CurrentRequest-backed state. #1177. -
Refactor: Replace Phase 1 helper instance-variable state (content, hooks, html, theme helpers) with
CurrentRequest-backed state. #1176. -
Fix: Restore admin preview locale compatibility for decorators and plugin helpers removed by #1166, preserving theme previews and admin-rendered frontend flows. #1175.
-
Security fix: Fix Rails OutputSafety sinks while preserving menu and asset rendering — escape untrusted HTML in attack responses, media crop output, edit-link labels, hierarchy titles, and select options. #1174.
-
Refactor: Implement native Rails STI for term taxonomies and posts, and add polymorphic
ownerassociations for metas and custom-field records. #1173. -
Style & tooling: Add RuboCop plugin gems (performance, rails, capybara, factory_bot, rake, rspec_rails), fix hundreds of offenses across 92 files, and set the maximum line length to 120. #1167.
-
Style & testability: Refactor HTML in Ruby code to Rails tags (
content_tag,tag,link_to,safe_join) across 12 files, and add factories and specs for nav menus, the captcha helper, andfix_post_order. #1172. -
Security fix: Fix two unprotected redirects via
params[:return_to]insessions_controller.rbandsession_helper.rbby routing through the existingsafe_redirect_urlhelper. #1168. -
Security, style, and cleanup: Rubocop fixes part 1 — style/performance/rails offenses, dead-code removal, and
TargetRailsVersionset to6.1. #1168. -
Security fix: Bump gems — Nokogiri to 1.19.3, action_text-trix, aws-sdk, puma, rubyzip, selenium-webdriver, sqlite3, Bundler 2.7.2. #1170.
-
Fix: Decorator locale resolution and language context mixing (fixes issue #233) — move
cama_get_i18n_frontendto the parent controller and rely onI18n.locale, so decorators use the site's frontend language in frontend context and the admin language in admin context. #1166. -
Bug fix: Fix thread-safety issues with
PluginRoutes.reloadcausing persistent 500 errors — remove the reload fromplugins#index/themes#indexand guard route reloading/cache access with aMonitor. #1163. -
Optimize
PluginRoutes.draw_gemsandapps_dir— micro-optimizations for memory consumption and performance. #1164.
Full Changelog: 2.9.2...2.9.3