SYSONLINE
RELv3.4.1
RACK9 PIPELINES
VRAM4.0-23.8 GB
VISION STUDIO v3.4.1 · FLUX.1 DEV + SCHNELL · SD 3.5 LARGE + MEDIUM · SDXL · SD 1.5 · LTX VIDEO · ANIMATEDIFF V1-5-2 · STABLE VIDEO DIFFUSION XT · LORA + CONTROLNET + IP-ADAPTER · CUDA 12.1 · METAL · LOCAL · FIRST
UTC06:42:46
S/N · VX-00001
MOD · DISPATCH · 01S/N · VX-260916PUBLISHED
← ALL · NOTES
ENGINEERINGBuild Engineering//6 MIN READ/BY

The Same Commit Shipped Twice. Only One Build Worked.

One commit, three build machines, two working apps. The unpinned dependency that broke our macOS release, and the YAML parser hiding inside the update path of the shipped binary.

On 13 September we tagged Vision Studio 3.4.0 and pushed one commit to three build machines. Linux built, passed its backend smoke test, and published. Windows built and published. macOS built from the identical commit, with identical backend source, and produced a bundle that died on startup before it drew a single pixel.

No one had touched the backend between 3.3.0 and 3.4.0. The Python did not change. The only thing that changed was what the installer resolved, and the only reason it changed on macOS is that macOS was the one platform where it was ever free to.

The Error That Named Itself

The macOS bundle raised this on import:

RuntimeError: operator torchvision::nms does not exist

That error is a version mismatch wearing a trench coat. It means torch and torchvision were compiled against different ABIs and got installed side by side anyway. It cascaded through the image utilities into the LoRA loaders and then into the motion model, so the job failed its smoke gate without ever reaching a model.

The installer had been asking for this the whole time. It installed torch torchvision torchaudio, with no version on any of the three.

CUDA Was Hiding the Bug, Not Preventing It

Here is the part worth internalising, because it is the part that made this invisible for three releases.

On Windows and Linux the installer pulls from the CUDA 12.1 wheel index. That index stops at torch 2.5.1. An unpinned request against an index whose newest build is 2.5.1 resolves to 2.5.1, every time, on every machine, forever. Those platforms were not pinned. They were accidentally pinned, by an upstream index that had stopped moving.

macOS has no CUDA wheels. It falls through to the CPU index, and the CPU index tracks latest. So the same unpinned spec that quietly meant "2.5.1" on two platforms meant "whatever shipped this week" on the third.

In September, that was torch 2.14.0, torchaudio 2.11.0, and torchvision 0.29.0. Nobody chose those versions. Nobody reviewed them. They arrived because a resolver was asked an open question and answered it honestly.

This is also why the macOS build could not simply be told to use an older wheel: PyTorch stopped building macOS x86_64 binaries after 2.2.0 in January 2024, so the Intel path was already gone and the deprecation RFC had said so plainly. Apple Silicon is the only macOS target that has wheels at all, and on that target the CPU index is the one that moves.

The Fix Is Boring, Which Is the Point

All four install paths now install one triple:

torch==2.5.1  torchvision==0.20.1  torchaudio==2.5.1

Only the wheel flavour differs by platform. Those three numbers are not a guess and they are not the newest thing that works. They are the exact set that Linux built, smoke tested, and published on the same run that macOS failed. The version that shipped is the version we pinned.

A test now holds all three to ==, fails if torchaudio drifts out of lockstep with torch, and fails if any install path is left free to resolve its own version again. A pin that nothing enforces is a comment.

The Second Bug Was Quieter and Closer to You

The same release carried a security fix worth describing precisely, because the interesting part is not the vulnerability. It is where it lived.

GHSA-2883-xcg3-v3hh (CVE-2026-84375, CVSS 7.5, High) is a denial of service in js-yaml. Its maxTotalMergeKeys limit does not account for empty mappings, so a document that merges empty objects repeatedly can burn CPU without ever tripping the threshold. A parser that will not stop is not an exotic exploit. It is a hung thread.

