mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-08 16:02:30 +00:00
* fix(flows): reject corrupt step paths at deploy + atomic cache writes (#9751) A flow step could execute an unrelated (and in the reported case, destructive) script at runtime even though every stored definition looked correct. A forensic dump traced it to two issues: - Deploy accepted absolute/local step paths. `wmill sync push` from a feature- branch checkout under /tmp baked an absolute path (`/tmp/.../ops/scripts/clean_device/...`) into a step's `value.path`. Persisted verbatim, it mis-resolved to an unrelated script at runtime. - The on-disk cache write was neither truncating nor atomic. `FsBackedCache::put` used `write+create`, so a shorter overwrite left stale trailing bytes and concurrent writers could interleave into a torn file — a corrupt cached blob that a worker then scheduled from. Fixes: - Reject non-workspace flow step paths (must be u/, f/, g/ or hub/) in `validate_flow_value` (covers create_flow + update_flow, recursively through loops/branches/AI-agent tools) and early in the CLI `pushFlow`. - Make `FsBackedCache::put` write a unique temp file (truncate + fsync) then atomically rename it over the target, cleaning up on error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(flows): validate failure/preprocessor module paths + sub-flow paths in CLI Address PR review (cubic + claude): - Backend `validate_flow_value` is the authoritative guard but only walked `modules`; extend it to also validate `failure_module` and `preprocessor_module` (which can themselves be sub-flows/loops/branches), so an absolute path there can't be persisted. - CLI preflight only collected `type: "script"` paths; now collects sub-flow (`type: "flow"`) step paths too (recursively, incl. failure/preprocessor), so the comment's claim matches the behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): include AI-agent tool step paths in flow path preflight Address Codex review: collectStepPaths skipped aiagent tools, so a bad path in a tool fell through to the API error instead of the local fail-fast. The backend already validates these (traverse_modules walks AIAgent tools); this aligns the CLI early-error with it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(flows): make failure/preprocessor path test key explicit The test used `slot:` as a json! key. json! does interpolate an ident key to its variable's value (json!({slot:1}) with slot="failure_module" => {"failure_module":1}), so the test was correct and exercised the validation — but the behavior is subtle, so build the key explicitly via serde_json::Map to remove ambiguity (review nit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cache): use a UUID temp name for atomic put (shared-volume safe) Address Codex (P1): pid+counter temp names collide across container PID namespaces on a shared cache volume (same pid, PUT_SEQ resets to 0 per process), so two workers could truncate/clobber the same temp file before rename. Use a random UUID suffix (matching worker.rs's atomic-write helpers) — globally unique, so the cross-process temp-file hazard is closed. Also trims the comment to the AGENTS.md <=4-line limit (Pi nit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
16022447c7
commit
3cda447621
@@ -135,6 +135,31 @@ function warnAboutLocalPathScriptDivergence(
|
||||
|
||||
const alreadySynced: string[] = [];
|
||||
|
||||
// Collect every script/sub-flow step path in a flow value — recursively through loops,
|
||||
// branches, and the failure/preprocessor modules — for workspace-path validation. Unlike
|
||||
// `collectPathScriptPaths` this also includes `type: "flow"` sub-flow steps.
|
||||
function collectStepPaths(flowValue: any): string[] {
|
||||
const paths: string[] = [];
|
||||
const walk = (modules: any[] | undefined) => {
|
||||
for (const m of modules ?? []) {
|
||||
const v = m?.value;
|
||||
if (!v) continue;
|
||||
if ((v.type === "script" || v.type === "flow") && typeof v.path === "string") {
|
||||
paths.push(v.path);
|
||||
}
|
||||
walk(v.modules);
|
||||
walk(v.default);
|
||||
for (const b of v.branches ?? []) walk(b?.modules);
|
||||
// AI-agent tools are step-like and can carry script paths too.
|
||||
walk(v.tools);
|
||||
}
|
||||
};
|
||||
walk(flowValue?.modules);
|
||||
if (flowValue?.failure_module) walk([flowValue.failure_module]);
|
||||
if (flowValue?.preprocessor_module) walk([flowValue.preprocessor_module]);
|
||||
return paths;
|
||||
}
|
||||
|
||||
export async function pushFlow(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
@@ -190,6 +215,21 @@ export async function pushFlow(
|
||||
);
|
||||
}
|
||||
|
||||
// Reject script/sub-flow steps whose path is not a workspace path (u/, f/, g/ or hub/).
|
||||
// A flow.yaml generated from a feature-branch checkout can carry absolute local paths
|
||||
// (e.g. /tmp/.../ops/scripts/...); pushed, they silently mis-resolve at runtime (#9751).
|
||||
// The backend re-validates the same rule for every step type, so this is a fail-fast.
|
||||
const badStepPaths = collectStepPaths(localFlow.value).filter(
|
||||
(p) => p !== "" && !/^(u|f|g|hub)\//.test(p)
|
||||
);
|
||||
if (badStepPaths.length > 0) {
|
||||
throw new Error(
|
||||
`Cannot push flow ${remotePath}: step(s) reference non-workspace path(s): ${badStepPaths.join(", ")}. ` +
|
||||
`Flow step paths must be workspace paths (u/, f/, g/ or hub/), not absolute or local filesystem paths. ` +
|
||||
`This usually means flow.yaml was generated with paths from a checkout directory.`
|
||||
);
|
||||
}
|
||||
|
||||
const hasOnBehalfOf = (localFlow as any).has_on_behalf_of ?? !!localFlow.on_behalf_of_email;
|
||||
delete (localFlow as any).has_on_behalf_of;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user