Skip to content

fix(workspaces): validate fork id as a git branch name component#9049

Merged
rubenfiszel merged 1 commit into
mainfrom
create-branch-api-issue
May 5, 2026
Merged

fix(workspaces): validate fork id as a git branch name component#9049
rubenfiszel merged 1 commit into
mainfrom
create-branch-api-issue

Conversation

@rubenfiszel
Copy link
Copy Markdown
Contributor

@rubenfiszel rubenfiszel commented May 5, 2026

Summary

POST /w/{workspace}/workspaces/create_workspace_fork_branch was returning 200 even when the id contained characters that make the resulting git branch name invalid (e.g. wm-fork-test:allowwm-fork/master/test:allow). The endpoint queues a deferred git-sync worker job, so the actual branch creation failure never made it back to the API caller. Reported by Matthew Parry.

This PR validates the fork id synchronously at the API layer against git check-ref-format rules, rejecting unsafe inputs with HTTP 400 before a job is queued. The same validator is also applied to POST /w/{workspace}/workspaces/create_fork, where the id likewise becomes a git branch suffix.

Changes

  • Add validate_fork_workspace_id() in windmill-common/src/workspaces.rs. Enforces the existing wm-fork- prefix check plus git ref rules: rejects :, , ~, ^, ?, *, [, \, control chars, .., @{, //, trailing ., .lock suffix, and components starting with ..
  • Call the validator from both create_workspace_fork_branch and create_workspace_fork in windmill-api-workspaces/src/workspaces.rs. The previous inline starts_with(WM_FORK_PREFIX) block in create_workspace_fork is folded into the helper so both endpoints share the same error contract.
  • Add four unit tests covering the positive case, missing prefix, nine forbidden characters, and seven forbidden sequences.

Test plan

  • cargo test -p windmill-common --lib workspaces::tests — all four new tests pass
  • Manual: POST /api/w/admins/workspaces/create_workspace_fork_branch with {"id":"wm-fork-test-allow","name":"test-allow"} → 200
  • Manual: same endpoint with {"id":"wm-fork-test:allow",...} → 400 with Fork workspace id 'wm-fork-test:allow' is invalid: contains forbidden character ':' (must be a valid git branch name component)
  • Manual: same endpoint with {"id":"not-a-fork",...} → 400 with prefix error
  • Manual: POST /api/w/admins/workspaces/create_fork with the same colon id → 400 (validation also applies to fork creation)

Summary by cubic

Validate fork workspace ids as safe git branch components to prevent invalid refs. The API now returns 400 for bad ids and avoids enqueueing failing git-sync jobs.

  • Bug Fixes
    • Added validate_fork_workspace_id() in windmill-common enforcing WM_FORK_PREFIX and git check-ref-format rules.
    • Applied the validator to create_workspace_fork_branch and create_workspace_fork in windmill-api-workspaces for consistent early rejection.
    • Added unit tests for valid ids, missing prefix, forbidden characters, and unsafe sequences.

Written for commit 0c69202. Summary will update on new commits.

@rubenfiszel rubenfiszel marked this pull request as ready for review May 5, 2026 21:42
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

Claude finished @rubenfiszel's task in 3m 31s —— View job


Mergeable, but should ideally address nits: validator accepts trailing / and the bare prefix wm-fork-, both of which git would still reject in the worker.

Summary

The fix is in the right place: validating the fork id synchronously in windmill-common::workspaces::validate_fork_workspace_id and calling it from both create_workspace_fork_branch and create_workspace_fork is a clean way to centralize the contract and stop a bad id before the deferred git-sync job is queued. The validator covers the bulk of git check-ref-format (most forbidden chars, control chars, .., @{, //, trailing ., .lock suffix, components starting with .). The unit tests are well-organized and cover positives, missing-prefix, every forbidden character, and every forbidden sequence the helper is meant to enforce.

Findings

P2 — Validator allows trailing /   backend/windmill-common/src/workspaces.rs:213-221

Git's rule 6 (git check-ref-format) forbids a name that ends with /, but the per-component loop accepts an empty component:

id = "wm-fork-foo/"
  -> id.split('/') = ["wm-fork-foo", ""]
  -> "" does not start with '.' and does not end with ".lock" -> Ok

So POST /create_workspace_fork_branch {"id":"wm-fork-foo/", ...} returns 200, the worker job is queued, and the failure shows up only in the worker — exactly the bug this PR is trying to eliminate. Easy fix:

for component in id.split('/') {
    if component.is_empty() {
        return reject("a path component cannot be empty");
    }
    if component.starts_with('.') { ... }
    if component.ends_with(".lock") { ... }
}

Fix this →

P2 — Validator accepts the bare prefix wm-fork-   backend/windmill-common/src/workspaces.rs:172-178

The prefix check is starts_with(WM_FORK_PREFIX), but nothing requires anything after the prefix. id = "wm-fork-" passes the validator, then becomes the literal workspace id and (per the doc comment) gets interpolated into a branch like wm-fork/<original>/ — same trailing-slash problem, plus a workspace with an empty user-chosen suffix. Cheap fix is to require id.len() > WM_FORK_PREFIX.len().

AGENTS.md compliance

No issues. The validator lives in windmill-common::workspaces next to the existing WM_FORK_PREFIX constant, public-surface doc comment explains the contract and why (deferred worker job), and the helper is called by the only two endpoints that interpolate the id into a git branch.

Out-of-scope coverage gap (not introduced by this PR, just noting)

POST /change_workspace_id (workspaces_extra.rs:41) does not validate new_id at all and is not gated on the source workspace being a fork. A user could create a non-fork workspace, then rename it to wm-fork-bad:id — bypassing this PR's check, and downstream fork detection (is_fork = workspace_id.starts_with(WM_FORK_PREFIX) in windmill-queue/src/tags.rs:29) would then see it as a fork. Pre-existing; worth a follow-up but not a blocker for this PR.

Test coverage

  • Backend — Four targeted unit tests in windmill-common's workspaces::tests module cover the positive case, missing prefix, every forbidden character, and every forbidden sequence. Good coverage for the helper; no API-layer integration test asserts the 400 response, but the PR description shows it was verified manually against /create_workspace_fork_branch and /create_fork. If the trailing-slash gap above is fixed, please add a test case for it.
  • Manual verification still worthwhile: hit POST /api/w/admins/workspaces/create_workspace_fork_branch with {"id":"wm-fork-foo/","name":"x"} and confirm it returns 400 (currently returns 200 and fails later in the worker), then re-confirm the existing positive case (wm-fork-test-allow) still returns 200.
    · branch create-branch-api-issue

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying windmill with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0c69202
Status: ✅  Deploy successful!
Preview URL: https://f83f07e1.windmill.pages.dev
Branch Preview URL: https://create-branch-api-issue.windmill.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/windmill-common/src/workspaces.rs">

<violation number="1" location="backend/windmill-common/src/workspaces.rs:199">
P2: Reject ids that end with '/' as git check-ref-format forbids refnames ending with a slash; currently `wm-fork-foo/` passes validation and still fails at git ref creation.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

if id.contains("@{") {
return reject("cannot contain '@{'");
}
if id.contains("//") {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Reject ids that end with '/' as git check-ref-format forbids refnames ending with a slash; currently wm-fork-foo/ passes validation and still fails at git ref creation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/windmill-common/src/workspaces.rs, line 199:

<comment>Reject ids that end with '/' as git check-ref-format forbids refnames ending with a slash; currently `wm-fork-foo/` passes validation and still fails at git ref creation.</comment>

<file context>
@@ -163,6 +163,65 @@ pub const LATEST_GIT_SYNC_SCRIPT_PATH: &str = "hub/28217/sync-script-to-git-repo
+    if id.contains("@{") {
+        return reject("cannot contain '@{'");
+    }
+    if id.contains("//") {
+        return reject("cannot contain '//'");
+    }
</file context>
Suggested change
if id.contains("//") {
if id.ends_with('/') {
return reject("cannot end with '/'");
}
if id.contains("//") {
Fix with Cubic

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Codex Review

Should address issues before merging: trailing slash fork ids still pass validation

Findings

[P1] backend/windmill-common/src/workspaces.rs:214validate_fork_workspace_id() accepts ids ending in /, for example wm-fork-test/. The new doc comment says the id is appended into wm-fork/<original_branch>/<id> and “must satisfy git check-ref-format rules”, but git check-ref-format --branch 'wm-fork/master/wm-fork-test/' rejects that branch name. This leaves the same deferred worker failure path for a caller-controlled invalid id that this PR is intended to reject synchronously. The component loop should reject empty components, or at least id.ends_with('/'), and add a regression test.

Test coverage

Backend unit tests were added for the validator and cover several forbidden characters and sequences, but they miss the trailing slash case above. I did not run the Rust test suite; I validated the missed case directly with git check-ref-format --branch.

Manual verification needed before merge: call POST /api/w/admins/workspaces/create_workspace_fork_branch with {"id":"wm-fork-test/","name":"test"} and confirm it returns HTTP 400 without queuing a git-sync job.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Pi Review

Good to merge.


Summary

This PR adds synchronous validation of fork workspace IDs against git check-ref-format rules, fixing a bug where POST .../create_workspace_fork_branch returned 200 for IDs containing characters invalid in git branch names (e.g., :), with the actual failure silently surfacing later in a deferred worker job.

What I verified

  • Validation logic (validate_fork_workspace_id in windmill-common/src/workspaces.rs): Covers the expected git check-ref-format --branch rules — forbidden characters (:, ~, ^, ?, *, [, \, space, control chars, DEL), forbidden sequences (.., @{, //), trailing ./.lock, and per-component .-prefix/.lock-suffix checks. The WM_FORK_PREFIX check from the old inline block is preserved with identical error message.
  • Call sites (windmill-api-workspaces/src/workspaces.rs): Validation is placed after the existing permission/cloud-hosted checks and before the deferred work (handle_fork_branch_creation) or DB transaction — so rejection happens synchronously as intended.
  • Error type: Error::BadRequest correctly maps to HTTP 400 via IntoResponse.
  • Module placement: The function lives alongside WM_FORK_PREFIX and other workspace utilities in windmill-common/src/workspaces.rs — appropriate.
  • No missing callers: The only deprecated endpoint (deprecated_create_workspace_fork) doesn't use the fork ID at all (it returns a static error).
  • Tests: Four unit tests cover the positive case, missing prefix, nine forbidden characters, and seven forbidden sequences — all passing per the PR description.

Test coverage

Backend — four new Rust unit tests in workspaces::tests cover the validation logic. The PR description also reports manual testing of both endpoints with valid and invalid inputs, confirming HTTP 200 and 400 responses respectively. No integration tests exist for this layer and none are needed here — the change is a pure validation guard with deterministic behavior.

Manual verification still needed: Run the git-sync worker after creating a fork with an edge-case-but-valid ID (e.g., wm-fork-my.fork_v2) to confirm the resulting branch name wm-fork/<original>/wm-fork-my.fork_v2 is accepted by git and the fork workflow completes.

@rubenfiszel rubenfiszel merged commit f4553e8 into main May 5, 2026
39 checks passed
@rubenfiszel rubenfiszel deleted the create-branch-api-issue branch May 5, 2026 21:49
@github-actions github-actions Bot locked and limited conversation to collaborators May 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant