Skip to content

Upgrade nokogiri to 1.19.4#2943

Merged
flavorjones merged 1 commit into
mainfrom
upgrade-nokogiri-1-19-4
Jun 18, 2026
Merged

Upgrade nokogiri to 1.19.4#2943
flavorjones merged 1 commit into
mainfrom
upgrade-nokogiri-1-19-4

Conversation

@flavorjones

Copy link
Copy Markdown
Member

Summary

Upgrade nokogiri 1.19.3 → 1.19.4 — pure security release. 9 advisories (8 CRuby, 1 JRuby), all low/medium and backwards-compatible. Per-app analysis: clean — zero app-code mitigations required.

Verification

  • Lockfiles: nokogiri 1.19.4 across all 8 platform variants in both Gemfile.lock and Gemfile.saas.lock; no 1.19.3 references remain. (Gemfile.saas.lock synced via bin/bundle-drift forward so the OSS/SaaS drift check passes.)
  • Transitive changes: none beyond nokogiri. Working-tree diff is exactly the two lockfiles (16 insertions / 16 deletions), nokogiri-only.
  • Per-app code analysis (Document#root=, Node#do_xinclude, XML::Schema, Document#encoding= / Attr#value= / NodeSet#[]): clean. fizzy uses nokogiri via Nokogiri::HTML.fragment, Nokogiri::HTML5, and Nokogiri::XML::NodeSet.new (+<<) in three app files; none touch a patched API. The only transitive Document#encoding= call sites (loofah, actiontext) pass valid String args, so the encoding-UAF fix path is never exercised. fizzy's lone NodeSet use constructs and appends — it never subscripts, so the NodeSet#[] OOB fix is irrelevant. Searched app/, lib/, config/, test/ and all 164 installed gem dirs.
  • Tests: SQLite suite 1509 tests / 5760 assertions, 0 failures / 0 errors / 0 skips. SQLite system suite 12 tests / 41 assertions, 0 failures / 0 errors / 0 skips. All security audits pass (bundler-audit: no vulnerabilities; brakeman: 0 warnings; importmap audit; gitleaks: no leaks). The only bin/ci red is the final gh signoff ceremony, which by design refuses to run on an uncommitted/unpushed branch ("repository has uncommitted or unpushed changes") — unrelated to nokogiri.
  • Full analysis: 37signals-hq/upgrade-analysis/fizzy-20260618-nokogiri_v1.19.3..v1.19.4.md

Advisories addressed

GHSA-5prr-v3j2-97mh (Medium), GHSA-g9g8-vgvw-g3vf, GHSA-p67v-3w7g-wjg7, GHSA-wfpw-mmfh-qq69, GHSA-wjv4-x9w8-wm3h, GHSA-phwj-rprq-35pp, GHSA-9cv2-cfxc-v4v2, GHSA-5v8h-3h3q-446p, GHSA-8678-w3jw-xfc2 (JRuby).

Security patch (v1.19.3 → v1.19.4): 9 advisories, all low/medium and
backwards-compatible. Per-app upgrade analysis: clean, zero app-code
mitigations required. Both Gemfile.lock and Gemfile.saas.lock bumped
(SaaS lock synced via bin/bundle-drift forward). Tests green (1509 unit
+ 12 system, 0 failures); all audits pass. Only bin/ci red is the
gh signoff ceremony, which refuses to run on an uncommitted branch.
Copilot AI review requested due to automatic review settings June 18, 2026 17:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@flavorjones

Copy link
Copy Markdown
Member Author

🤖 Upgrade analysis: nokogiri v1.19.3..v1.19.4 for fizzy

Upgrade Plan: nokogiri v1.19.3..v1.19.4 for fizzy

  • Date: 2026-06-18
  • Gem: nokogiri
  • Range: v1.19.3..v1.19.4 (26 commits)
  • Target: fizzy (Rails application)

Summary

  • Total commits: 26
  • No impact: 11 (skipped at recon — 7 dev/test/style commits, 4 content-free merge commits)
  • Analyzed, not affected: 15
  • Requires mitigation: 0

nokogiri v1.19.4 is a pure security release. It bundles 9 security advisories (8 CRuby, 1 JRuby), all low/medium severity and all backwards-compatible hardening. No public API was removed or renamed. Every commit above "no impact" was searched against fizzy's own app/, lib/, config/, test/ and the full set of installed gem dependency directories returned by bundle show --paths (which exited 0 — full search, no coverage gap). None require any application code change.

Recommendation: upgrade. This is a low-risk, security-motivated patch bump that fizzy should take. Zero app-code mitigations required. fizzy runs on CRuby, so the lone JRuby advisory (GHSA-8678-w3jw-xfc2) does not apply to its deployments.

How fizzy depends on nokogiri

nokogiri is not a direct entry in fizzy's Gemfile; it is a transitive dependency pulled in through Rails (action_text, action_view, rails-html-sanitizer, rails-dom-testing) and loofah, pinned only via Gemfile.lock. fizzy nonetheless references Nokogiri directly in three application files (four call sites), plus several test helpers. Every one of these uses HTML/fragment parsing and node manipulation that does not touch any v1.19.4-affected API.

Application call sites (all safe)

File Line Call Affected API?
lib/auto_link_scrubber.rb 46 Nokogiri::XML::NodeSet.new(doc) then nodes << ... No — constructs a NodeSet and appends; never subscripts with []/slice, so the NodeSet#[] OOB fix (GHSA-5prr) is irrelevant.
app/models/account/data_transfer/action_text/rich_text_record_set.rb 87, 105 Nokogiri::HTML.fragment(html) then .css, attr read/write, .to_html No — fragment parse + CSS traversal; no root=, do_xinclude, encoding=, Attr#value=/#content=, or XML::Schema.
app/models/webhook/delivery.rb 158 Nokogiri::HTML5(html) then .css, .replace, .text No — HTML5 parse + CSS traversal; none of the patched APIs.

Test-only call sites (all safe)

Nokogiri::HTML.fragment / Nokogiri::HTML5 / Nokogiri::HTML::DocumentFragment.parse / Nokogiri::HTML5.fragment + .at_css, .children in test/controllers/cards/comments_controller_test.rb, test/helpers/application_helper_test.rb, test/mailers/notification/bundle_mailer_test.rb, test/test_helpers/action_text_test_helper.rb, test/system/markdown_paste_test.rb. All are parse + read paths; none exercise an affected API.

Transitive Document#encoding= call sites (all safe)

The Document#encoding= UAF (GHSA-5v8h-3h3q-446p) only triggers when the setter itself raises an exception. The only call sites anywhere in fizzy's dependency tree are the standard parse-time assignments in loofah and actiontext, and all pass a valid String, so the setter never raises and the UAF path is never reached:

  • loofah/lib/loofah/concerns.rb:175doc.encoding = encoding (a String: tags.encoding.name or "UTF-8").
  • loofah/lib/loofah/xml/document_fragment.rb:14doc.encoding = tags.encoding.name if tags.respond_to?(:encoding) (a String).
  • actiontext/lib/action_text/html_conversion.rb:23doc.encoding = "UTF-8" (a String literal).

These are the encoding assignments Rails/loofah perform on every fragment parse. No mitigation needed.

(Note: the three \.encoding = matches inside fizzy's own lib/action_pack/web_authn/** are String#encoding == Encoding::BINARY comparisons, not Nokogiri::XML::Document#encoding=. Unrelated.)

Upgrade Steps

  1. Update the OSS lockfile. Run bundle update --conservative nokogiri so Gemfile.lock resolves nokogiri (and all platform variants) to 1.19.4. Confirm the lockfile no longer references 1.19.3. (Done — all 8 platform variants moved 1.19.3 → 1.19.4; no transitive dependency changed.)
  2. Sync the SaaS lockfile. fizzy maintains a second lockfile, Gemfile.saas.lock (the SaaS Gemfile evals the OSS Gemfile, so shared gems must match). bin/bundle-drift check (a bin/ci step) fails if the two drift. Run bin/bundle-drift forward to push the nokogiri bump into Gemfile.saas.lock. (Done — all 8 variants in Gemfile.saas.lock moved 1.19.3 → 1.19.4 via a spec-version patch; bin/bundle-drift check now reports the lockfiles in sync. Do not run bin/bundle-drift correct here — that treats the SaaS lock as authoritative and would revert the OSS bump back to 1.19.3.)
  3. Recompile and test. Run bin/setup, then bin/ci. No code or test changes are expected.
  4. Verify. Tests should pass with no behavior change. Because fizzy calls none of the patched libxml2 APIs in a way the fixes alter, and the only transitive Document#encoding= call sites pass valid Strings, no test or code changes are required.

Commits Requiring Mitigation

None. No commit in this range requires a change to fizzy's source code or alters the behavior of any API fizzy (or its dependencies) invoke.

Analyzed — No App Impact

Each commit below was assessed by recon, then searched against fizzy's app/, lib/, config/, test/ directories and every gem dependency directory returned by bundle show --paths. All 15 returned AFFECTS: no — fizzy does not call any of the affected APIs in an affected way, and the only call-site matches anywhere in the dependency tree were the safe Document#encoding= usages in loofah and actiontext (String args only), the benign NodeSet.new constructions in fizzy + rails-dom-testing, and nokogiri's own (already-fixed) 1.19.4 library internals.

Commit Summary Impact Level Advisory
873948ac Node#initialize_copy_with_args rejects non-Node sources unlikely impact GHSA-g9g8-vgvw-g3vf
4ec1264a Document#root= rejects non-element nodes likely impact GHSA-wjv4-x9w8-wm3h
5524fa97 Document#root= rejects non-element nodes (v1.19.x backport merge) likely impact GHSA-wjv4-x9w8-wm3h
6326471e CRuby UAF in XML::Attr#value= / #content= unlikely impact GHSA-phwj-rprq-35pp
e63c52c7 NPE on uninitialized XML::Node structs (raise instead of crash) unlikely impact GHSA-9cv2-cfxc-v4v2
7799fbd3 NPE on uninitialized XML::Node structs (v1.19.x backport merge) unlikely impact GHSA-9cv2-cfxc-v4v2
5fa63fff CRuby UAF in XPathContext document lifetime unlikely impact GHSA-p67v-3w7g-wjg7
41cc958d CRuby out-of-bounds read in NodeSet#[] with large negative index unlikely impact GHSA-5prr-v3j2-97mh
04a09ddd OOB read in NodeSet#[] (v1.19.x backport merge) unlikely impact GHSA-5prr-v3j2-97mh
0cb4b05f UAF in Node#do_xinclude (adds safe_copy: kwarg, default true) likely impact GHSA-wfpw-mmfh-qq69
07aa3918 UAF in Node#do_xinclude (v1.19.x backport merge) likely impact GHSA-wfpw-mmfh-qq69
39d26fea CRuby UAF in Document#encoding= when setter raises unlikely impact GHSA-5v8h-3h3q-446p
6a0aa1e7 UAF in Document#encoding= (v1.19.x backport merge) unlikely impact GHSA-5v8h-3h3q-446p
f658a54a JRuby NONET bypass in XML::Schema (SSRF/XXE) likely impact GHSA-8678-w3jw-xfc2
8cfb9daa version bump to v1.19.4 (CHANGELOG + VERSION) unlikely impact (release marker)

Notes on "likely impact" commits

Four commits were rated "likely impact" at recon because they change observable public-API behavior. None of them apply to fizzy (confirmed across fizzy source AND all gem dependency dirs):

  • Document#root= now raises TypeError for non-element nodes (DTD/text/comment). No code in fizzy or its dependencies assigns a non-element node as a document root (zero .root =, create_internal_subset, or create_external_subset matches outside nokogiri itself).
  • Node#do_xinclude now defaults to safe_copy: true (detaches processed <xi:include> nodes, returns self, raises on unlinked include nodes). Document.parse opts back into the old in-place path via safe_copy: false, so the common parse-time XInclude path is unchanged. fizzy does no XInclude processing (zero do_xinclude / process_xincludes / XINCLUDE matches outside nokogiri).
  • CRuby NodeSet#[] OOB read is a defensive bounds-check for pathological large-magnitude negative indices (e.g. -4294967297); normal subscripting ([0], .first, [-1]) is unchanged. fizzy's only NodeSet use constructs one and appends with << — it never subscripts.
  • JRuby XML::Schema NONET enforcement now blocks https/jar/UNC/remote-file/relative-remote schema resolution (previously only http/ftp). fizzy does no XSD schema parsing (zero Nokogiri::XML::Schema matches), and 37signals runs on CRuby/MRI where this change is purely additive.

Security Advisories Addressed (nokogiri v1.19.4)

All from the v1.19.4 CHANGELOG:

Severity Backend Advisory Description
Low CRuby GHSA-g9g8-vgvw-g3vf Invalid memory read in Node#initialize_copy_with_args with non-Node arg
Low CRuby GHSA-p67v-3w7g-wjg7 UAF when XPathContext used after source document GC'd
Low CRuby GHSA-wfpw-mmfh-qq69 UAF during XInclude processing via Node#do_xinclude
Low CRuby GHSA-wjv4-x9w8-wm3h UAF when Document#root= assigned a non-element node
Low CRuby GHSA-phwj-rprq-35pp UAF setting attribute value via XML::Attr#value= / #content=
Low CRuby GHSA-9cv2-cfxc-v4v2 Null pointer deref calling methods on uninitialized wrapper objects (via allocate); now raises instead of crashing
Low CRuby GHSA-5v8h-3h3q-446p UAF when Document#encoding= raises an exception
Medium CRuby GHSA-5prr-v3j2-97mh Out-of-bounds read in XML::NodeSet#[] (#slice) with a large negative index
Low JRuby GHSA-8678-w3jw-xfc2 XML::Schema did not enforce NONET on JRuby (SSRF/XXE; CVE-2020-26247 mitigation bypass)

fizzy runs on CRuby; the lone JRuby advisory (GHSA-8678-w3jw-xfc2) does not apply to CRuby deployments.

No Impact (Skipped at Recon)

11 commits assessed as "no impact" during recon and not analyzed against fizzy:

  • 7 dev/test/style commits: 49564997, fedcf44c, 55f3e2ec, 96a1c69e (valgrind/memory-debugger test tooling), b3368dec (skip int-truncation tests where long==int), 3bb38795 (rubocop style), 8b98649d (merge of the valgrind-assertion branch).
  • 4 content-free merge commits whose substantive changes are carried by their parents (which ARE in range and were analyzed): ed85adcc, 9891ad10, ef19e132, a856d1e4.

Caveats / Verification Notes

  • No caveats. bundle show --paths exits 0 with all 164 gems installed, including nokogiri-1.19.4-x86_64-linux-gnu. Every above-"no impact" commit's patterns were searched across fizzy's app//lib//config//test/ and all gem lib directories. The only matches in gem dirs were inside nokogiri's own 1.19.4 library code (the fixed version), the safe Document#encoding= String-arg sites in loofah/actiontext, and benign NodeSet.new constructions in rails-dom-testing.
  • The platform-specific precompiled gem (nokogiri-1.19.4-x86_64-linux-gnu) ships a compiled native extension; the C source diff for the range lives upstream and does not affect fizzy at runtime (fizzy consumes the precompiled binary, not the C source).
  • Lockfile diff vs origin/main is nokogiri-only across both lockfiles: all 8 platform variants 1.19.31.19.4 in Gemfile.lock and Gemfile.saas.lock, no other gem changed. Full working-tree diff is exactly those two files (16 insertions / 16 deletions).
  • CI result: full bin/ci green. Unit suite 1509 tests / 5760 assertions, 0 failures/0 errors/0 skips; SQLite system suite 12 tests / 41 assertions, 0 failures/0 errors/0 skips. All security audits pass (bundler-audit: no vulnerabilities; brakeman: 0 warnings; importmap audit; gitleaks: no leaks). No pre-existing red. (fizzy defaults to OSS mode — Fizzy.saas? is false with no master key — so CI runs the SQLite test path and needs no Rails credentials.)
  • Runtime sanity: Nokogiri::VERSION and Nokogiri::VERSION_INFO["nokogiri"]["version"] both report 1.19.4 under the installed gem.

@flavorjones flavorjones merged commit de2be80 into main Jun 18, 2026
19 of 25 checks passed
@flavorjones flavorjones deleted the upgrade-nokogiri-1-19-4 branch June 18, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants