Commit Graph

254 Commits

Author SHA1 Message Date
Mike Dalessio 8e0aa7d636 dep: update nokogiri 1.18.10 → 1.19.3
Security bump clearing 3 advisories: GHSA-c4rq-3m3g-8wgx (High, CSS
selector tokenizer ReDoS), GHSA-v2fc-qm4h-8hqv (XSLT transform memory
leak), GHSA-wx95-c6cv-8532 (unchecked xmlC14NExecute return). None
exposed: loofah/rails-html-sanitizer use DOM/XPath not CSS selectors,
every app CSS selector is a compile-time literal, and campfire uses no
XSLT or XML canonicalization.

The 1.18→1.19 minor bump is a Ruby-4-support/packaging milestone —
bundled libxml2 2.13.9 / libxslt 1.1.43 are unchanged, so HTML parsing
and ActionText sanitization output are identical. Ruby floor 3.2
(campfire runs 3.4.5); all 4 locked platforms still ship.

Transitive dep via Rails/loofah (no Gemfile change). 33 commits analyzed,
0 mitigations. Verified green via full unit + system suites.

https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-nokogiri_v1.18.10..v1.19.3.md

🤖 Assisted by Claude
2026-06-09 12:42:51 -04:00
Mike Dalessio efaae642b0 dep: update rack 3.2.4 → 3.2.6
Security bump clearing ~11 CVEs (multipart parser differentials/DoS, Host
header validation, byte-range DoS, Forwarded injection, Rack::Static/
Directory/Sendfile disclosures). Reachable fixes (multipart uploads, AS
byte-range streaming, host parsing) apply transparently via corrective
behavior and generous new defaults; the Static/Directory/Sendfile/
Deflater CVEs are not reachable (campfire uses none of those middlewares).

No app changes required. Optional hardening noted in plan: new env knob
RACK_MULTIPART_PARSER_BYTESIZE_LIMIT (default 10 GiB) could be tuned to
campfire's real max attachment size — left at default here.

Transitive dep via Rails (no Gemfile change). 23 commits analyzed,
0 required mitigations. Verified green via full unit + system suites.

https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-rack_v3.2.4..v3.2.6.md

🤖 Assisted by Claude
2026-06-09 12:42:51 -04:00
Mike Dalessio 28525bec5d dep: update sinatra 4.1.1 → 4.2.1
Security bump for CVE-2025-61921 (ReDoS via ETag header generation). Not
exposed: sinatra reaches campfire only through resque-web, which is never
mounted — config.ru runs plain Rails, no `require "resque/server"`
anywhere, so Sinatra::Base is never defined. Dead code at runtime.

Conservative update also bumped rack-protection 4.1.1 → 4.2.1 (same
monorepo); its shipped lib is byte-identical bar the version string.

Transitive dep (no Gemfile change). 14 commits analyzed, 0 mitigations.

https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-sinatra_v4.1.1..v4.2.1.md

🤖 Assisted by Claude
2026-06-09 12:42:51 -04:00
Mike Dalessio 52ccd24efa dep: update action_text-trix 2.1.15 → 2.1.19
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
2026-06-09 12:42:51 -04:00
Mike Dalessio c189ddea41 dep: update net-imap 0.5.12 → 0.6.4
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
2026-06-09 12:42:51 -04:00
Mike Dalessio 7ffb043cf3 dep: update erb 6.0.0 → 6.0.4
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
2026-06-09 12:42:51 -04:00
Mike Dalessio 17f81d4fa0 dep: update jwt 3.1.2 → 3.2.0
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
2026-06-09 12:42:51 -04:00
Mike Dalessio 905165b7e2 dep: update bcrypt 3.1.20 → 3.1.22
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
2026-06-09 12:42:51 -04:00
Mike Dalessio ce62fd0814 dep: update addressable 2.8.7 → 2.9.0
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
2026-06-09 12:42:51 -04:00
Mike Dalessio 47cdbb2a0f dep: update rexml 3.4.1 → 3.4.4
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
2026-06-09 12:42:51 -04:00
Mike Dalessio 6ec04cbc28 dep: update rack-session 2.1.1 → 2.1.2
Security bump for CVE-2026-39324 (Rack::Session::Cookie secretless
session forgery). Campfire uses Rails ActionDispatch CookieStore, not
Rack::Session::Cookie, so was never exposed — hygiene update.

