diff --git a/README.md b/README.md index c5ca10c..6a46b23 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,7 @@ If you'd rather run the Docker image yourself, you can read more about that in t You are welcome - and encouraged - to modify Campfire to your liking. Please see our [development guide](docs/development.md) for how to get Campfire set up for local development. + +## Security + +See [SECURITY.md](SECURITY.md) for how to report a vulnerability and a description of our trust model. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4472371 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Security + +Report vulnerabilities through our [HackerOne program](https://hackerone.com/basecamp) or at +https://github.com/basecamp/once-campfire/security. Note that Campfire is in scope for our security +program, but is not bounty eligible. + +## Trust model + +Campfire is self-hosted and single-tenant, so the administrator is the server operator, +with shell, network, and database access already. Anything requiring the administrator +role grants nothing they do not already have, and is not a vulnerability. + +We do want reports of anything a **non-administrator** can reach, and of any credential or +network path held by the Campfire process but not by the operator's own shell. + +## Intentional behavior + +Bot webhook URLs are unrestricted: an administrator can point one at any address, +including internal ones, because operators legitimately wire bots to their own services. +Link unfurling is different because any member can trigger it by pasting a URL, so it +validates destinations through `RestrictedHTTP::PrivateNetworkGuard`. The difference is who +picks the destination. diff --git a/app/models/webhook.rb b/app/models/webhook.rb index d8e7b43..5c307e7 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -24,6 +24,8 @@ class Webhook < ApplicationRecord Net::HTTP::Post.new(uri, "Content-Type" => "application/json").tap { |request| request.body = payload } end + # No PrivateNetworkGuard, unlike Opengraph::Fetch: only an administrator sets this URL, + # and operators legitimately point bots at their own internal services. def http Net::HTTP.new(uri.host, uri.port).tap do |http| http.use_ssl = (uri.scheme == "https")