js-yaml is a YAML parser. Vision Studio is an image and video app. You would reasonably assume it is a build-time dependency and shrug.

It is not. It reaches the shipped application through electron-updater, which parses the update feed your installed copy fetches over the network to decide whether a new version exists. That is a parse of remote input, performed by the shipped binary, on a schedule, on your machine. It is about as far from "build-time only" as a dependency gets.

Shipped Tree, Not Dependency Tree

This is the distinction most audit workflows blur. npm audit tells you what is in your dependency tree. It does not tell you what is in the application you hand to a user. Those are very different lists, and only one of them can be attacked by a stranger.

The industry conversation about supply chain risk is dominated by malicious packages, and the numbers are genuinely alarming: Sonatype's 2026 report counts more than 1,233,000 malicious open source packages catalogued across npm, PyPI, Maven, NuGet and Hugging Face. That risk is real and it deserves the attention it gets.

But neither bug in this release was malicious. Both dependencies behaved exactly as documented. One resolved an unpinned request the only way it could, and one had a CPU bound that did not cover an edge case. No attacker was involved in the macOS failure at all. The build simply believed a version range instead of a version.

Datadog's 2026 research found that 71% of organisations never pin a CI action to a commit SHA, preferring mutable tags. The same instinct that leaves @v4 floating in a workflow leaves torch floating in an installer. It is not laziness. Floating ranges are invisible until the day the upstream moves, and on most platforms, most of the time, the upstream does not move.

What We Actually Changed

ProblemWhy it survivedWhat stops it now
Unpinned torch stackCUDA index froze at 2.5.1, so two of three platforms looked pinnedExact pins on all four install paths, enforced by a test that rejects a self-resolving path
js-yaml in the update pathRead as a build dependency; it parses remote feeds inside the shipped appOverride scoped to electron-updater, verified by parsing all three live production feeds and diffing the output
BothRules written in commentsTests that fail the build, not prose that asks nicely

On that second row: electron-updater 6.8.9, the current release, still declares js-yaml: ^4.1.0. We pinned its copy forward rather than wait for that range to be tightened upstream, then verified the substituted parser round-trips a real production feed with numeric scalars intact. A security fix that silently changes how your update feed parses is not a fix. It is a different outage.

Four Things Worth Stealing

  • A green build on one platform is evidence about one platform. Our Linux job was not wrong. It answered a different question than the macOS job, and we read it as though it had answered both.
  • Ask what your installer resolves, not what it requests. The request was identical across every platform for three releases. The resolution was not.
  • Sort dependencies by whether they ship, not by whether they are direct. A transitive dependency that parses network input in the shipped binary outranks a direct one that runs once on your laptop.
  • Pin to what you shipped, not to what is newest. The correct version is the one that passed the test you actually ran.

The Version You Did Not Choose

Every dependency range is a decision you have deferred to a resolver, on a date you will not pick, on a machine you may not be watching. Most of the time the resolver makes the same choice it made yesterday, and you never learn the decision was open.

Our macOS users learned on a Sunday. The fix took one line per install path. Finding out the line was missing took a platform failing in a way its two siblings could not.

Vision Studio 3.4.0 is free, open-source (MIT), and runs locally on your GPU. The full changelog for this release, including both fixes above, is at github.com/Git-Rocky-Stack/vision-studio. Download it at vision-studio-x.com/download.

-Rocky

#DependencyManagement #SupplyChainSecurity #PyTorch #Electron #BuildEngineering #OpenSource #VisionStudio

dependency pinningsupply chain securityPyTorchElectronbuild engineeringreproducible buildsopen sourceVision Studio
DOWNLOAD · FREE
VX-DISPATCH · MODULE
S/N · VX-260916
STX · ENGINEERED · LOCAL · ALWAYS