mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-28 09:32:37 +09:00
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.
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
require "restricted_http/private_network_guard"
|
||||
|
||||
class Push::Subscription < ApplicationRecord
|
||||
# Web push endpoints only ever point at a browser vendor's push service. An
|
||||
# allowlist keeps a user-supplied endpoint from turning delivery into an SSRF
|
||||
# sink, and pinning the resolved public IP on every delivery closes the
|
||||
# DNS-rebinding gap the way Opengraph::Fetch does for unfurls.
|
||||
# DNS-rebinding gap. The private-network check itself is the shared
|
||||
# RestrictedHTTP::PrivateNetworkGuard (surfguard) -- the same hostname-in,
|
||||
# address-out guard Opengraph::Fetch pins its unfurls to.
|
||||
PERMITTED_ENDPOINT_HOSTS = %w[
|
||||
jmt17.google.com
|
||||
fcm.googleapis.com
|
||||
@@ -24,14 +28,16 @@ class Push::Subscription < ApplicationRecord
|
||||
# permitted push service or doesn't resolve to a public IP. Enforced here, not
|
||||
# only at save time, so a row that predates validation (or was inserted around
|
||||
# it) still can't drive delivery at a non-allowlisted or private target.
|
||||
# Re-resolved on every call so each delivery pins a freshly looked-up address
|
||||
# rather than trusting the host to still resolve the way it did at sign-up.
|
||||
# Re-resolved on every call, through the shared guard, so each delivery pins a
|
||||
# freshly looked-up public address rather than trusting the host to still
|
||||
# resolve the way it did at sign-up.
|
||||
def resolved_endpoint_ip
|
||||
Surfguard.resolve_public_ips(endpoint_uri.host).first if permitted_endpoint_uri?
|
||||
rescue Surfguard::Unresolvable
|
||||
# A host that resolves to nothing has no usable public IP -- the same outcome
|
||||
# as one whose only addresses are blocked: no endpoint IP to pin, which fails
|
||||
# endpoint validation. Push has no lookup-failed surface to distinguish.
|
||||
RestrictedHTTP::PrivateNetworkGuard.resolve(endpoint_uri.host) if permitted_endpoint_uri?
|
||||
rescue RestrictedHTTP::Violation, Surfguard::Unresolvable
|
||||
# No usable public address: the host resolves only to blocked (private)
|
||||
# addresses (Violation) or to nothing at all (Unresolvable). Either way there
|
||||
# is no endpoint IP to pin, which fails endpoint validation. Push has no
|
||||
# lookup-failed surface to distinguish the two.
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user