# Class BSV::Network::EndpointPolicy <a id="class-BSV-Network-EndpointPolicy"></a>

**Inherits:** `Object`

SSRF gate for caller-supplied peer endpoints (#385 Task 5, #390).

Pure validator — no I/O state, no mutation after construction. The
<code>#validate!</code> entry point does perform a DNS resolution as part of
validation (the only way to defend against a DNS-resolves-to-private attack),
but the policy itself holds only configuration. Frozen at construction so
callers cannot mutate the rule set out from under an in-flight delivery.

The caller-supplied peer endpoint is the wallet's external attack surface: a
malicious or compromised endpoint string could redirect an outbound BEEF
through a SSRF chain to the cloud provider's metadata endpoint
(169.254.169.254 on AWS / GCP / Azure), loopback services on the wallet host,
or RFC1918 / unique-local IPv6 destinations on the operator's LAN. Reject all
of these by default; the e2e harness opts in via
+BSV_WALLET_ALLOW_PRIVATE_ENDPOINTS=1+ to talk to fixtures running on
127.0.0.1.

Resolves DNS once at validate time and returns the resolved IP for the caller
to dial — set the <code>Host:</code> header to the original hostname so TLS
SNI + virtual-host routing still work. This closes the DNS TOCTOU window where
<code>Net::HTTP</code> would re-resolve and potentially land on a different
(private) IP than the one the policy approved.

## Constants
### `PRIVATE_RANGES` <a id="constant-PRIVATE_RANGES"></a> <a id="PRIVATE_RANGES-constant"></a>
Private / link-local / unique-local ranges that an outbound BEEF delivery has
no business reaching:

*   <code>0.0.0.0/8</code> — RFC 1122 "this network". Linux + macOS route
    <code>0.0.0.0</code> to the loopback interface (the kernel's wildcard
    bind-address shorthand), so it must be rejected alongside
    <code>127.0.0.0/8</code>.
*   <code>127.0.0.0/8</code> — IPv4 loopback (the wallet host itself).
*   <code>10.0.0.0/8</code>, <code>172.16.0.0/12</code>,
    <code>192.168.0.0/16</code> — RFC1918 private networks (operator LAN,
    container subnets, k8s pods).
*   <code>100.64.0.0/10</code> — RFC 6598 carrier-grade NAT shared space. Not
    RFC1918, but indistinguishable from "private" for our purposes — no
    business reaching it from an outbound BEEF.
*   <code>169.254.0.0/16</code> — IPv4 link-local. INCLUDES the cloud metadata
    service at 169.254.169.254 — the canonical SSRF target for credential
    exfiltration on AWS / GCP / Azure.
*   <code>224.0.0.0/4</code> — IPv4 multicast (RFC 5771).
*   <code>255.255.255.255/32</code> — limited broadcast (RFC 919).
*   <code>::1/128</code> — IPv6 loopback.
*   <code>fc00::/7</code> — IPv6 unique-local (the IPv6 equivalent of
    RFC1918).
*   <code>::ffff:0:0/96</code> — IPv4-mapped IPv6 (RFC 4291 § 2.5.5.2).
    Defence-in-depth: <code>#validate_ip!</code> also unwraps any IPv4-mapped
    address to its native IPv4 form BEFORE the membership check, so
    <code>::ffff:127.0.0.1</code> falls into the <code>127.0.0.0/8</code>
    rule. Listing the <code>/96</code> here closes the gap for any future code
    path that skips the unwrap.

## Attributes
### `allow_private` [R] <a id="attribute-i-allow_private"></a> <a id="allow_private-instance_method"></a>
Returns the value of attribute allow_private.

### `max_body_bytes` [R] <a id="attribute-i-max_body_bytes"></a> <a id="max_body_bytes-instance_method"></a>
Returns the value of attribute max_body_bytes.

### `require_https` [R] <a id="attribute-i-require_https"></a> <a id="require_https-instance_method"></a>
Returns the value of attribute require_https.

## Public Instance Methods
### `allow_body?(byte_size)` <a id="method-i-allow_body-3F"></a> <a id="allow_body?-instance_method"></a>
- **@param** `byte_size` [Integer]
- **@return** [Boolean] true iff a body of that size is within the cap

### `initialize(allow_private: = ENV['BSV_WALLET_ALLOW_PRIVATE_ENDPOINTS'] == '1', require_https: = true, max_body_bytes: = 32 * 1024 * 1024)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
- **@param** `allow_private` [Boolean] dev/test escape hatch. Defaults to
reading +ENV['BSV_WALLET_ALLOW_PRIVATE_ENDPOINTS']+ as truthy iff
set to '1'. Used by the e2e harness to talk to local fixture
wallets bound on 127.0.0.1.
- **@param** `require_https` [Boolean] reject plain http://. Defaults
true — plain HTTP gives an on-path attacker the BEEF, the
recipient's identity inference (BRC-29
+sender_identity_key+), and the ability to forge a 200 ACK.
- **@param** `max_body_bytes` [Integer] cap on POST body size. Default
32MiB. A trimmed BEEF for a routine payment is a few kB; this
bound is a defence against either a runaway hydration walk or
a malicious request to ship oversize bundles over peer links.
- **@return** [EndpointPolicy] a new instance of EndpointPolicy

### `validate!(endpoint)` <a id="method-i-validate-21"></a> <a id="validate!-instance_method"></a>
Validate `endpoint` and return the resolved coordinates for dialling. Each
rejection raises `Violation` with a message that names the rule fired — kept
short so a <code>PeerDelivery::Result</code> `error_message` stays
log-friendly.
- **@param** `endpoint` [String] absolute URI string
- **@raise** [Violation] for any policy violation
- **@return** [Hash] +{ uri: URI, ip: String }+ — +uri+ carries the
parsed endpoint (use +.host+ for the +Host+ header, +.port+
for the dial port); +ip+ is the resolved address to dial.
