mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 16:15:51 +00:00
fix(hub-publish): validate project slug before interpolating into Hub path
slug/project_slug are caller-controlled and were interpolated straight into the Hub request path; a crafted value (e.g. ../../admin) could reach an unintended Hub endpoint after URL normalization. Validate against the frontend charset (lowercase alphanumerics + hyphens, 3-50 chars) in the four handlers that put the slug in the path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
271fd2d307
commit
31c2dffc8e
@@ -33,6 +33,21 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/projects/{slug}/export", get(get_project_export))
|
||||
}
|
||||
|
||||
fn validate_project_slug(slug: &str) -> Result<(), Error> {
|
||||
let ok = slug.len() >= 3
|
||||
&& slug.len() <= 50
|
||||
&& !slug.starts_with('-')
|
||||
&& !slug.ends_with('-')
|
||||
&& slug
|
||||
.bytes()
|
||||
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-');
|
||||
if ok {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::BadRequest(format!("invalid project slug: {slug}")))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PublishDraftBody {
|
||||
slug: String,
|
||||
@@ -231,6 +246,7 @@ async fn publish_resource_type(
|
||||
Json(body): Json<PublishResourceTypeBody>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
validate_project_slug(&body.project_slug)?;
|
||||
forward_to_hub(
|
||||
&format!("/projects/{}/resource_types", body.project_slug),
|
||||
&workspace,
|
||||
@@ -259,6 +275,7 @@ async fn publish_resources(
|
||||
Json(body): Json<PublishResourcesBody>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
validate_project_slug(&body.project_slug)?;
|
||||
forward_to_hub(
|
||||
&format!("/projects/{}/resources", body.project_slug),
|
||||
&workspace,
|
||||
@@ -294,6 +311,7 @@ async fn publish_triggers(
|
||||
Json(body): Json<PublishTriggersBody>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
validate_project_slug(&body.project_slug)?;
|
||||
forward_to_hub(
|
||||
&format!("/projects/{}/triggers", body.project_slug),
|
||||
&workspace,
|
||||
@@ -307,6 +325,7 @@ async fn get_project_export(
|
||||
Path((workspace, slug)): Path<(String, String)>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
validate_project_slug(&slug)?;
|
||||
get_from_hub(&format!("/projects/{}/export", slug), &workspace).await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user