From 6e2f7d15308001aaf2a0480a7516b92957e19eaf Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 2 Sep 2026 17:03:20 +0000 Subject: [PATCH] fix: guests are gated on the Enterprise plan server-side; pin ee-repo-ref --- backend/ee-repo-ref.txt | 2 +- .../windmill-api-workspaces/src/workspaces.rs | 5 ++++ backend/windmill-common/src/workspaces.rs | 24 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 06d07b2240..402480a6c6 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -9623e36c58c51e085739636efd44dd5cecfd24af \ No newline at end of file +f7853642aba08173dba3fc38c5c8a4036efa9bde \ No newline at end of file diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index 7f39203eec..d5d14f69ea 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -4621,6 +4621,11 @@ async fn edit_guest_access( Json(EditGuestAccess { guest_access_enabled }): Json, ) -> Result { require_admin(authed.is_admin, &authed.username)?; + if guest_access_enabled && !windmill_common::workspaces::guest_access_licensed().await { + return Err(Error::BadRequest( + "Guest access requires an Enterprise license".to_string(), + )); + } let mut tx = db.begin().await?; sqlx::query!( diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 5dd59838ab..160a3aeb7a 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -775,6 +775,24 @@ pub struct BillableSeats { pub seats: i64, } +/// Guests are an Enterprise-plan feature, so a Pro key refuses them at every gate — +/// the workspace switch, the mint, the door — not only in the UI that hides them. A +/// build without `enterprise` has no plan to consult and cannot mint a guest session +/// at all (the mint lives in EE code), so nothing is gated there. +pub async fn guest_access_licensed() -> bool { + #[cfg(feature = "enterprise")] + { + matches!( + crate::ee_oss::get_license_plan().await, + crate::ee_oss::LicensePlan::Enterprise + ) + } + #[cfg(not(feature = "enterprise"))] + { + true + } +} + /// Whether `w_id` admits guest sessions — someone the identity provider authenticated /// who is a member of no workspace, and who therefore takes no seat. /// @@ -784,6 +802,9 @@ pub struct BillableSeats { /// push `guest` past every deploy-time gate; the per-request read is what makes /// turning the switch off take effect on sessions already issued. pub async fn is_guest_access_enabled(db: &crate::DB, w_id: &str) -> Result { + if !guest_access_licensed().await { + return Ok(false); + } Ok(sqlx::query_scalar!( "SELECT guest_access_enabled FROM workspace_settings WHERE workspace_id = $1", w_id @@ -803,6 +824,9 @@ pub async fn guest_app_admits<'c, E: sqlx::Executor<'c, Database = sqlx::Postgre w_id: &str, app_path: &str, ) -> Result { + if !guest_access_licensed().await { + return Ok(false); + } let admits: Option = sqlx::query_scalar( "SELECT COALESCE(ws.guest_access_enabled AND app.policy->>'execution_mode' = 'guest', false) FROM app JOIN workspace_settings ws ON ws.workspace_id = app.workspace_id