Commit Graph

255 Commits

Author SHA1 Message Date
Stanko K.R. 5c5821a395 Keep an unsent message as a draft while switching rooms
The composer stores what's being written in localStorage per room and
restores it when the room is opened again. Sending clears it.
2026-09-26 10:38:13 +02:00
Stanko Krtalić 8fa6138fb9 Merge pull request #254 from basecamp/dependabot/bundler/web-push-3.1.0
build(deps): bump web-push from 3.0.2 to 3.1.0
2026-09-26 10:28:20 +02:00
Stanko Krtalić beaf467a38 Merge pull request #257 from basecamp/dependabot/bundler/mocha-3.1.0
build(deps-dev): bump mocha from 2.7.1 to 3.1.0
2026-09-26 10:27:58 +02:00
Stanko Krtalić 9a258bd1a0 Merge pull request #224 from basecamp/replace-trix-with-lexxy
Replace Trix with Lexxy
2026-09-26 10:27:29 +02:00
Stanko Krtalić f8e36e9761 Merge pull request #282 from basecamp/dependabot/bundler/rubyzip-3.4.0
build(deps-dev): bump rubyzip from 3.0.2 to 3.4.0
2026-09-26 10:23:25 +02:00
Stanko Krtalić 83e7a7f4c9 Merge pull request #281 from basecamp/dependabot/github_actions/github-actions-f89073ee97
build(deps): bump the github-actions group with 4 updates
2026-09-26 10:23:12 +02:00
Stanko Krtalić eadbaab7d2 Merge pull request #262 from excid3/redis-url-env
Support Redis configuration with REDIS_URL env var
2026-09-26 10:22:53 +02:00
Stanko K.R. b5d3543e64 Test that Trix-formatted messages render and survive editing 2026-09-26 10:21:40 +02:00
Stanko K.R. 1694accbf8 Let tables through the message sanitizers
Lexxy imports pasted and Markdown tables, and the tag sanitizer dropped
them with everything in them on render.
2026-09-26 10:04:38 +02:00
Stanko K.R. 8bdff5ddc6 Run the link preview hardening tests against both stored forms
Every case only exercised the Trix attribute form, so dropping the URL
validation from the Lexxy content parser went unnoticed.
2026-09-26 09:59:13 +02:00
Stanko K.R. 6acad0549d Rebuild every attachment before editing a message
The editor keeps an attachment's content as it finds it, so a hand-written
embed with a url but no href reached the editor with its markup unvalidated,
and a mention saved under Trix's editor carried the generic octet-stream
content type the editor doesn't permit and was dropped on save. Rebuilding
each attachment from its attachable renders the hardened preview partial
and restores the mention content type.
2026-09-26 09:51:05 +02:00
dependabot[bot] 6e7dd2fa0a build(deps-dev): bump rubyzip from 3.0.2 to 3.4.0
Bumps [rubyzip](https://github.com/rubyzip/rubyzip) from 3.0.2 to 3.4.0.
- [Release notes](https://github.com/rubyzip/rubyzip/releases)
- [Changelog](https://github.com/rubyzip/rubyzip/blob/main/Changelog.md)
- [Commits](https://github.com/rubyzip/rubyzip/compare/v3.0.2...v3.4.0)

---
updated-dependencies:
- dependency-name: rubyzip
  dependency-version: 3.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-26 07:33:43 +00:00
Stanko K.R. eab554aa4b Adapt the Lexxy composer to main's link preview hardening
Main strips a javascript: href from a preview node before it's parsed, so
tell legacy Trix attachments apart by their filename instead. escapeHTML
moved to the string helpers, and the unfurling system test now drives
the Lexxy editor.
2026-09-26 09:26:53 +02:00
Stanko K.R. 1bb7ef7c17 Fix Codex's code review comments 2026-09-26 09:13:44 +02:00
Stanko K.R. c38e77a897 Ensure backwards compatibility with Trix 2026-09-26 09:13:35 +02:00
Stanko K.R. 9b1602d088 Polish 2026-09-26 09:13:27 +02:00
Stanko K.R. 47bc5f5425 Replace Trix with Lexxy 2026-09-26 09:13:27 +02:00
dependabot[bot] 07f2482970 build(deps): bump the github-actions group with 4 updates
Bumps the github-actions group with 4 updates: [ruby/setup-ruby](https://github.com/ruby/setup-ruby), [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) and [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `ruby/setup-ruby` from 1.321.0 to 1.324.0
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](https://github.com/ruby/setup-ruby/compare/95ef2b042f9d7a56d8268cba8559e2842e2ad01b...a0102e0972be65f351c307e2d64b9314a57c8073)

Updates `zizmorcore/zizmor-action` from 0.6.3 to 0.6.4
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/70fb788f84895a7701f5643d103d587e460b5c99...cc914d7f3750a2d13d75c7f184a1060aa0e9d482)

Updates `docker/setup-buildx-action` from 4.3.0 to 4.4.1
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069)

Updates `docker/build-push-action` from 7.3.0 to 7.4.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-version: 1.324.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-25 19:35:29 +00:00
Sam Ruby 91d294f4a0 Mint the tampered attachable sgid without extending a Room
The companion to #279: the same per-instance `extend` appeared in
test/lib/rails_ext/action_text_attachables_test.rb. `attachable_sgid` is
`to_sgid(expires_in: nil, for: ActionText::Attachable::LOCATOR_NAME).to_s`,
so mint that directly; the minted bytes and the assertion are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 14:29:07 -05:00
Jeremy Daer 9ff6b4a381 Keep .claude out of the Docker build context (#266)
Agent worktrees under .claude/worktrees are whole extra checkouts of
this repo, and the build context picks them up. .claude is local
session state and never belongs in an image.
2026-09-22 14:09:33 -05:00
Jeremy Daer e3b4cbc674 Bump surfguard to the published 0.2.0 (#271)
Moves the pin from the pre-release 910be91 (0.1.0) to 59e278c, the v0.2.0
tag Fizzy already runs. The default policy now admits IPv6 only inside
IANA-allocated unicast prefixes instead of default-allowing reserved and
unallocated space. No call-site changes: both shim entry points take the
new policy keyword's default.
2026-09-22 14:09:24 -05:00
dependabot[bot] b7da9c63f7 build(deps): bump zizmorcore/zizmor-action in the github-actions group (#277)
Bumps the github-actions group with 1 update: [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).


Updates `zizmorcore/zizmor-action` from 0.6.2 to 0.6.3
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/3dc1ecc9bcb9e94e9b2c709687979e1298497054...70fb788f84895a7701f5643d103d587e460b5c99)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 14:09:10 -05:00
Rosa Gutierrez ed0f9a5dba Merge pull request #279 from rubys/attachment-test-without-extend
Mint the tampered sgid without extending a Room
2026-09-20 21:08:13 +02:00
Sam Ruby 0ed0af44ef Mint the tampered sgid without extending a Room
The invalid-sgid test made one live Room attachable — `rooms(:pets).tap
{ |r| r.extend ActionText::Attachable }` — only to call
`attachable_sgid` on it. That method is `to_sgid(expires_in: nil,
for: ActionText::Attachable::LOCATOR_NAME).to_s`, so the test can mint
the same sgid directly and drop the per-instance extend; the assertion
(a signature that does not verify resolves to MissingAttachable) is
unchanged, and so are the bytes it tampers with.
2026-09-20 11:19:14 -04:00
Rosa Gutierrez 977cbcd135 Merge pull request #276 from basecamp/card-7348960853-room-render-injection
Render link previews only from web URLs
2026-09-11 21:29:14 +02:00
Rosa Gutierrez 5742dfaf73 Cover markup-only OpenGraph title and description in link previews
Link previews fetch a page's OpenGraph title and description, and
Opengraph::Metadata strips tags from both before the values reach the
browser. When a field consists entirely of a markup tag, stripping
leaves it blank, the metadata fails its presence validation, and the
unfurl endpoint returns no content, so no preview is produced.

Add regression tests at the model and controller layers that pin this:
a title or description made only of a markup tag is stripped to blank
and rejected, and the endpoint answers 204. The existing sanitize tests
only cover fields that keep non-blank text after stripping, so this
blank-and-rejected path was previously untested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 21:11:13 +02:00
Rosa Gutierrez 8722057545 Keep one escapeHTML, and make it safe in an attribute
There were two: the one in dom_helpers escaped through a text node, which
leaves double quotes alone, so it could not have closed the hole in the
link preview's img src.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 21:10:57 +02:00
Rosa Gutierrez e6022a52c3 Assert only that the preview image gained no extra attributes
Pinning the exact attribute set also pinned which of them Trix's own
sanitizer keeps, which is not what this test is about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 21:10:57 +02:00
Rosa Gutierrez c1ad057db8 Escape the OpenGraph image URL in link previews
Pasting a link builds the preview by interpolating the unfurled metadata
into an HTML string. The image URL went into src="..." unescaped, so a
page whose og:image carries a double quote closes the attribute early and
everything after it becomes attributes on the preview's img element.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 21:10:57 +02:00
Rosa Gutierrez 436ea06457 Read a preview's host as a name by its last label
A domain name ends in a word, which is what keeps it from reading as an
address. "0x7f.0.0.1" carries a dot and a letter, so the previous shape
check let it through while a browser fetched 127.0.0.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 20:25:35 +02:00
Rosa Gutierrez 9e19658ee0 Take a link preview's host as a domain name, not an address
A browser rewrites the many spellings of an address into one before it
fetches, so "http://2130706433/rooms/1" arrives at 127.0.0.1 while a
comparison here still reads the digits. A preview names a page on the
public internet, so require its host to look like a domain name and leave
the rewriting race alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 20:21:01 +02:00
Rosa Gutierrez 32e3e5aea8 Bust the cached message presentation
The room caches each message's rendered presentation, and its key can't
see the link preview partial, which ActionText renders by name rather than
through a render call the digestor can follow. Without a new version, a
message already in the cache would keep its old preview.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 19:20:46 +02:00
Rosa Gutierrez ef4b88d748 Compare a preview's host to ours with the escapes resolved
Ruby leaves a percent-escape in URI#host, so "https://%77ww.example.com"
read as a different host than the one Campfire answers on while a browser
unescaped it straight back to us. A host that carries an escape, or a
trailing dot, is now measured the way the browser will read it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 19:14:59 +02:00
Rosa Gutierrez eceec2898b Keep a link preview's link and image off this Campfire's own host
A preview belongs to the page it previews, so both URLs point somewhere
else. An absolute URL on our own host passed the scheme and host checks,
and every reader's browser fetched it with their session attached, which
turns a message into a GET request made on the reader's behalf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 19:08:54 +02:00
Rosa Gutierrez c3ae67a2b6 Require a host on a link preview's link and image
Ruby parses "https:/rooms/1" as an HTTPS URL with no host, and a browser
resolves it against whatever origin Campfire is served from, so the scheme
check alone still let a message body aim the preview at a path here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 19:02:25 +02:00
Rosa Gutierrez 3a501cd32c Render link previews only from web URLs
A link preview's link and image come from attributes on the message body,
which the composer fills in from the unfurl the server performed. A body
written by hand can put anything in those attributes, and the preview
partial rendered them as they were.

Keep the link and the image only when they parse as absolute http or https
URLs, so nothing in a message body can aim either one at another scheme or
at a path on this Campfire, and render the title and the description as
text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 18:58:04 +02:00
Jeremy Daer ef147d17db Redact bot key from request logs (#269)
The bot HTTP API carries the bot key as a URL path segment
(/rooms/:room_id/:bot_key/...). config.filter_parameters redacts query
and form parameters but never path segments, so the key was written
verbatim to the request log (the "Started POST ..." line) and to any
log line echoing the pagination Link header.

Add a log formatter that redacts the bot-key path segment wherever it
appears in a formatted line, and wire it into the production logger.
2026-09-01 12:40:55 -07:00
Jeremy Daer 9dd19d0c95 Close live Action Cable connections on sign out (#268)
Action Cable authorizes a Connection once at the WebSocket handshake and
never re-checks it. Destroying the session record refuses future handshakes
and HTTP requests bearing the cookie, but a socket opened before sign out
keeps its handshake-time current_user and keeps authorizing new
subscriptions and delivering frames as the signed-out user.

Reset the user's remote connections when the session is terminated. Clients
tear down and reconnect: the signed-out device carries a destroyed session
and cleared cookie and is rejected at the fresh handshake, while the user's
other devices with still-valid sessions reconnect and stay live. This reuses
the existing reset_remote_connections primitive already used on membership
removal, for the same reason.

Run the disconnect last and best-effort, after the session record and cookie
are already gone, so sign out completes even when the realtime service is
unreachable.
2026-08-31 18:21:16 -07:00
Jeremy Daer 79eb9a5516 Require authentication for Active Storage direct uploads (#267)
Active Storage mounts its direct-upload write endpoints -- POST
/rails/active_storage/direct_uploads and the disk-service PUT at
/rails/active_storage/disk/:token -- on framework controllers that inherit
from ActiveStorage::BaseController, so they never pass through
ApplicationController's require_authentication. Anyone who can read the
public login page can lift a CSRF token and Rails session cookie, POST to
the metadata endpoint, and receive a signed disk PUT URL without holding a
Campfire session_token.

That is enough to allocate ActiveStorage::Blob rows and persist bytes to
disk anonymously. The blobs stay unattached (no message can be created
without an account) and nothing purges them, so an unauthenticated caller
can grow storage without bound. Because the recommended self-host layout
co-locates uploaded files and the SQLite database on one /rails/storage
volume, that growth eventually makes database writes fail -- blocking login
and messaging until an administrator frees space and purges the blobs.

Campfire uploads attachments through MessagesController as a normal
multipart POST and does not use direct uploads at all, so these endpoints
have no legitimate anonymous caller. Require a valid Campfire session
before the metadata endpoint allocates a blob or the disk endpoint accepts
an upload; both return 401 to anonymous callers. Serving (disk#show,
representations, blob redirects) is unchanged.
2026-08-30 10:24:41 -07:00
Jeremy Daer 94a48aacb6 Guard push-subscription endpoints against SSRF (#265)
* Guard push-subscription endpoints against SSRF

Web push delivery POSTed to the endpoint URL a user supplied when
registering a subscription, with no scheme, host, or private-network
check -- unlike the OpenGraph unfurl path, which already routes through
the shared SSRF address policy (surfguard). Any authenticated user could
register a subscription whose endpoint pointed at an internal address and
have the server fetch it on every chat message: blind SSRF for internal
recon and reachability probing, plus a thread-pool DoS on the delivery
pool.

Validate the endpoint when the subscription is saved: it must be HTTPS,
its host must belong to a known browser push service (allowlist), and it
must resolve to a public IP. On every delivery, re-resolve the host and
pin the connection to that public IP so a later DNS rebind can't redirect
the request to an internal address. If no public IP resolves at delivery
time -- a rebind, or a subscription that predates this validation --
delivery is skipped rather than falling back to re-resolving the raw
host.

Adds model, controller, and delivery-pinning tests, plus a DNS stub
helper for deterministic resolution in tests.

* Route push endpoint guarding through RestrictedHTTP::PrivateNetworkGuard

The endpoint SSRF check already delegated its private-network classification
to surfguard, but called Surfguard.resolve_public_ips directly rather than
through RestrictedHTTP::PrivateNetworkGuard -- the hostname-in, address-out
shim #241 established as the app's single guarded-outbound entry point and
that Opengraph::Fetch resolves through. Route push resolution through the same
guard so once-campfire keeps one place that resolves and classifies outbound
addresses. Behavior is unchanged: the guard raises Violation when a permitted
host resolves only to blocked addresses (previously an empty list -> nil) and
propagates Surfguard::Unresolvable for a host that resolves to nothing; both
map to a nil endpoint IP, which fails validation and skips delivery. The
allowlist, HTTPS/443 constraints, and per-delivery IP pinning are unchanged.

* Address push-SSRF review: defer DNS off enqueue path, disable proxy on pinned path, revalidate on re-registration

- Resolve the guarded endpoint IP lazily inside WebPush::Notification#deliver
  (on the bounded delivery worker) instead of eagerly when the notification is
  built on the serial enqueue path, so a slow resolver can't stall the push job
  before any delivery starts. resolved_endpoint_ip only reads the already-loaded
  endpoint attribute, so it is safe off the AR connection.
- Pin the delivery socket with an explicit nil proxy address so http_proxy/
  https_proxy can't route the request through a proxy that re-resolves the host
  and defeats the ipaddr pin.
- Revalidate an existing subscription on re-registration so a row predating
  endpoint validation gets the same 422 as a fresh create instead of being kept
  alive by touch.
- Regression tests: resolution deferred to delivery, pin survives proxy env,
  legacy invalid row rejected with 422.

* Bound the web-push delivery queue (max_queue, not the ignored queue_size)

Concurrent::ThreadPoolExecutor takes :max_queue; :queue_size was silently
ignored, leaving the delivery backlog unbounded (max_queue: 0). A flood of
valid push subscriptions could accumulate queued deliveries without limit --
more acute now that each delivery task also resolves DNS. Using max_queue: 10000
activates the intended cap; overflow raises RejectedExecutionError under the
default :abort policy, which deliver_later already rescues (push is best-effort,
retried on the next message).

* Trim push-SSRF guard comments to match house style

Apply the review suggestions on the push-subscription SSRF guard: replace
the verbose rationale comments with terse one-liners (or drop them where
the code speaks for itself). No behavior change -- the Surfguard-shim
routing, per-delivery public-IP pin, and bounded delivery queue are
untouched.
2026-08-28 11:44:39 -07:00
Chris Oliver c7ad91c088 Support Redis configuration with REDIS_URL env var 2026-08-24 11:26:38 -05:00
Jeremy Daer e63402d60e Merge pull request #259 from basecamp/dependabot/github_actions/github-actions-ed39cb2a14
build(deps): bump the github-actions group across 1 directory with 7 updates
2026-08-24 08:39:19 -07:00
dependabot[bot] 6e25ddd92b build(deps): bump the github-actions group across 1 directory with 7 updates
Bumps the github-actions group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4.3.1` | `7.0.1` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3.11.1` | `4.3.0` |
| [docker/login-action](https://github.com/docker/login-action) | `3.5.0` | `4.6.0` |
| [docker/metadata-action](https://github.com/docker/metadata-action) | `5.8.0` | `6.2.0` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `6.18.0` | `7.3.0` |
| [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) | `3.0.0` | `4.2.2` |
| [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) | `3.9.2` | `4.1.2` |



Updates `actions/checkout` from 4.3.1 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.3.1...3d3c42e5aac5ba805825da76410c181273ba90b1)

Updates `docker/setup-buildx-action` from 3.11.1 to 4.3.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/e468171a9de216ec08956ac3ada2f0791b6bd435...37fe631027851001ddb9b187196cc803df7f5f0e)

Updates `docker/login-action` from 3.5.0 to 4.6.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/184bdaa0721073962dff0199f1fb9940f07167d1...dbcb813823bdd20940b903addbd779551569679f)

Updates `docker/metadata-action` from 5.8.0 to 6.2.0
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/c1e51972afc2121e065aed6d45c65596fe445f3f...dc802804100637a589fabce1cb79ff13a1411302)

Updates `docker/build-push-action` from 6.18.0 to 7.3.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/263435318d21b8e681c14492fe198d362a7d2c83...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a)

Updates `actions/attest-build-provenance` from 3.0.0 to 4.2.2
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/977bb373ede98d70efdf65b84cb5f73e068dcc2a...4d101475d8b20a2381f78447822ac1eab6504dd8)

Updates `sigstore/cosign-installer` from 3.9.2 to 4.1.2
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/d58896d6a1865668819e1d91763c7751a165e159...6f9f17788090df1f26f669e9d70d6ae9567deba6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/login-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/metadata-action
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/attest-build-provenance
  dependency-version: 4.2.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 02:19:57 +00:00
Jeremy Daer fbc3fba364 Merge pull request #258 from basecamp/retire-docker-updater-and-unify-ruby
Retire the inert docker updater, bump setup-ruby, and put every Ruby pin on 3.4.10
2026-08-23 19:18:19 -07:00
Jeremy Daer b827d7ca9b Put every Ruby pin on 3.4.10
Three pins, two values: Dockerfile said 3.4.5, Dockerfile-export said
3.4.7, .ruby-version said 3.4.5 — the export image had already been
bumped on its own and nobody noticed the other two lagging. Both
Dockerfiles carry a comment telling you to keep them in step.

3.4.10 is the current 3.4.x. CI resolves its Ruby from .ruby-version,
so this is the version the tests now run under.
2026-08-22 12:30:43 -07:00
Jeremy Daer 22b6d4bdaf Bump ruby/setup-ruby to v1.321.0
v1.295.0 predates Ruby 3.4.10 — its baked-in version index stops at
3.4.9, so asking for anything newer fails with "Unknown version 3.4.10
for ruby on ubuntu-24.04". The next commit needs 3.4.10, and pinning
our Ruby to whatever an old action happens to know is backwards.

SHA verified against the v1.321.0 tag.
2026-08-22 12:30:43 -07:00
Jeremy Daer 431aad8b71 Retire the docker ecosystem from Dependabot
The base image tag is a build arg — `ARG RUBY_VERSION` plus
`FROM ruby:$RUBY_VERSION-slim` — and Dependabot's docker updater matches
literal tags, so this entry has never had anything to propose. It ran
green every week and reported nothing, which reads as coverage and
isn't.

No other repo in the fleet configures a docker ecosystem, and none
could: they all either interpolate a variable or pull from the internal
registry. Inlining the tag here to buy coverage would make this
Dockerfile the outlier instead, against Rails-generated boilerplate.
Ruby bumps stay a manual, human-decided step.
2026-08-22 02:21:49 -07:00
dependabot[bot] aa9fe42810 build(deps-dev): bump mocha from 2.7.1 to 3.1.0
Bumps [mocha](https://github.com/freerange/mocha) from 2.7.1 to 3.1.0.
- [Changelog](https://github.com/freerange/mocha/blob/main/RELEASE.md)
- [Commits](https://github.com/freerange/mocha/compare/v2.7.1...v3.1.0)

---
updated-dependencies:
- dependency-name: mocha
  dependency-version: 3.1.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-22 04:52:14 +00:00
dependabot[bot] db5275918f build(deps): bump web-push from 3.0.2 to 3.1.0
Bumps [web-push](https://github.com/pushpad/web-push) from 3.0.2 to 3.1.0.
- [Release notes](https://github.com/pushpad/web-push/releases)
- [Commits](https://github.com/pushpad/web-push/compare/v3.0.2...v3.1.0)

---
updated-dependencies:
- dependency-name: web-push
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-22 04:51:22 +00:00
Jeremy Daer 9310b002d7 Drop the unsupported semver cooldown keys from the docker ecosystem (#251)
The docker block copied bundler's cooldown wholesale, but Dependabot only
accepts semver-major/minor/patch-days for ecosystems whose versions it
classifies as semver, and container tags aren't. One invalid property
invalidates the entire file rather than the block it sits in, so since
this config landed in #248 the version updater has not run for any
ecosystem at all:

  Your .github/dependabot.yml contained invalid details
  The property '#/updates/2/cooldown/semver-major-days' is not supported
  for the package ecosystem 'docker'. (and -minor-, -patch-)

That is why #249's cooldown exclude for brakeman never took effect, and
why #250 had to bump the workflow linter pins by hand while every other
repo got a Dependabot PR. It also left `bin/brakeman --ensure-latest 15`
armed with nothing to disarm it: the lock pins brakeman 8.0.6, and CI
would have gone red roughly 15 days after 8.0.7 shipped.

Security updates were never affected — those don't read this file, which
is why the only four Dependabot runs here are single-gem security bumps.

docker keeps default-days, which is supported for every ecosystem.
2026-08-21 21:49:40 -07:00