diff --git a/backend/windmill-api/src/drafts.rs b/backend/windmill-api/src/drafts.rs index 40e2a3cb65..22d9c2ba74 100644 --- a/backend/windmill-api/src/drafts.rs +++ b/backend/windmill-api/src/drafts.rs @@ -657,8 +657,10 @@ async fn move_draft( }; // Validate before authorizing: `require_can_write_path` is not a format check // (an admin returns immediately, and a user returns early inside their own - // namespace), so without this a malformed destination is stored as-is, and an - // over-long or NUL-bearing one reaches Postgres as a raw server error. + // namespace), so without this a malformed path is stored as-is, and an over-long + // or NUL-bearing one reaches Postgres as a raw server error. The source is a URL + // segment and arrives decoded, so it needs the same check as the destination. + check_proper_path(path)?; check_proper_path(new_path)?; // A summary-only edit is a legitimate use of this endpoint: the drawer edits // both fields, and for a draft-only script the path it posts back is the row diff --git a/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte index b56606776e..e81dfb9e90 100644 --- a/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte @@ -497,13 +497,10 @@ onOpenOthersDrafts={() => (othersModalOpen = true)} onDeploy={({ version, head, headBy, headAt }) => { // The editor stays open across a deploy and pins what it wrote onto the value - // itself; the prompt's own pair is what the loader knows, and this deploy - // consumed the draft it described, so the route holds no base until it loads - // again (the deploy guard covers that window). - // Keep the base the editor just pinned when this deploy is the head: the - // prompt has no draft to describe either way, and the drawer needs it to - // keep offering Take latest. A raced deploy leaves it unknown, since the - // pair would then differ and open the prompt on a draft that is gone. + // itself. Keep that as the prompt's base while this deploy is the head, so + // the pair still describes the draft the next edit starts; a raced deploy + // leaves it unknown, or the pair would differ and open the prompt on the + // draft this deploy consumed. draftBaseVersion = version != null && version === head ? String(version) : undefined draftSavedAt = undefined if (head != null) { diff --git a/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte index f1f39306f0..87197e3dff 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte @@ -578,10 +578,11 @@ onDeploy={(e) => { // "Deploy & Stay here" / lib: stay on the editor (just confirm). if (e.stay) { - // The editor is now on the version it just wrote, and so is the draft the - // builder re-pinned. Without this the prompt and the drawer keep comparing - // the pre-deploy pair, so Diff offers Take latest against a head the next - // draft is already on. + // `e.hash` is the hash the create call returned, so it names this deploy's own + // version with nothing to attribute (unlike an app, which has to read its + // version back). The builder re-pinned the draft to it, so the pair moves + // here too: otherwise Diff offers Take latest against a head the draft is + // already on. draftBaseHash = e.hash deployedHeadHash = e.hash sendUserToast('Deployed')