Transitive dep (no Gemfile change). 10 commits analyzed, 0 mitigations.

https://github.com/basecamp/37signals-hq/blob/main/upgrade-analysis/campfire-20260609-rack-session_2.1.1..2.1.2.md

🤖 Assisted by Claude
2026-06-09 12:42:51 -04:00
Mike Dalessio e74d192aec dep: explicitly add bundler-audit 2026-06-09 12:42:51 -04:00
Mike Dalessio 67ca61da1f fix(docker): install libssl-dev to build the openssl gem
The build stage was missing the OpenSSL headers, so bundle install
failed compiling the openssl gem's native extension.

🤖 Assisted by Claude
2026-06-09 12:42:51 -04:00
Mike Dalessio 0febc9f415 dep: update to bundler 4 2026-06-09 12:20:38 -04:00
Mike Dalessio 4a75954080 style(rubocop): Layout/SpaceInsideArrayLiteralBrackets 2026-06-09 12:20:37 -04:00
Mike Dalessio 69c94b6594 bin/setup only asks for pacman install password when needed 2026-06-09 12:20:37 -04:00
Stanko Krtalić ba11a0a67f Merge pull request #192 from ESH13/fix-negative-limit-direct-placeholders
Fix crash when user has 20+ direct conversations
2026-04-13 16:47:16 +02:00
Eric Hiler 8c7f49e003 Fix crash when user has 20+ direct conversations 2026-04-10 09:23:30 -04:00
John-Mason Shackelford 20ab5b1bc6 feat: Add bot API endpoint for adding reactions (boosts) to messages
Adds a new endpoint that allows bots to add emoji reactions (boosts) to messages:
POST /rooms/:room_id/:bot_key/messages/:message_id/boosts

This enables bots to acknowledge messages with reactions like 👀 (eyes) when
mentioned, providing immediate feedback to users before generating a full response.

The endpoint:
- Validates the bot is a member of the room
- Validates the message exists in the room
- Broadcasts the boost to connected clients via Turbo Streams
- Returns 201 Created on success, 404 if room/message not found

Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-08 18:53:03 -04:00
John-Mason Shackelford e8c1349aac style: Use bot? predicate instead of role comparison
Changed `message.creator.role == "bot"` to `message.creator.bot?` per
Copilot code review. This is the idiomatic Rails pattern for enum checks
and consistent with how other role checks are done in the codebase.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-08 14:51:21 -04:00
John-Mason Shackelford c82a8d38c0 fix: Use correct room messages in pagination tests
Fixed test bug identified by Copilot code review: pagination tests were
using Message.first and Message.last which could return messages from
different rooms than the test's @room (watercooler).

Now explicitly using messages(:fourth) and messages(:thirteenth) which
are fixtures belonging to the watercooler room where bender bot is a member.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-08 14:45:02 -04:00
John-Mason Shackelford 057d56513e fix: Align create and index to both return 404 for non-member rooms
Both create and index now return HTTP 404 Not Found when a bot tries to
access a room it's not a member of. This is consistent with REST API
security best practices (not revealing resource existence) and ensures
read and write permissions are handled identically.

Changed create action to no longer call super (which rendered HTML) and
instead directly handle the request with proper JSON API error responses.

Added test to verify create returns 404 for non-member rooms.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-08 13:59:56 -04:00
John-Mason Shackelford 5340f3da01 fix: Ensure bot can only read messages from rooms it is a member of
Added explicit RecordNotFound handling to return 404 when a bot tries to
read messages from a room it's not a member of. This matches the security
model used by the create action.

Added tests to verify:
- Bot gets 404 when trying to read from room it's not a member of
- Bot can successfully read from room it IS a member of

Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-08 13:56:02 -04:00
John-Mason Shackelford d4a56784e0 feat: Add bot API endpoint for reading room messages
Adds a new GET endpoint at /rooms/:room_id/:bot_key/messages that allows
bots to read messages from rooms they are members of.

The endpoint returns JSON with:
- Room info (id, name)
- Messages array with body (plain/html), created_at, and creator info
- Pagination info (oldest_id, newest_id, has_more)

Supports pagination via ?before=:id and ?after=:id query parameters,
consistent with the existing pagination in the messages controller.

