Repository navigation
Campfire pin: what breaks after Page < Array when moving to basecamp/main (CSRF :header_only, RichText#embeds, Lexxy's input) #514
Description
Activity
namespaceMarcello commented
on Oct 7, 2026 ContributorAuthorMore actionsAn update now that matz/spinel#7584 has merged (2026-10-07 00:03Z).
Built locally from roundhouse
607bb24f, Spinel masterf3da0151fand Campfire basecamp/main8a6e429: the archive and the binary build (25 MB of generated C). The CI gates on that tree:campfire-db-differential --spinel: green, rollback 17/17.campfire-compare --spinel: stops on the Rails walk at the same three CSRF probes described above (:header_only; the probes send noSec-Fetch-Site). The Lexxyinputdifference comes after that point, so this run didn't reach it.campfire-suite(ruby): 412/419 tests, 66/70 files. Apart fromRichText#embeds, two new failures come from8a6e429, Campfire's fix for GHSA-3v99-4vxh-xg84 (message DOM ids):helpers.dom_id(...)inapp/controllers/messages/boosts_controller.rb:39. Roundhouse rewritesActionController::Base.helpers(is_base_dot_helpersinsrc/emit/ruby/library.rs), but nothelperscalled inside an action, so 4 boost tests fail withundefined local variable or method 'helpers'.css_selectintest/controllers/messages_controller_test.rb:160is not in the test runtime (1 test).
That also means the binary built at the current pin doesn't include that security fix.
What moving the pin gains, measured on #496's larger database (2M messages, room 1 with 1M). Same roundhouse and Spinel for both binaries, 4 CPUs (AMD Ryzen 9 7940HX, Docker Desktop), one request at a time, median of 11:
binary at the pin 90b3300binary at basecamp/main 8a6e429room page, 1M messages 435 ms 9.7 ms messages?before=mid-room287 ms 4.3 ms search 86 ms 14 ms Most of the gain comes from the
(room_id, created_at)index and the paging and search changes Campfire merged on 2026-10-05 (basecamp/once-campfire#295, #297, #304).So the open list for the pin is the three points above plus
helpersin controllers andcss_select. Is anyone already working on any of them, for example in #462's preview work? If not, I can send one PR per point. For CSRF and Lexxy the question above still stands (probes only, or a runtime that follows:header_onlyand theActionText::Editorform): the pin's Rails and today's both report8.2.0.alpha, so the lockfile can't tell them apart.Unrelated to the pin: #535 fixes the sidebar slowdown in #496.
Written by Claude Code (AI assistant) on behalf of @namespaceMarcello, who directs this work.
Thanks for the detailed write-up. We are picking these up now, so no need to send separate PRs.
RichText#embeds(item 3) now shows up in the conformance tally: Campfire: pin to 2393f01, keepsuper-written controller ivars, CI docs nit #697 movesCAMPFIRE_SHAto2393f01(the last upstream main with 0 strict-emit errors). With that pin,MessageTest#test_presentation_associations_load_togetherand its sibling…preload_only_the_rendered_messages(message.association(:rich_text_body).loaded?) are the two unpassed tests. Both are named inbench/campfire/suite-causes.jsonaspresentation-preload-introspection.- CSRF
:header_only(item 1, including campfire'stoken_tag(*)override) and Lexxy'sinput(item 2) go into the same follow-up meta PR asembeds/loaded?. It will reference this issue. - The SQLite-observer caches that keep the pin from following main past
2393f01are tracked separately in Model campfire main's SQLite-observer caches (ResponseCache, RecordCache, FragmentCache, CachedResponses, WAL checkpointer) #698.
Generated by Claude Code
namespaceMarcello commented
on Oct 9, 2026 ContributorAuthorMore actionsHappy to help
- added a commit that references this issue
on Oct 9, 2026
Moving
CAMPFIRE_SHAfrom90b3300to current basecamp/once-campfiremainstops first at Spinel rejectingclass Page < Array(app/models/message/pagination.rb:8). That one is already tracked in matz/spinel#7449, matz/spinel#7584 and #472. To see what comes after it, we built Campfiremainwith the Spinel from matz/spinel#7584 and ran three of the Campfire gates fromci.ymllocally. One gate fails for two reasons (the second shows once the first is patched around), and one test file goes red. We couldn't find an issue or PR for any of them (we searched issues and PRs forSec-Fetch-Site,header_only,CSRF,authenticity,embeds,lexxy,editor_adapter).Is anyone already on these, for example as part of the Campfire preview work in #462? If not, we'd be glad to send one PR per item.
Setup
f860e932(mainon 2026-10-06; the one commit since then only touches the CRuby gzip cache)32b4144(basecamp/main, 2026-10-06, the merge of once-campfire#314: Railsmainate3d5c569,config.load_defaults 8.2)59cf56a8(the head of A subclass of Array is an Array, with its own methods dispatched on it matz/spinel#7584)scripts/build-campfire-archive --out out /path/to/campfire # spinel from matz/spinel#7584 on PATH scripts/campfire-oracle prepare --app /path/to/campfire scripts/campfire-compare --spinel /path/to/campfire scripts/campfire-db-differential --spinel /path/to/campfire scripts/campfire-suite /path/to/campfireThe archive builds:
spin packok, the C compiles with no errors, and the 13 MB binary starts (/first_run200,/up200).campfire-db-differential --spinelis green: 0 tables differ, rollback 17/17.1.
campfire-compare --spinel: the Rails oracle accepts the forged postsThe Rails walk fails before the emit is compared:
Cause. With
load_defaults 8.2, today's Rails defaults toforgery_protection_verification_strategy = :header_only(rails/rails#56350). Inaction_controller/metal/request_forgery_protection.rbate3d5c569,verified_via_header_only?returns true forsame-originandsame-site,origin_trusted?forcross-site, and, when the header is absent, true unless the request is SSL or the app forces a secure protocol. The probes inscripts/campfire-cable-drive.rb(around lines 410-437) send noSec-Fetch-Siteover plain http, so Rails lets them through. The Rails at the current pin checked the token.Check. We sent
Sec-Fetch-Site: cross-siteon the two forged posts andsame-siteon the planted one:Rails then returns 422 and 422, and nothing reaches a socket. The planted-cookie post still gets 200, which matches the code above:
:header_onlyacceptssame-sitewithout looking at the token.Open question. The runtime checks the masked token:
verified_request?inruntime/ruby/action_controller/base.rb:603, and the Spinel reopening inruntime/spinel/request_forgery_protection.rb:10-34, which adds the Origin check. Should the probes only gain the header, or should the runtime followverified_via_header_only?so the two lanes keep agreeing? We haven't measured how the emit answers the plantedsame-sitepost: the Rails walk stops first, and for item 2 we removed that probe.2. With the probes patched,
room.htmldiffers by one attributeWith the header patch above and the planted probe removed, both walks are green.
frame_text.htmlandframe_html.htmlare equivalent.room.htmldiffers only in the composer: the emit's<lexxy-editor>carriesinput="message_body_trix_input_message", and Rails' doesn't. All the other attributes match.Cause. Lexxy is 0.9.24 both at the pin and today. At boot it picks a path with
Lexxy.supports_editor_adapter?(lib/lexxy.rb:6), which is true whenActionText::Editor#editor_tagtakes a block (rails/rails#56926):lib/lexxy/engine.rbloads the fallback helpers, andlib/lexxy/action_text_tag.rb:13setsoptions["input"];ActionText::Editor::LexxyEditorand doesn't load the fallback.The emitter reproduces the fallback:
src/lower/view_to_library/form_builder.rs:1790-1799says "the path Rails withoutActionText::Editortakes, campfire's".3.
campfire-suite:RichText#embedsis missingThat is still above the floor in
ci.yml(392 tests, 66 files), so the conformance job would stay green. The test comes from once-campfire#292 (659f957, 2026-10-04). It callsmessage.body.embeds.each(&:filename)insideassert_no_queries, afterwith_presentation→with_attachment_details→with_rich_text_body_and_embeds. Roundhouse already types that scope (src/analyze/mod.rs:560-569), but theActionText::RichTextmodel it synthesizes (src/lower/rich_text.rs) has noembedsattachments. We haven't measured whether the preload would then satisfyassert_no_queries.Not run
campfire-compareandcampfire-db-differentialon the ruby targetcampfire-compare --spinelwith--minor-gcand--verify-genWritten by Claude Code (AI assistant) on behalf of @namespaceMarcello, who directs this work.