///|
fn moongate_security_handler(
next : @httpx.Handler,
control : ControlPlaneAuth,
configured_host : String,
port : Int,
) -> @httpx.Handler {
(r, w) => {
if !moongate_request_authority_allowed(r.headers, configured_host, port) {
moongate_write_security_error(w)
return
}
let path = r.path.to_owned().split("?").head().unwrap_or("").to_owned()
if path == "/control/bootstrap" {
if r.method_ != Get ||
!moongate_control_bootstrap_allowed(r.headers, configured_host, port) {
moongate_write_security_error(w)
return
}
control.write_bootstrap(w)
return
}
if moongate_route_requires_control(r.method_, path) &&
!control.authorized(r.headers) {
moongate_write_control_auth_error(w)
return
}
if moongate_static_route(r.method_, path) {
w.header().set("Cache-Control", "no-store")
}
next(r, w)
}
}
///|
fn moongate_control_bootstrap_allowed(
headers : Map[String, String],
configured_host : String,
port : Int,
) -> Bool {
match headers.get("origin") {
Some(origin) => {
if !moongate_origin_allowed(origin, configured_host, port) {
return false
}
match headers.get("sec-fetch-site") {
Some(site) => site == "same-origin"
None => true
}
}
None => headers.get("sec-fetch-site") == Some("same-origin")
}
}
///|
fn moongate_route_requires_control(
method_ : @httpx.Method,
path : String,
) -> Bool {
if moongate_inference_route(method_, path) ||
moongate_public_route(method_, path) ||
moongate_static_route(method_, path) {
return false
}
true
}
///|
fn moongate_inference_route(method_ : @httpx.Method, path : String) -> Bool {
match method_ {
Post =>
path == "/v1/messages" ||
path == "/claude-desktop/v1/messages" ||
path == "/v1/chat/completions" ||
path == "/openclaw/v1/chat/completions" ||
path == "/v1/responses" ||
path == "/v1/responses/compact" ||
path.has_prefix("/gemini/v1/") ||
path.has_prefix("/gemini/v1beta/")
Get =>
path == "/v1/models" ||
path == "/openclaw/v1/models" ||
path == "/claude-desktop/v1/models" ||
path.has_prefix("/gemini/v1/") ||
path.has_prefix("/gemini/v1beta/")
_ => false
}
}
///|
fn moongate_public_route(method_ : @httpx.Method, path : String) -> Bool {
method_ == Get &&
(
path == "/health" ||
path == "/status" ||
path == "/stats" ||
path == "/metrics" ||
path == "/suite/manifest" ||
path == "/suite/status" ||
path == "/suite/moonclaw-providers"
)
}
///|
fn moongate_static_route(method_ : @httpx.Method, path : String) -> Bool {
method_ == Get &&
(
path == "/" ||
path == "/ui/rabbita" ||
path == "/participant/register" ||
path == "/favicon.ico" ||
path.has_prefix("/assets/")
)
}
///|
fn moongate_request_authority_allowed(
headers : Map[String, String],
configured_host : String,
port : Int,
) -> Bool {
guard headers.get("host") is Some(host_header) else { return false }
guard moongate_parse_authority(host_header) is Some(authority) else {
return false
}
if !moongate_host_allowed(authority.host, configured_host) ||
!moongate_request_port_allowed(authority.host, authority.port, port) {
return false
}
match headers.get("origin") {
None => true
Some(origin) => moongate_origin_allowed(origin, configured_host, port)
}
}
///|
fn moongate_request_port_allowed(
host : String,
actual : Int?,
expected : Int,
) -> Bool {
moongate_port_allowed(actual, expected) ||
(actual is None && moongate_loopback_host(host))
}
///|
fn moongate_loopback_host(host : String) -> Bool {
let normalized = host.trim().to_lower()
normalized == "localhost" || normalized == "127.0.0.1" || normalized == "::1"
}
///|
fn moongate_parse_authority(authority : String) -> @url.Url? {
let value = authority.trim()
if value.is_empty() {
return None
}
Some(@url.parse("http://\{value}")) catch {
_ => None
}
}
///|
fn moongate_origin_allowed(
origin : String,
configured_host : String,
port : Int,
) -> Bool {
let value = origin.trim().to_owned()
if value.is_empty() || value == "null" {
return false
}
let parsed = @url.parse(value) catch { _ => return false }
moongate_host_allowed(parsed.host, configured_host) &&
moongate_port_allowed(parsed.port, port)
}
///|
fn moongate_host_allowed(host : String, configured_host : String) -> Bool {
let normalized = host.trim().to_lower()
let configured = configured_host.trim().to_lower()
normalized == configured ||
normalized == "localhost" ||
normalized == "127.0.0.1" ||
normalized == "::1"
}
///|
fn moongate_port_allowed(actual : Int?, expected : Int) -> Bool {
match actual {
Some(port) => port == expected
None => expected == 80 || expected == 443
}
}
///|
async fn moongate_write_security_error(w : @httpx.ResponseWriter) -> Unit {
moongate_write_json_error(
w,
@status.Forbidden,
"moongate_untrusted_request_authority",
"Host or Origin is not trusted by MoonGate",
)
}
///|
async fn moongate_write_control_auth_error(w : @httpx.ResponseWriter) -> Unit {
moongate_write_json_error(
w,
@status.Unauthorized,
"moongate_control_auth_required",
"A valid MoonGate control token is required",
)
}
///|
async fn moongate_write_json_error(
w : @httpx.ResponseWriter,
status_code : Int,
code : String,
message : String,
) -> Unit {
w.header().set("Content-Type", "application/json")
w.header().set("Cache-Control", "no-store")
w.write_header(status_code)
w.write(
{
"error": { "code": Json::string(code), "message": Json::string(message) }.to_json(),
}
.to_json()
.stringify(),
)
w.flush()
}