Skip to content

Writ coordinator v1.0.2

Choose a tag to compare

@benjamingarciaMTL benjamingarciaMTL released this 11 Aug 18:36
· 3 commits to main since this release
v1.0.2
2e088e6

Attaching a file while recording shipped in v1.0.1, but a file picked during a recording did not survive to replay. These four defects were the gap between the feature existing and the feature working.

No schema or configuration changes — upgrade by pulling and rebuilding.

git pull && docker compose up -d --build

Fixed

A recorded upload failed at replay with "no file is bound" — even though you had picked a file. A saved step carries its binding in config when the editor wrote it and in options when the recorder did. The run's file map read only config, so every upload bound while recording was simply absent from it. Both shapes are canonical for a saved workflow — the UI already tolerates the step.x / config.x / options.x spread — so both are read now, with config winning as the explicit later edit.

An upload step with a file already pinned to it was invisible in the run dialog. A pinned file was treated as "not a run-time slot" and excluded entirely, so a workflow that ran perfectly well offered no way to swap the file for a single run without editing the workflow itself. Every upload step is now listed, and a pinned file comes through as that slot's default — the run works untouched, and the default is there to override.

Answering the recorder's file chooser could cancel itself. FilePicker calls onSelect and then onClose, while answering is asynchronous — it mints a signed URL first. Both handlers therefore ran against the same React render, and the close handler sent skip before the answer had gone out, so the file you chose was discarded and the page's dialog was dismissed empty. The prompt is now claimed through a ref that updates synchronously: whichever handler takes it first wins, and the other finds nothing and does nothing. State could not fix this — a state update is not visible to a handler in the same render.

MCP callers could not tell which files a workflow needs. Workflow tools now describe their files parameter per workflow: each slot, its label, and the filename of any pinned default, so a model can see what the call will use if it passes nothing, and which slot it must supply when a step ships no file. The workflow-agnostic runner keeps a generic {slot: file_id} schema. No file_id is ever exposed — only slot names, labels and the default's filename.

Security

The automation builder's hand-off draft now re-validates where it sends you. Leaving the builder to create a workflow stashes the draft in sessionStorage along with a returnTo route, and finishing interpolates that value straight into a navigate() target. It was checked only for being a string.

It is written as location.pathname, so it is same-origin by construction — but it then survives in storage and is re-read rather than re-derived, so anything able to write that key chose where "back to the builder" landed, including an absolute URL or the /\evil.com form that gets past React Router's own check (CVE-2026-53669). It is now resolved against this origin on the way out, via the same safeInternalPath guard the login redirect uses, and falls back to /automations/new when it does not point here. Validating on read rather than on write means the guarantee holds however the value got there.

Defence in depth rather than a fix for a live hole: writing that key requires script execution on the page already. Legitimate builder routes — including query and fragment — are unaffected.

Verification

Tagged only after scripts/release-e2e.sh passed against this commit: docker compose up → create the owner → install the published writ-agent release through the same one-liner the UI prints, checksum verified → record a workflow on a real browser → replay it → call it over REST and over MCP.

You can run the same gate yourself with ./scripts/release-e2e.sh.

The tag is signed — git verify-tag v1.0.2.

Full changelog: CHANGELOG.md · v1.0.1…v1.0.2