Released in v1.824.0, 5 Oct 2026 Upstream fix in Windmill
Windmill: unbounded S3 downloads for AI attachments
Our scanner flagged that Windmill, an open-source platform for scripts, workflows and internal tools, downloaded S3 objects used as AI attachments into memory and base64-encoded them without any size limit, so one large object could exhaust a worker’s memory. Our pull request added a size check. Maintainer Ruben Fiszel pointed out that it came too late and implemented a better fix, released the same day in v1.824.0.
What was wrong
When the messages of an AI step reference an S3 object, download_and_encode_s3_image() in backend/windmill-ai/src/image_handler.rs fetched the whole object and turned it into a base64 data URL for the model provider. Nothing limited its size, and workspace storage can hold large job outputs, dumps or archives. A big enough object meant a big allocation, plus about a third more for the base64 copy, and could run the worker out of memory.
How it was fixed
Our pull request #11515 rejected objects over 30 MB before base64-encoding them. Ruben Fiszel’s review was precise:
“With the check here, a 2 GB object still gets fully buffered before the 30 MiB check runs, so it can still cause the OOM this aims to prevent.”
We agreed, and ours was closed in favour of his pull request #11534. It lets the S3 download helper take a byte limit, rejects a declared Content-Length over the limit before reading the body, and stops reading a streamed response once it passes 30 MiB, with regression tests for both cases. SQL S3 inputs keep downloading without the limit.
Timeline
- 2 Oct 2026. A scan of Windmill’s AI backend (model qwen3.8-27b) rated the unbounded download and encoding in
image_handler.rsas high severity. - 4 Oct. We opened pull request #11515 with a size check before encoding.
- 5 Oct. Ruben Fiszel reviewed it, opened and merged #11534 with a bounded download, and it shipped in v1.824.0 the same day.
How to read this
Our patch only avoided the extra base64 copy; the whole object was still buffered first. The maintainer’s review caught that, and the upstream fix is better than ours. The scan’s value was pointing at the unbounded allocation; the right fix came from someone who knows the code. Findings are leads for people, not verdicts.