This enables AI bots and other automated agents to understand conversation
context when responding to messages, rather than only receiving the single
message that triggered the webhook.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-08 13:53:17 -04:00
Mike Dalessio 3fada3d997 ci: harden GitHub Actions workflows (#185)
* 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>
2026-03-20 19:26:25 -04:00
Rosa Gutierrez dde94b06ed Delete server-side session on logout
When it's set. Also, store it in current attributes for convenience.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 09:31:22 +01:00
Jeremy Daer 1852adb06c Fix 1Password account ID (was user UUID) (#156) 2025-12-31 13:59:12 -08:00
Jeremy Daer e983e3f79f Block IPv6 SSRF bypass via ipv4_compat addresses (#153)
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
2025-12-31 13:01:43 -08:00
Jeremy Daer 53e36a16ea Latest Brakeman 2025-12-31 12:56:01 -08:00
Jeremy Daer a05292f548 Switch 1Password account to 37signals.1password.com (#154) 2025-12-31 11:54:27 -08:00
Stanko Krtalić 71ffeeea78 Merge pull request #147 from basecamp/fix-sgid-decoding
Try to decodde SGIDs in multiple ways
v1.4.3
2025-12-15 17:05:01 +01:00
Stanko K.R. 77bcad65b5 Try to decode SGIDs in multiple ways
This should avoid message decoding failures between different versions
of sgids
2025-12-15 17:00:50 +01:00
Mike Dalessio 238f73c26f Merge pull request #146 from basecamp/flavorjones/fix-account-creation-race
Address race condition during "first run" account creation
2025-12-12 11:14:52 -05:00
Mike Dalessio 1feb2d94b9 Address race condition during "first run" account creation 2025-12-12 10:51:28 -05:00
Jason Zimdars 49c0ce496c Merge pull request #144 from basecamp/user-self
User self
2025-12-08 23:03:07 -06:00
Jason Zimdars 88f3f942f7 Ensure edit (not trash) is displayed
Fix conditional
2025-12-08 22:57:38 -06:00
Jason Zimdars 6f256f5f2d Show admin toggle, but disable for current user
You shouldn't be able to demote yourself but displaying the disabled
toggle helps explain the group of admins at the top of the list. Builds
upon #140
2025-12-08 22:53:11 -06:00
Jason Zimdars 089a8b35c0 Merge pull request #140 from ashwin47/admin-ordering
Sort users with administrators appearing first in ordered scope
2025-12-08 22:33:49 -06:00
Ashwin M b52c318518 Group administrators separately from members with visual divider 2025-12-09 08:42:00 +05:30
Stanko Krtalić de5493d8a9 Merge pull request #124 from ashwin47/ban_typo
Fix button label to 'UnBan' for clarity
2025-12-06 11:42:50 +01:00
Ashwin M 74346342df Rewrite label 2025-12-06 16:09:13 +05:30
Stanko Krtalić e4a49d52b2 Merge pull request #125 from mphalliday/involvement-fix
Allow non-admins to update their room involvements
2025-12-04 21:43:31 +01:00
Michael Halliday b8919161a8 Allow non-admins to update their room involvements 2025-12-03 09:56:15 -05:00
Ashwin M 80585a9585 Fix button label to 'UnBan' for clarity 2025-12-03 17:50:16 +05:30
Stanko K.R. b3d97ecb0e Add safety checks to release script 2025-12-03 08:24:04 +01:00
Stanko Krtalić 94692026d3 Merge pull request #122 from basecamp/unfurl-blind-ssrf
Security: disallow blind SSRF to link-local IPs via URL unfurling
v1.4.2
2025-12-03 08:12:24 +01:00
Stanko K.R. 0672673916 Disallow SSRF via IPv6 addresses mapped to IPv4 addresses 2025-12-03 08:08:34 +01:00
Jeremy Daer 5667262d1c Security: disallow blind SSRF to link-local IPs via URL unfurling 2025-12-02 21:33:44 -08:00
Stanko Krtalić 1babf3f9ed Merge pull request #121 from basecamp/fix-crash-on-mentions-when-upgrading-from-rails-7-to-8
Parse Rails 7 GIDs
v1.4.1
2025-12-02 11:37:51 +01:00
Stanko K.R. 4d04f9beee Use urlsafe base64 decode 2025-12-02 11:34:12 +01:00