| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Core.Security.Authority
Description
Textual extraction of the host[:port] authority an outbound request dials.
These are comparison extractors, not an RFC 3986 parser: a value with no recognisable
authority yields the empty string or Nothing, which every guard treats as not-allowed. The
SSRF gates in Ecluse.Core.Security.Host consume the extracted HostPort, so the parsing
here carries no policy of its own.
Synopsis
- data HostPort = HostPort {}
- hostAddress :: Text -> Text
- hostPortAddress :: Text -> Maybe HostPort
- hostPortAddressWithDefault :: Word16 -> Text -> Maybe HostPort
- splitHostPort :: Text -> Maybe (Text, Text)
- refuseCredentialMaterial :: Text -> Text -> Either Text ()
- authorityLabel :: Text -> Text
- dialledAuthorityLabel :: Text -> Text
- credentialFreeUrl :: Text -> Text
The dialled authority
The authority an outbound fetch dials: a bare host with its effective port. The gate authorises the pair, so an allowlisted host at an attacker-chosen port is not authorised.
Constructors
| HostPort | |
Authority extraction
hostAddress :: Text -> Text Source #
The bare host of a URI or host[:port] authority, lower-cased and unbracketed. A value with no
recognisable host yields the empty string, which the guards treat as not-allowed.
hostPortAddress :: Text -> Maybe HostPort Source #
The host and the effective port a URI or host[:port] authority dials, or Nothing when it
holds none. A portless URL dials 443, and an out-of-grammar or written-but-empty port is refused.
>>>hostPortAddress "https://[2606:4700::1111]:8443/thing"Just (HostPort {hpHost = "2606:4700::1111", hpPort = 8443})
hostPortAddressWithDefault :: Word16 -> Text -> Maybe HostPort Source #
hostPortAddress with the caller's own port for a URL that writes none, for a scheme whose
portless default is not the gate's 443. The authority split and the port grammar do not change.
splitHostPort :: Text -> Maybe (Text, Text) Source #
Split a host[:port] authority into its bare host and the raw ":port" remainder, empty when
no port is present. The split is bracket-aware, and an unclosed opening bracket yields Nothing.
Configured-URL refusal
refuseCredentialMaterial :: Text -> Text -> Either Text () Source #
Refuse an operator-configured URL that carries credential material: userinfo, a query string, or a fragment. Run it before any check that quotes the value, and the reason it returns never does.
>>>refuseCredentialMaterial "server.publicUrl" "https://deploy:hunter2@ecluse.example.test"Left "server.publicUrl must not carry userinfo (a credential belongs in its own configuration key)"
>>>refuseCredentialMaterial "registry.url" "https://registry.npmjs.org/@acme/thing"Right ()
Log-safe rendering
authorityLabel :: Text -> Text Source #
The log-safe label for a URL: its validated host and effective port, or <unresolved>. An
attacker-influenced or credential-bearing URL must never reach a log line or a span as written.
>>>authorityLabel "https://deploy:hunter2@registry.npmjs.org/thing/-/thing-1.0.0.tgz?sig=abc""registry.npmjs.org:443"
dialledAuthorityLabel :: Text -> Text Source #
authorityLabel for a URL a client dials as written, so a portless http:// URL names port 80.
>>>dialledAuthorityLabel "HTTP://mirror.example.test/epss.csv.gz""mirror.example.test:80"
credentialFreeUrl :: Text -> Text Source #
A fetched URL with every credential carrier removed: userinfo, query, and fragment, the last two whole, because either can hold a signed-URL credential.
>>>credentialFreeUrl "https://deploy:hunter2@osv.example.test/npm/all.zip?sig=abc#frag""https://osv.example.test/npm/all.zip"