Compare commits

...
16 Commits
Author SHA1 Message Date
Donal McBreen 69e8cd7885 Merge pull request #225 from basecamp/security/ssrf-ipv6-transition-guard
Block IPv6 addresses that reach internal IPs in the unfurl guard
2026-07-21 12:34:38 +01:00
Donal McBreen c0cade51a1 Address Copilot review: require ipaddr and re-assert the socket port
The guard uses IPAddr but relied on something else loading it first;
require it explicitly. And the rebinding tests lost their port assertion
when the matchers were loosened for newer Net::HTTP keyword args, so
check the port alongside the IP again.
2026-07-21 11:53:37 +01:00
Donal McBreen 80fdd44622 Remove IPv4 ranges the IPAddr predicates already cover
The RFC1918, loopback, and link-local ranges in DISALLOWED_IPV4
duplicated the private?/loopback?/link_local? checks that run right
before the list scan, so they could never be the deciding factor. Keep
only the ranges the predicates don't catch.
2026-07-21 11:37:17 +01:00
Donal McBreen 4cfcc2a370 Re-check the IPv4 embedded in local-use NAT64 instead of blocking outright
Matches the fizzy guard: the local-use NAT64 prefix (64:ff9b:1::/48,
RFC 8215) embeds an IPv4 target in its low 32 bits just like the
well-known prefix, so run it through the same embedded-IPv4 recheck.
Local-use NAT64 to a public address now resolves (keeping unfurls
working for self-hosters on such networks) while local-use NAT64 to an
internal address stays blocked.
2026-07-20 17:13:18 +01:00
Donal McBreen 9085adcbb3 Block local-use NAT64 and IPv6 benchmarking ranges
The local-use NAT64 prefix (64:ff9b:1::/48, RFC 8215) embeds an IPv4
target like the well-known prefix does, but at a deployment-chosen
position we can't extract, so block the whole range outright.

Also block the IPv6 benchmarking range (2001:2::/48, RFC 5180) to match
the IPv4 benchmarking block on 198.18.0.0/15.
2026-07-20 16:49:40 +01:00
Donal McBreen 0de302c977 Fix unfurl rebinding tests for newer Ruby
Net::HTTP now passes an open_timeout: option to TCPSocket.open, so the mock
that matched exact positional arguments no longer matches. Match on the
host instead.
2026-07-20 16:20:15 +01:00
Donal McBreen 9fb419e469 Block IPv6 addresses that reach internal IPs in the unfurl guard
The guard blocked the usual private, loopback, and link-local ranges (and
the IPv4-mapped/-compatible IPv6 forms), but let through NAT64, 6to4, and
Teredo addresses, which can point at an internal IPv4, and CGNAT.

