{- | Outbound-request and response-bound guards for the proxy's data plane. Écluse builds outbound HTTP requests from two untrusted sources -- __client-supplied package identifiers__ (the request path) and __upstream-supplied artifact locations__ (a packument's @dist.tarball@) -- and then parses whatever an upstream returns. This module is the pure guard layer that keeps those steps from being steered or exhausted by hostile input. It defends three boundaries: * __Where the proxy fetches.__ 'isAllowedUpstreamHost' restricts outbound fetches to the configured upstream hosts, and 'isBlockedTarget' rejects internal address ranges (cloud instance metadata, loopback, RFC1918) that the proxy's network position can otherwise reach. Together they are the SSRF gate: a target must be both on the allowlist /and/ not an internal address. * __How an upstream URL is derived.__ 'upstreamUrlFor' builds an artifact\/metadata URL from a configured base URL and an __already-parsed__ 'PackageName', never from raw client path segments, re-checking each name component with the router's own safety rule so traversal, encoded slashes, or an absolute URL cannot change the target. * __How much an upstream may cost.__ A 'Limits' budget plus 'boundedRead' (abort a streamed body past 'maxBodyBytes') and 'checkVersionCount' \/ 'checkNestingDepth' (reject an oversized or deeply-nested parsed document) bound algorithmic-complexity DoS from a hostile or compromised upstream. Every limit __fails closed__: exceeding one yields 'Left', never a truncated or partial result. The functions are pure and total; the streamed-body guard ('boundedRead') is polymorphic over the producing monad so the streaming data plane can run it in 'IO' while tests drive it purely. They are __primitives__: the fetch and serve layers compose them at the boundary (see @docs\/architecture\/registry-model.md@ → "Registry Abstraction", @docs\/architecture\/web-layer.md@, and @docs\/architecture\/hosting.md@ → "URL rewriting"). Path-component safety is shared with the router's "Ecluse.Core.Server.Route" ('isSafeComponent'); the threat model these guards answer is recorded there too. -} module Ecluse.Core.Security ( module Ecluse.Core.Security.Host, module Ecluse.Core.Security.Url, module Ecluse.Core.Security.Limits, ) where import Ecluse.Core.Security.Host import Ecluse.Core.Security.Limits import Ecluse.Core.Security.Url