* 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.
Campfire
Campfire is a web-based chat application. It supports many of the features you'd expect, including:
- Multiple rooms, with access controls
- Direct messages
- File attachments with previews
- Search
- Notifications (via Web Push)
- @mentions
- API, with support for bot integrations
Running your own Campfire instance
Campfire's Docker image contains everything needed for a fully-functional,
single-machine deployment. This includes the web app, background jobs, caching,
file serving, and SSL. You can use our pre-built image at
ghcr.io/basecamp/once-campfire:latest, or build your own from this repo.
Deploying with ONCE
The easiest way to self-host Campfire is with ONCE. It will guide you through the initial set up and then keep your instance up to date automatically.
If you don't already have once installed, run this on the machine you want to run Campfire on:
curl https://get.once.com | sh
once will launch as soon as the install is finished.
Choose Campfire from the list of applications, follow the instructions, and ONCE will take care of the rest.
If you prefer the command line to the dashboard, you can deploy directly:
once deploy ghcr.io/basecamp/once-campfire --host chat.example.com
Deploying with Docker
If you'd rather run the Docker image yourself, you can read more about that in the self-hosting guide.
Tip
When you start Campfire for the first time, you'll be guided through a wizard to create an admin account. The email address that you enter for the admin account will be visible on the sign-in page, it's there so that people have someone to contact if they need help with their account. If that bothers you, put in any email address you want and create yourself a new admin account.
Development
You are welcome - and encouraged - to modify Campfire to your liking. Please see our development guide for how to get Campfire set up for local development.
Security
See SECURITY.md for how to report a vulnerability and a description of our trust model.