Skip to main content

submilli_engine/stdlib/http/
transport_policy.rs

1//! Request-scoped blueprint restrictions, independent of the blueprint parser.
2
3use url::Url;
4
5/// Custom HTTP transports must check the initial destination and every redirect
6/// with this policy before sending. A request without a policy keeps the
7/// embedder's existing transport behavior.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct HttpTransportPolicy {
10    pub allow_insecure_http: bool,
11    /// Ordered, first-match-wins host rules, including their cleartext opt-ins.
12    pub auth_proxy_hosts: Vec<(String, bool)>,
13    /// Set only after auth-proxy injection. Never forward injected credentials
14    /// to a different scheme, host, or effective port.
15    pub same_origin_redirects: bool,
16}
17
18impl HttpTransportPolicy {
19    pub fn check_destination(&self, url: &Url) -> Result<(), TransportPolicyError> {
20        match url.scheme() {
21            "https" => return Ok(()),
22            "http" => {}
23            _ => return Err(TransportPolicyError::UnsupportedScheme),
24        }
25        if !self.allow_insecure_http {
26            return Err(TransportPolicyError::BlueprintRequiresHttps);
27        }
28        if let Some((host, false)) = self
29            .auth_proxy_hosts
30            .iter()
31            .find(|(host, _)| Some(host.as_str()) == url.host_str())
32        {
33            return Err(TransportPolicyError::RuleRequiresHttps(host.clone()));
34        }
35        Ok(())
36    }
37
38    pub fn check_redirect(&self, initial: &Url, next: &Url) -> Result<(), TransportPolicyError> {
39        self.check_destination(next)?;
40        if self.same_origin_redirects && initial.origin() != next.origin() {
41            return Err(TransportPolicyError::CrossOriginRedirect);
42        }
43        Ok(())
44    }
45}
46
47/// Contains no request URL, headers, or query values, so credentials cannot
48/// leak through a policy diagnostic.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum TransportPolicyError {
51    BlueprintRequiresHttps,
52    RuleRequiresHttps(String),
53    CrossOriginRedirect,
54    UnsupportedScheme,
55}
56
57impl std::fmt::Display for TransportPolicyError {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            Self::BlueprintRequiresHttps => f.write_str(
61                "HTTPS required: blueprint allow_insecure_http is false; use HTTPS or explicitly enable allow_insecure_http in the blueprint",
62            ),
63            Self::RuleRequiresHttps(host) => write!(
64                f,
65                "HTTPS required: auth_proxy rule for host '{host}' requires its own allow_insecure_http: true",
66            ),
67            Self::CrossOriginRedirect => f.write_str(
68                "auth_proxy credentials require a same-origin redirect (same scheme, host, and effective port)",
69            ),
70            Self::UnsupportedScheme => f.write_str("HTTP requests require an http or https URL"),
71        }
72    }
73}
74
75impl std::error::Error for TransportPolicyError {}