feat: exempt static asset routes from the origin allowlist

This commit is contained in:
hugocasa
2026-08-31 16:03:54 +02:00
parent 74ca65fc23
commit 209d42f164
5 changed files with 38 additions and 16 deletions
+3 -3
View File
@@ -30304,7 +30304,7 @@ components:
items:
type: string
maxLength: 256
description: "Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset."
description: "Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset. Ignored on a route serving static assets: those hand out public files, so restricting which browsers may read them protects nothing while breaking cross-origin webfonts and fetches."
error_handler_path:
type: string
description: Path to a script to run when the triggered job fails. A bare
@@ -30403,7 +30403,7 @@ components:
items:
type: string
maxLength: 256
description: "Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset."
description: "Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset. Ignored on a route serving static assets: those hand out public files, so restricting which browsers may read them protects nothing while breaking cross-origin webfonts and fetches."
error_handler_path:
type: string
description: Path to a script to run when the triggered job fails. A bare
@@ -30509,7 +30509,7 @@ components:
items:
type: string
maxLength: 256
description: "Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset."
description: "Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset. Ignored on a route serving static assets: those hand out public files, so restricting which browsers may read them protects nothing while breaking cross-origin webfonts and fetches."
error_handler_path:
type: string
description: Path to a script to run when the triggered job fails. A bare
@@ -102,6 +102,12 @@ enum CorsDecision {
Unavailable,
}
/// A route serving static assets is never subject to an allowlist, its own
/// included. It hands out public files that any non-browser client can already
/// fetch, so restricting which browsers may read them protects nothing, while
/// breaking the cross-origin uses that do consult CORS: a webfont, a
/// `crossorigin` asset, a `fetch`.
///
/// The CORS verdict for a request, published by whoever resolved its trigger.
///
/// The middleware stamps headers after the handler returns, but only the
@@ -117,16 +123,20 @@ impl ResolvedCorsPolicy {
/// Record what the trigger being served allows. Called once, where the
/// route is resolved, so the answer cannot drift from the response.
fn publish(&self, trigger: &TriggerRoute, method: Option<HttpMethod>, headers: &HeaderMap) {
let instance_default = HTTP_ROUTE_DEFAULT_ALLOWED_ORIGINS.load();
let decision = match effective_allowed_origins(
trigger.allowed_origins.as_deref(),
instance_default.as_slice(),
) {
None => CorsDecision::Unrestricted,
Some(allowed_origins) => CorsDecision::Restricted {
route_method: method,
allow_origin: match_origin(allowed_origins, headers.get(http::header::ORIGIN)),
},
let decision = if trigger.static_asset_config.is_some() {
CorsDecision::Unrestricted
} else {
let instance_default = HTTP_ROUTE_DEFAULT_ALLOWED_ORIGINS.load();
match effective_allowed_origins(
trigger.allowed_origins.as_deref(),
instance_default.as_slice(),
) {
None => CorsDecision::Unrestricted,
Some(allowed_origins) => CorsDecision::Restricted {
route_method: method,
allow_origin: match_origin(allowed_origins, headers.get(http::header::ORIGIN)),
},
}
};
let _ = self.0.set(decision);
}
@@ -170,6 +180,12 @@ async fn resolve_cors_decision(
};
let route = router.at(requested_path).ok();
if route
.as_ref()
.is_some_and(|trigger| trigger.value.static_asset_config.is_some())
{
return CorsDecision::Unrestricted;
}
let route_allowed_origins = route
.as_ref()
.and_then(|trigger| trigger.value.allowed_origins.as_deref());
+4 -1
View File
@@ -8395,7 +8395,10 @@ properties:
the runnable returns via wm_headers. Use [''*''] to opt out of any restriction,
including the http_route_default_allowed_origins instance setting. An empty
list is not a configuration and resolves exactly as null does. When null, the
instance setting applies, or Access-Control-Allow-Origin: * if it is unset.'
instance setting applies, or Access-Control-Allow-Origin: * if it is unset.
Ignored on a route serving static assets: those hand out public files, so restricting
which browsers may read them protects nothing while breaking cross-origin webfonts
and fetches.'
error_handler_path:
type: string
description: Path to a script to run when the triggered job fails. A bare path,
@@ -69,7 +69,7 @@ export const httpTriggerRequestSchema = z.object({
"wrap_body": z.boolean().describe("If true, wraps the request body in a 'body' parameter").optional(),
"mode": z.enum(["enabled", "disabled", "suspended"]).describe("job trigger mode").optional(),
"raw_string": z.boolean().describe("If true, passes the request body as a raw string instead of parsing as JSON").optional(),
"allowed_origins": z.array(z.string()).describe("Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset.").nullable().optional(),
"allowed_origins": z.array(z.string()).describe("Origins allowed to call this route cross-origin, matched against the request's Origin header (ignoring case) and echoed back on a match. When set, the list governs both the preflight and the response, overriding any Access-Control-Allow-Origin the runnable returns via wm_headers. Use ['*'] to opt out of any restriction, including the http_route_default_allowed_origins instance setting. An empty list is not a configuration and resolves exactly as null does. When null, the instance setting applies, or Access-Control-Allow-Origin: * if it is unset. Ignored on a route serving static assets: those hand out public files, so restricting which browsers may read them protects nothing while breaking cross-origin webfonts and fetches.").nullable().optional(),
"error_handler_path": z.string().describe("Path to a script to run when the triggered job fails. A bare path, without the script/ or flow/ prefix a schedule error handler takes; it cannot be a flow.").optional(),
"error_handler_args": z.record(z.string(), z.any()).describe("Arguments to pass to the error handler").optional(),
"retry": z.object({
@@ -105,7 +105,10 @@ properties:
the runnable returns via wm_headers. Use [''*''] to opt out of any restriction,
including the http_route_default_allowed_origins instance setting. An empty
list is not a configuration and resolves exactly as null does. When null, the
instance setting applies, or Access-Control-Allow-Origin: * if it is unset.'
instance setting applies, or Access-Control-Allow-Origin: * if it is unset.
Ignored on a route serving static assets: those hand out public files, so restricting
which browsers may read them protects nothing while breaking cross-origin webfonts
and fetches.'
error_handler_path:
type: string
description: Path to a script to run when the triggered job fails. A bare path,