Security fix for 3 stored/DOM XSS advisories in Trix:
GHSA-g9jg-w8vm-g96v (attachment attribute), GHSA-qmpg-8xg6-ph5q
(serialized attributes), GHSA-53p3-c7vp-4mcc (JSON deserialization
bypass in drag-and-drop).
EXPOSED: campfire's composer/editor use Trix for message bodies, and the
browser actually ran the hand-vendored vendor/javascript/trix.esm.min.js
pinned at 2.0.10 — which lacks the DOMPurify/isValidAttribute sanitizers.
The gem bump alone is a no-op for the browser; the real fix is
re-vendoring the ESM build:
- bump gem to 2.1.19 (Gemfile.lock)
- re-vendor vendor/javascript/trix.esm.min.js from Trix 2.1.19
(2.0.10 → 2.1.19; bundles DOMPurify 3.4.2, rangy 1.3.2)
- update importmap pin comment @2.0.10 → @2.1.19
89 commits analyzed. Verified green via full unit + system suites
(system tests drive the Trix composer via fill_in_rich_text_area).
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-action_text-trix_v2.1.15..v2.1.19.md🤖 Assisted by Claude
Security bump clearing 5 CVEs (CVE-2026-42245/42246/42256/42257/42258:
literal DoS, STARTTLS stripping, SCRAM DoS, command injection ×2). Not
exposed: all require acting as an IMAP client. net-imap is autoloaded by
mail only when retriever_method :imap is set; campfire configures no
retriever and has no inbound email — net/imap is never required.
Conservative update landed 0.6.4 (minor jump, dormant dep). Ruby floor
raised to >= 3.2.0; campfire runs 3.4.5. 206 commits triaged, 0
mitigations.
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-net-imap_v0.5.12..v0.6.4.md🤖 Assisted by Claude
Security bump for CVE-2026-41316 (High, @_init deserialization guard
bypass via def_module/def_method/def_class). Not exposed: ActionView
renders via Erubi, not Ruby's ERB class; no def_* calls or Marshal of ERB
objects anywhere — vulnerable path absent.
Transitive dep (no Gemfile change). 24 commits analyzed, 0 mitigations.
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-erb_v6.0.0..v6.0.4.md🤖 Assisted by Claude
Security bump for CVE-2026-45363 (High, empty-key HMAC bypass). Not
exposed: jwt reaches campfire only via web-push VAPID signing, which uses
ES256 (asymmetric ECDSA), not HMAC — the vulnerable JWA::Hmac path is
unreachable. web-push requires jwt ~> 3.0; minor bump, encode API
unchanged.
Transitive dep (no Gemfile change). 15 commits analyzed, 0 mitigations.
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-jwt_v3.1.2..v3.2.0.md🤖 Assisted by Claude
Security bump for CVE-2026-33306 (integer overflow → zero KDF iterations
at Cost=31). Not exposed: JRuby-only bug; campfire runs MRI (Ruby 3.4.5),
no java platform in lockfile, ActiveModel default cost is 10-12.
Direct gem (no version constraint, unchanged). 33 commits analyzed,
2 no-action mitigations (constant-time password compare hardening on the
has_secure_password path) — auth tests green.
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-bcrypt_v3.1.20..v3.1.22.md🤖 Assisted by Claude
Security bump for CVE-2026-35611 (High, ReDoS in Addressable::Template).
Not exposed: campfire never reaches Addressable::Template — runtime use
is geared_pagination's URI.parse; templates appear only in webmock test
stubs (all string/Regexp, never templates).
Transitive dep (no Gemfile change). 49 commits analyzed, 0 mitigations.
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-addressable_2.8.7..2.9.0.md🤖 Assisted by Claude
Security bump for CVE-2025-58767 (DoS on malformed XML). Conservative
update landed 3.4.4 (latest patch, above advisory floor of 3.4.2).
Test/build-only transitive dep (webmock→crack, selenium-webdriver);
absent from app runtime. 34 commits analyzed, 1 test-only mitigation
(no-root-element parse change) — verified green via full unit + system
test suites.
https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-rexml_3.4.1..3.4.4.md🤖 Assisted by Claude
* Add GitHub Actions audit job (actionlint + zizmor) to CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Configure dependabot for GitHub Actions, bundler, and Docker
Batches all action updates into a single weekly PR. Adds cooldown
periods to all ecosystems.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add local GitHub Actions linting (actionlint + zizmor) to bin/setup and bin/ci
Install actionlint, shellcheck, and zizmor in bin/setup. Run both
linters as CI steps in config/ci.rb alongside existing style checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Pin all GitHub Actions to SHA hashes
Run pinact to pin action versions to specific commit SHAs,
preventing supply chain attacks from tag mutation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix high severity zizmor findings
- Suppress unpinned-images for redis service containers (digest
pinning is nontrivial for service containers)
- Move workflow-level permissions to job-level in publish-image.yml
(build gets full set, manifest gets only what it needs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix medium severity zizmor findings
- Add persist-credentials: false to all checkout steps
- Add permissions: {} at workflow level in ci.yml
- Add job-level permissions (contents: read) to all CI jobs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix informational template-injection findings in publish-image.yml
Move steps.meta.outputs.tags from inline ${{ }} expressions to env
vars in both the manifest creation and cosign signing steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update brakeman to 8.0.4
bin/brakeman uses --ensure-latest which fails if not on the newest version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds ipv4_mapped? and ipv4_compat? checks to PrivateNetworkGuard.private_ip?
to block SSRF bypass attempts using IPv6 address formats like:
- ::ffff:169.254.169.254 (IPv4-mapped)
- ::169.254.169.254 (IPv4-compatible)
These formats could previously bypass the link_local? check since Ruby
treats them as IPv6 addresses, not IPv4.
Ref: HackerOne #3481701