From b52972d0de89004e98d18241d238ca028e4eecba Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 1 Jul 2026 10:42:15 +0200 Subject: [PATCH] fix: validate workspace name length (max 50 chars) on create and fork (#9854) Co-authored-by: Claude Opus 4.8 (1M context) --- backend/windmill-api-workspaces/src/workspaces.rs | 10 +++++++--- backend/windmill-common/src/workspaces.rs | 13 +++++++++++++ cli/src/commands/workspace/fork.ts | 9 +++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index 589fed899f..7dcff7a9d2 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -44,9 +44,9 @@ use windmill_common::workspaces::GitRepositorySettings; use windmill_common::workspaces::WorkspaceDeploymentUISettings; use windmill_common::workspaces::{ check_deploy_rules, check_user_against_rule, get_datatable_resource_from_db_unchecked, - validate_dev_workspace_id, validate_fork_workspace_id, DataTable, DataTableCatalogResourceType, - DataTableForkBehavior, ProtectionRuleKind, ProtectionRules, ProtectionRuleset, RuleCheckResult, - WorkspaceGitSyncSettings, DEV_WORKSPACE_LOCK_RULE_NAME, + validate_dev_workspace_id, validate_fork_workspace_id, validate_workspace_name, DataTable, + DataTableCatalogResourceType, DataTableForkBehavior, ProtectionRuleKind, ProtectionRules, + ProtectionRuleset, RuleCheckResult, WorkspaceGitSyncSettings, DEV_WORKSPACE_LOCK_RULE_NAME, }; use windmill_common::workspaces::{Ducklake, DucklakeCatalogResourceType}; use windmill_common::PgDatabase; @@ -3845,6 +3845,8 @@ async fn create_workspace( } } + validate_workspace_name(&nw.name)?; + let mut tx: Transaction<'_, Postgres> = db.begin().await?; check_w_id_conflict(&mut tx, &nw.id).await?; @@ -5021,6 +5023,7 @@ async fn create_workspace_fork_branch( } else { validate_fork_workspace_id(&nw.id)?; } + validate_workspace_name(&nw.name)?; // Fail before creating any git branch so a name conflict doesn't leave a // dangling branch on the synced repos. @@ -5170,6 +5173,7 @@ async fn create_workspace_fork( } else { validate_fork_workspace_id(&nw.id)?; } + validate_workspace_name(&nw.name)?; // Check the id conflict before the CE workspace-count limit so that // re-using a taken (possibly archived) fork id reports the actual // conflict instead of a misleading "maximum number of workspaces" error. diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 73d95a3af9..cd3693cd92 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -189,6 +189,19 @@ pub fn validate_dev_workspace_id(id: &str) -> error::Result<()> { validate_workspace_branch_id(id, false) } +/// The `workspace.name` column is `character varying(50)`, so a name longer than 50 characters +/// triggers a raw `value too long for type character varying(50)` SQL error on insert. Validate +/// up front to return a clear message instead. +pub fn validate_workspace_name(name: &str) -> error::Result<()> { + if name.chars().count() > 50 { + return Err(Error::BadRequest(format!( + "Workspace name is too long ({} chars). Maximum length is 50 characters.", + name.chars().count() + ))); + } + Ok(()) +} + fn validate_workspace_branch_id(id: &str, require_fork_prefix: bool) -> error::Result<()> { if id.is_empty() { return Err(Error::BadRequest( diff --git a/cli/src/commands/workspace/fork.ts b/cli/src/commands/workspace/fork.ts index 5742d9ae68..4248a18dc6 100644 --- a/cli/src/commands/workspace/fork.ts +++ b/cli/src/commands/workspace/fork.ts @@ -186,6 +186,15 @@ async function createWorkspaceFork( // branch creation — a late backend rejection would leave cloned databases // behind. validateForkWorkspaceId(trueWorkspaceId); + // The workspace.name column is character varying(50); reject an over-long name + // up front (matching the name actually sent to the backend below) instead of + // failing late after databases are cloned and the git branch is created. + const effectiveName = opts.createWorkspaceName ?? workspaceName ?? trueWorkspaceId; + if (effectiveName.length > 50) { + throw new Error( + `Fork workspace name is too long (${effectiveName.length} chars; max 50). Choose a shorter name.` + ); + } let alreadyExists = false; try { alreadyExists = await wmill.existsWorkspace({