Now it pulls the IPv4 out of a NAT64 address and checks that (so NAT64 to a
public site still works), blocks 6to4 and Teredo outright, and adds the
missing IPv4 and IPv6 ranges.
2026-07-20 16:20:15 +01:00
Stanko K.R. df5ed25e14 Recommend the latest tag over main 2026-07-16 21:17:17 +02:00
Stanko K.R. 048ef90ae7 Add the missing development guide 2026-07-16 21:17:17 +02:00
Mike Dalessio ea994053ea Bump sqlite3 to 2.9.5 (#222)
to resolve security advisories
2026-07-16 02:13:05 -04:00
Mike Dalessio 018a0b5dd9 Bump websocket-driver to 0.8.2
Conservative lockfile-only bump clearing four advisories affecting
websocket-driver < 0.8.2: GHSA-2x63-gw47-w4mm, CVE-2026-54463,
CVE-2026-54464, CVE-2026-54465.

Notes: https://github.com/faye/websocket-driver-ruby/blob/main/CHANGELOG.md
2026-07-16 01:52:48 -04:00
Mike Dalessio 38741c53f1 Upgrade loofah to 2.25.2 and rails-html-sanitizer to 1.7.1 (#221)
Coordinated security releases; 1.7.1 requires loofah >= 2.25.2.

- https://github.com/flavorjones/loofah/releases/tag/v2.25.2
- https://github.com/rails/rails-html-sanitizer/releases/tag/v1.7.1
2026-07-16 00:51:35 -04:00
Stanko K.R. cf0a67fcaf Add a development guide 2026-07-15 13:43:14 +02:00
Stanko K.R. e945d4217b Explain how we tag docker images 2026-07-15 13:39:42 +02:00
Stanko K.R. cccfb8f774 Add backup and restore instructions 2026-07-15 13:34:09 +02:00
Stanko K.R. 55fbf16629 Improve self-hosting instructions 2026-07-15 13:29:13 +02:00
8 changed files with 430 additions and 49 deletions

No files matched your search

+8 -8
View File
@@ -201,7 +201,7 @@ GEM
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
loofah (2.24.1)
loofah (2.25.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.0)
@@ -278,8 +278,8 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
rails-html-sanitizer (1.7.1)
loofah (~> 2.25, >= 2.25.2)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails_autolink (1.1.8)
actionview (> 3.1)
@@ -367,10 +367,10 @@ GEM
rack-protection (= 4.2.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
sqlite3 (2.7.3-aarch64-linux-gnu)
sqlite3 (2.7.3-arm64-darwin)
sqlite3 (2.7.3-x86_64-darwin)
sqlite3 (2.7.3-x86_64-linux-gnu)
sqlite3 (2.9.5-aarch64-linux-gnu)
sqlite3 (2.9.5-arm64-darwin)
sqlite3 (2.9.5-x86_64-darwin)
sqlite3 (2.9.5-x86_64-linux-gnu)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.8)
@@ -397,7 +397,7 @@ GEM
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket (1.2.11)
websocket-driver (0.8.0)
websocket-driver (0.8.2)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
+28 -34
View File
@@ -11,51 +11,45 @@ expect, including:
- @mentions
- API, with support for bot integrations
## Deploying with Docker
## 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.
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.
To persist storage of the database and file attachments, map a volume to `/rails/storage`.
### Deploying with ONCE
To configure additional features, you can set the following environment variables:
The easiest way to self-host Campfire is with [ONCE](https://github.com/basecamp/once).
It will guide you through the initial set up and then keep your instance up to date automatically.
- `SSL_DOMAIN` - enable automatic SSL via Let's Encrypt for the given domain name
- `DISABLE_SSL` - alternatively, set `DISABLE_SSL` to serve over plain HTTP
- `VAPID_PUBLIC_KEY`/`VAPID_PRIVATE_KEY` - set these to a valid keypair to
allow sending Web Push notifications. You can generate a new keypair by running
`/script/admin/create-vapid-key`
- `SENTRY_DSN` - to enable error reporting to sentry in production, supply your
DSN here
If you don't already have `once` installed, run this on the machine you want to run Campfire on:
For example:
```sh
curl https://get.once.com | sh
```
docker build -t campfire .
`once` will launch as soon as the install is finished.
docker run \
--publish 80:80 --publish 443:443 \
--restart unless-stopped \
--volume campfire:/rails/storage \
--env SECRET_KEY_BASE=$YOUR_SECRET_KEY_BASE \
--env VAPID_PUBLIC_KEY=$YOUR_PUBLIC_KEY \
--env VAPID_PRIVATE_KEY=$YOUR_PRIVATE_KEY \
--env TLS_DOMAIN=chat.example.com \
campfire
Choose Campfire from the list of applications, follow the instructions, and ONCE will take care of the rest.
## Running in development
If you prefer the command line to the dashboard, you can deploy directly:
bin/setup
bin/rails server
```sh
once deploy ghcr.io/basecamp/once-campfire --host chat.example.com
```
## Worth Noting
### Deploying with Docker
When you start Campfire for the first time, you’ll be guided through
creating an admin account.
The email address of this admin account will be shown on the login page
so that people who forget their password know who to contact for help.
(You can change this email later in the settings)
If you'd rather run the Docker image yourself, you can read more about that in the [self-hosting guide](docs/self-hosting.md).
Campfire is single-tenant: any rooms designated "public" will be accessible by
all users in the system. To support entirely distinct groups of customers, you
would deploy multiple instances of the application.
> [!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](docs/development.md) for how to get Campfire set up for local development.
+69
View File
@@ -0,0 +1,69 @@
## Development
### Setting up
First, get everything installed and configured with:
```sh
bin/setup
```
This installs the system packages Campfire needs (SQLite, ffmpeg), the right Ruby version (via [mise](https://mise.jdx.dev)), and the app's gems; prepares the database; and starts Redis (in a Docker container called `campfire-redis`, if it isn't already running locally).
If you want to start over at any point, run:
```sh
bin/setup --reset
```
### Running the server
Start the development server with:
```sh
bin/dev
```
You'll be able to access the app at http://localhost:3000.
On first run you'll be guided through creating your admin account, and you can sign in with that account from then on.
Note that Campfire needs Redis (for Action Cable, caching, and background jobs), so if you've restarted your machine or stopped the container, `docker start campfire-redis` will bring it back.
### Web Push notifications
Campfire uses VAPID (Voluntary Application Server Identification) keys to send browser push notifications. For notifications to work in development you'll need to generate a key pair and set these environment variables:
- `VAPID_PRIVATE_KEY`
- `VAPID_PUBLIC_KEY`
You can generate a fresh pair (along with a secret key base, which you can ignore in development) by running:
```sh
script/admin/generate-secrets
```
### Running tests
Run the unit tests with:
```sh
bin/rails test
```
And the browser-based system tests with:
```sh
bin/rails test:system
```
Before pushing your changes, you can run the full CI suite locally - style checks, security audits, and all the tests - with a single command:
```sh
bin/ci
```
### Contributing
You are welcome - and encouraged - to modify Campfire to your liking.
If you'd like to contribute your changes back, please read our [contributing guide](../CONTRIBUTING.md) first.
+213
View File
@@ -0,0 +1,213 @@
## Self-hosting Campfire
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.
> [!TIP]
> The easiest way to self-host Campfire is with [ONCE](https://github.com/basecamp/once), which handles installation, updates, and backups for you. See the [README](../README.md#deploying-with-once) for details. This guide covers running the Docker image by hand.
We recommend using `ghcr.io/basecamp/once-campfire:latest`, which always points to the most recent tagged release - the most stable and battle-tested version of Campfire.
We provide a tagged release for every major, minor and patch version of Campfire, so you can also pin your deployment to a specific version if you want to avoid unexpected changes. For example:
```bash
# exactly version 1.4.4
ghcr.io/basecamp/once-campfire:1.4.4
# any 1.4.x version
# e.g. 1.4.4 or 1.4.5
# the last number is usually changed for bug fixes
ghcr.io/basecamp/once-campfire:1.4
# any 1.x version
# e.g. 1.4.4 or 1.5.0
# the middle number is usually changed for changes to, or addition of, features
ghcr.io/basecamp/once-campfire:1
```
If you want to live on the bleeding edge, the `main` tag tracks the main release branch instead.
It changes with every merged pull request, so it's the newest - but least battle-tested - version of Campfire.
To run it you'll need three things:
1. a machine that runs Docker
2. a mounted volume (so that your database and file attachments are kept around between restarts)
3. some environment variables for configuration
If you'd rather build the image yourself from your own copy of the source, you can do that too:
```sh
docker build -t campfire .
```
### Mounting a storage volume
Campfire keeps all of its storage - the database and uploaded file attachments - inside the path `/rails/storage`.
By default Docker containers don't persist storage between runs, so you'll want to mount a persistent volume into that location.
The simplest way to do this is with the `--volume` flag with `docker run`. For example:
```sh
docker run --volume campfire:/rails/storage ghcr.io/basecamp/once-campfire:latest
```
That will create a named volume (called `campfire`) and mount it into the correct path.
Docker will manage where that volume is actually stored on your server.
You can also specify the data location yourself, mount a network drive, and more.
Check the Docker documentation to find out more about what's available.
### Configuring with environment variables
To configure your Campfire installation, you can use environment variables.
At a minimum you'll want to configure your secret key and your SSL domain.
#### Secrets
Campfire needs a few secret values that are specific to your instance:
- `SECRET_KEY_BASE` - the basis for cryptographic features like signed cookies. This should be a long, unguessable random string.
- `VAPID_PRIVATE_KEY`/`VAPID_PUBLIC_KEY` - a key pair used for sending Web Push notifications.
You can generate them by running:
```sh
docker run --rm ghcr.io/basecamp/once-campfire:latest script/admin/generate-secrets
```
It prints a fresh set of values ready to set as environment variables:
```
SECRET_KEY_BASE=...
VAPID_PRIVATE_KEY=...
VAPID_PUBLIC_KEY=...
```
Keep them safe and reuse the same values across restarts and upgrades - changing them later will invalidate sessions and push notification subscriptions.
#### SSL
If you want the Campfire container to handle its own SSL (HTTPS) automatically (via Let's Encrypt), you just need to specify the domain name that you're running it on.
You can do that with the `TLS_DOMAIN` environment variable.
> [!NOTE]
> If you're using SSL, you'll want to allow traffic on ports 80 and 443.
So if you were running on `chat.example.com` you could enable SSL like this:
```sh
docker run --publish 80:80 --publish 443:443 --env TLS_DOMAIN=chat.example.com ...
```
If you are terminating SSL in some other proxy in front of Campfire, or aren't using SSL at all (for example, if you want to run it locally on your laptop), then you should set `DISABLE_SSL=true` instead and just publish port 80:
```sh
docker run --publish 80:80 --env DISABLE_SSL=true ...
```
#### Error reporting (optional)
To enable error reporting to Sentry in production, supply your DSN in the `SENTRY_DSN` environment variable.
To disable Sentry initialization entirely, set `SKIP_TELEMETRY=true`.
### Example
Putting it all together, here's a complete `docker run` invocation:
```sh
docker run \
--name campfire \
--publish 80:80 --publish 443:443 \
--restart unless-stopped \
--volume campfire:/rails/storage \
--env SECRET_KEY_BASE=$YOUR_SECRET_KEY_BASE \
--env VAPID_PUBLIC_KEY=$YOUR_PUBLIC_KEY \
--env VAPID_PRIVATE_KEY=$YOUR_PRIVATE_KEY \
--env TLS_DOMAIN=chat.example.com \
ghcr.io/basecamp/once-campfire:latest
```
And here's an equivalent `docker-compose.yml` that you could use to run Campfire via `docker compose up`:
```yaml
services:
web:
image: ghcr.io/basecamp/once-campfire:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- SECRET_KEY_BASE=abcdefabcdef
- TLS_DOMAIN=chat.example.com
- VAPID_PRIVATE_KEY=myvapidprivatekey
- VAPID_PUBLIC_KEY=myvapidpublickey
volumes:
- campfire:/rails/storage
volumes:
campfire:
```
### First run
When you start Campfire for the first time, you'll be guided through creating an admin account.
> [!TIP]
> The email address of this admin account will be shown on the login page so that people who forget their password know who to contact for help.
> (You can change this email later in the settings.)
Campfire is single-tenant: any rooms designated "public" will be accessible by all users in the system.
To support entirely distinct groups of customers, you would deploy multiple instances of the application.
### Upgrading
All of Campfire's state lives in the mounted volume, so upgrading is a matter of pulling a newer image and recreating the container:
```sh
docker pull ghcr.io/basecamp/once-campfire:latest
```
Any pending database migrations run automatically when the container boots.
### Backups
To back up your instance, back up the contents of the `/rails/storage` volume.
Because the SQLite database may be written to at any moment, you shouldn't copy its files directly while Campfire is running.
Instead, first run `script/admin/prepare-backup` inside the running container to produce a consistent snapshot of the database (it's written to `storage/backups/` inside the volume):
```sh
docker exec campfire script/admin/prepare-backup
```
(If you're using Docker Compose, replace `docker exec campfire` with `docker compose exec web`)
Then archive the whole storage volume to a file on the host:
```sh
docker run --rm \
--user root \
--volume campfire:/rails/storage \
--volume "$PWD":/backup \
ghcr.io/basecamp/once-campfire:latest \
tar czf "/backup/campfire-backup.tar.gz" -C /rails storage
```
This gives you a `campfire-backup.tar.gz` in your current directory containing the database snapshot and all uploaded files.
Copy it somewhere safe, ideally off the machine.
To restore, extract the archive back into a (stopped) instance's volume, and replace the live database with the snapshot:
```sh
docker run --rm \
--user root \
--volume campfire:/rails/storage \
--volume "$PWD":/backup \
ghcr.io/basecamp/once-campfire:latest \
bash -c "tar xzf /backup/campfire-backup.tar.gz -C /rails &&
cp /rails/storage/backups/production.sqlite3 /rails/storage/db/production.sqlite3 &&
rm -f /rails/storage/db/production.sqlite3-wal /rails/storage/db/production.sqlite3-shm &&
chown -R rails:rails /rails/storage"
```
Then start Campfire again.
+54 -3
View File
@@ -1,3 +1,4 @@
require "ipaddr"
require "resolv"
module RestrictedHTTP
@@ -6,7 +7,32 @@ module RestrictedHTTP
module PrivateNetworkGuard
extend self
LOCAL_IP = IPAddr.new("0.0.0.0/8") # "This" network
# IPv4 special-use ranges (RFC 5735/6890) not already covered by the
# private?/loopback?/link_local? predicates in #disallowed_ipv4?.
DISALLOWED_IPV4 = %w[
0.0.0.0/8 100.64.0.0/10 192.0.0.0/24 192.0.2.0/24 192.88.99.0/24
198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/4 240.0.0.0/4
].map { |cidr| IPAddr.new(cidr) }.freeze
# IPv6 special-use ranges not caught by the predicates. 6to4 (2002::/16) and
# Teredo (2001::/32) are deprecated transition mechanisms with no legitimate
# fetch target, so they are blocked outright. ULA (fc00::/7, incl. the AWS
# IMDSv6 address fd00:ec2::254), link-local, and loopback are covered by the
# predicates in #disallowed_ipv6?.
DISALLOWED_IPV6 = %w[
::/128 100::/64 2001::/32 2001:2::/48 2001:db8::/32 2002::/16
fec0::/10 ff00::/8
].map { |cidr| IPAddr.new(cidr) }.freeze
# NAT64 prefixes: the well-known prefix (RFC 6052/6146) and the local-use
# prefix (RFC 8215). An address here embeds an IPv4 target in its low 32
# bits; we extract it and re-check against the IPv4 rules so NAT64 to a
# public address still resolves while NAT64 to an internal address is
# blocked.
NAT64_PREFIXES = [
IPAddr.new("64:ff9b::/96"),
IPAddr.new("64:ff9b:1::/48")
].freeze
def resolve(hostname)
Resolv.getaddress(hostname).tap do |ip|
@@ -15,11 +41,36 @@ module RestrictedHTTP
end
def private_ip?(ip)
IPAddr.new(ip).then do |ipaddr|
ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? || ipaddr.ipv4_mapped? || ipaddr.ipv4_compat? || LOCAL_IP.include?(ipaddr)
ipaddr = IPAddr.new(ip)
# DNS never legitimately returns these embedded forms, so block them all
# regardless of the address they wrap.
if ipaddr.ipv4_mapped? || ipaddr.ipv4_compat?
true
elsif ipaddr.ipv4?
disallowed_ipv4?(ipaddr)
elsif NAT64_PREFIXES.any? { |prefix| prefix.include?(ipaddr) }
disallowed_ipv4?(embedded_ipv4(ipaddr))
else
disallowed_ipv6?(ipaddr)
end
rescue IPAddr::InvalidAddressError
true
end
private
def disallowed_ipv4?(ipaddr)
ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? ||
DISALLOWED_IPV4.any? { |range| range.include?(ipaddr) }
end
def disallowed_ipv6?(ipaddr)
ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? ||
DISALLOWED_IPV6.any? { |range| range.include?(ipaddr) }
end
def embedded_ipv4(ipaddr)
IPAddr.new([ ipaddr.to_i & 0xffffffff ].pack("N").unpack("C4").join("."))
end
end
end
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env ruby
require "bundler/setup"
require "securerandom"
require "web_push"
vapid_key = WebPush.generate_key
puts "SECRET_KEY_BASE=#{SecureRandom.hex(64)}"
puts "VAPID_PRIVATE_KEY=#{vapid_key.private_key}"
puts "VAPID_PUBLIC_KEY=#{vapid_key.public_key}"
@@ -64,6 +64,50 @@ class RestrictedHTTP::PrivateNetworkGuardTest < ActiveSupport::TestCase
assert_private_ip "::93.184.216.34"
end
test "private_ip? returns true for carrier-grade NAT addresses (RFC6598)" do
assert_private_ip "100.64.0.1"
assert_private_ip "100.127.255.255"
end
test "private_ip? returns true for NAT64 addresses embedding a private IPv4" do
assert_private_ip "64:ff9b::a9fe:a9fe" # NAT64 -> 169.254.169.254 (AWS metadata)
assert_private_ip "64:ff9b::a00:5" # NAT64 -> 10.0.0.5
end
test "private_ip? returns true for local-use NAT64 addresses embedding a private IPv4 (RFC8215)" do
assert_private_ip "64:ff9b:1::a00:1" # local-use NAT64 -> 10.0.0.1
end
test "private_ip? returns false for local-use NAT64 addresses embedding a public IPv4 (RFC8215)" do
assert_not RestrictedHTTP::PrivateNetworkGuard.private_ip?("64:ff9b:1::808:808") # -> 8.8.8.8
end
test "private_ip? returns false for NAT64 addresses embedding a public IPv4" do
# DNS64 legitimately synthesizes these for public sites on IPv6-only hosts.
assert_not RestrictedHTTP::PrivateNetworkGuard.private_ip?("64:ff9b::808:808") # -> 8.8.8.8
end
test "private_ip? returns true for 6to4 and Teredo transition addresses" do
assert_private_ip "2002:a9fe:a9fe::" # 6to4 embedding 169.254.169.254
assert_private_ip "2001::1" # Teredo
end
test "private_ip? returns true for IPv6 loopback, ULA, and link-local" do
assert_private_ip "::1"
assert_private_ip "fd00:ec2::254" # AWS IMDSv6 (ULA)
assert_private_ip "fe80::1"
end
test "private_ip? returns true for IPv6 multicast, documentation, and benchmarking ranges" do
assert_private_ip "ff02::1"
assert_private_ip "2001:db8::1"
assert_private_ip "2001:2::1" # benchmarking (RFC5180), matches 198.18.0.0/15
end
test "private_ip? returns false for public IPv6 addresses" do
assert_not RestrictedHTTP::PrivateNetworkGuard.private_ip?("2606:4700:4700::1111")
end
test "private_ip? returns true for invalid addresses" do
assert RestrictedHTTP::PrivateNetworkGuard.private_ip?("not-an-ip")
assert RestrictedHTTP::PrivateNetworkGuard.private_ip?("")
+4 -4
View File
@@ -49,8 +49,8 @@ class Opengraph::FetchTest < ActiveSupport::TestCase
# to a resolved IP, not a hostname to re-resolve.
WebMock.disable_net_connect! allow: [ @url.host ]
Resolv.stubs(:getaddress).with(@url.host).returns("1.2.3.4", "127.0.0.1")
TCPSocket.expects(:open).with(@url.host, 443, nil, nil).never
TCPSocket.expects(:open).with("1.2.3.4", 443, nil, nil).throws(:dns_not_rebound)
TCPSocket.expects(:open).with { |*args, **| args.first == @url.host }.never
TCPSocket.expects(:open).with { |*args, **| args.first == "1.2.3.4" && args[1] == 443 }.throws(:dns_not_rebound)
assert_throws :dns_not_rebound do
@fetch.fetch_document(@url)
@@ -66,8 +66,8 @@ class Opengraph::FetchTest < ActiveSupport::TestCase
# to a resolved IP, not a hostname to re-resolve.
WebMock.disable_net_connect! allow: [ @url.host ]
Resolv.stubs(:getaddress).with(@url.host).returns("1.2.3.4", "127.0.0.1")
TCPSocket.expects(:open).with(@url.host, 443, nil, nil).never
TCPSocket.expects(:open).with("1.2.3.4", 443, nil, nil).throws(:dns_not_rebound)
TCPSocket.expects(:open).with { |*args, **| args.first == @url.host }.never
TCPSocket.expects(:open).with { |*args, **| args.first == "1.2.3.4" && args[1] == 443 }.throws(:dns_not_rebound)
assert_throws :dns_not_rebound do
@fetch.fetch_document(URI.parse("https://www.other.com/"), ip: "1.2.3.4")