DevSecOps27 October 20266 min read

The AI Software Supply Chain — Part 2 of 9

Pin the version before you pin your hopes on the platform

A shared AI component that resolves 'latest' at install time will eventually install something nobody chose. Part 2 explains what pinning actually means for gateways, models and agent tooling, why it is not the same as freezing, and the upgrade path that keeps a pinned platform current.

#AI Supply Chain#DevSecOps#Platform Engineering#Production Readiness

Ask a platform team which version of their model gateway is running and you will usually get a confident answer about production. Ask which version the CI evaluation jobs install, or what a developer gets when they set up the project on a new laptop, and the answer becomes "whatever is current".

That gap is where most supply-chain exposure lives. Production is often pinned, because someone built a container image once and it has not changed since. Everything around production resolves versions at install time, every time it runs.

Langeberg Software is fictional, and its systems and figures are illustrative. In Part 1, Langeberg's platform team found they could prove which gateway version ran in production but not which version their CI and developer tooling had installed on any given day. This part is about closing that gap without turning the platform into a museum.

What pinning means, precisely

"Pinning" gets used loosely. There are three levels, and they protect against different things.

Pinning a version number (package==2.3.1) stops an install from silently moving to a newer release. It does not protect you if the registry serves different content under that number, or if a dependency of that package floats.

Pinning with a lockfile and hashes records the exact version of every direct and transitive dependency, plus a cryptographic hash of each file. The installer refuses anything whose hash does not match. Python's pip supports this with --require-hashes; most other ecosystems have an equivalent in their lockfile format. This is the level that would have kept a bad release out in the March incident described in Part 1, regardless of how long it was live.

Pinning an image by digest (image@sha256:... rather than image:2.3.1) does the same for containers. A tag can be moved to point at a different image. A digest cannot.

For anything that holds secrets or sees sensitive data, the target is the second and third levels everywhere the component runs: production, staging, CI runners, scheduled jobs and developer environments.

The morning that makes the case

Same morning, two install policiesPACKAGE REGISTRYbad release live08:0010:4011:2017:00FLOATING: INSTALL LATESTCI job installs latestBad version is now insidePINNED: LOCKFILE AND HASHBuild reads lockfileKnown version, same hash
A malicious release is live for a short window. The floating install resolves 'latest' during the window and pulls it. The pinned build resolves the version and hash in the lockfile, so the bad release never enters it, however long the window is. Times are illustrative.

The timeline above is illustrative, but its shape is the shape of every registry compromise. A malicious release is live for a window. Anything that resolves "latest" during that window installs it. Anything that reads a lockfile with hashes does not. The length of the window matters only to the floating installs.

At Langeberg, the production gateway image had been built from a lockfile months earlier and was safe. But the nightly evaluation pipeline, an ad hoc benchmarking script one developer ran from their laptop, and the onboarding script for new engineers all ran a plain pip install of the gateway's client library and its proxy extras. On a bad morning, three different environments would have pulled three potentially different things.

Models and prompts need pinning too

The same logic applies to the AI-specific components, with some differences in mechanism.

Hosted models. Most providers offer both a moving alias (a name that tracks the latest version of a model family) and dated or versioned identifiers. A moving alias is convenient for experiments and risky in production, because the behaviour your evaluation suite approved may not be the behaviour you get next month. Production configuration should name a specific model version, and changing it should be a release.

Self-hosted models. Record the source, the exact file and its hash. A model file downloaded from a public hub by name is the equivalent of an unpinned package.

Prompts and policies. Store them in version control and deploy them like code, so the version in production is a commit you can name. Editing a system prompt in a live admin screen is an unreviewed production change.

Agent tooling. Coding-agent skills and MCP servers installed by developers are often the least pinned items in the estate. Part 5 covers them in detail; the principle is the same: a version or hash, recorded somewhere other than one person's machine.

Pinning is not freezing

The usual objection to strict pinning is that the platform will fall behind, miss security fixes and become painful to upgrade. That happens when pinning is treated as a destination. It is a starting point for deliberate change.

A pinned dependency still upgrades, deliberately1 PROPOSENew version andrelease notes read2 LOCKLockfile, hashand image digest3 TESTUnit, contract andevaluation suite4 STAGESoak in stagingon realistic load5 RELEASECanary, theneveryoneRollback: previous digest stays deployable
Pinning is not freezing. Every upgrade walks the same five steps, and the previous version stays deployable until the new one has proven itself. The return path is tested before it is needed, not discovered during an incident.

Langeberg's upgrade path has five steps, and the previous version stays deployable until the new one has proven itself:

  1. Propose. Someone reads the release notes, including what changed in permissions, configuration and dependencies, and opens a change.
  2. Lock. The lockfile, hashes and image digest are regenerated in a clean environment and committed. Tooling such as Dependabot or Renovate can open these changes automatically; a person still decides to merge them.
  3. Test. Unit and contract tests run, and so does the evaluation suite, because a gateway or framework upgrade can change model behaviour through retries, timeouts or prompt formatting without any code change in the application.
  4. Stage. The new version runs in staging against a realistic load for long enough to see streaming, timeouts and error handling under pressure.
  5. Release. A canary takes a slice of traffic first, then everyone.

The rollback path is tested before it is needed. For Langeberg, that means the previous image digest and its configuration remain deployable with a single change, and the team has actually done it once in staging.

Security fixes still move fast under this model. The difference is that a fast upgrade is a shortened walk through the same steps, with an explicit decision about which steps can be compressed, rather than a pip install --upgrade on a production host.

Where to start

You do not need to pin everything at once. Langeberg's order was driven by reach:

  1. The gateway image and its configuration, because it holds the provider keys and sees every prompt.
  2. CI and scheduled jobs that install AI tooling, because they run unattended and usually hold secrets for test environments.
  3. Production model identifiers and prompts, because behaviour changes there reach customers.
  4. Developer setup scripts, because developer machines hold a surprising number of credentials.
  5. Agent skills and MCP servers, starting with anything that runs code or holds credentials.

Each step is small. Together they mean that the answer to "which version was installed where?" is a lookup, not an investigation.

The test

A useful test for any shared AI component: if a new release were found to be malicious tomorrow, could you show from your repositories and registries alone that it never entered any environment? If the answer depends on what people remember running, the component is not pinned yet.

For a broader view of how delivery pipelines carry these controls, see Least-privilege as code and Keeping systems fit: technical debt in the age of cheap code.

Next: Part 3, What to do when an AI dependency is compromised. All parts are on the series page.

How CloudNala can help

We help platform teams move shared AI components onto pinned, hash-verified installs across production, CI and developer environments, and design an upgrade path with evaluation and rollback built in. The work is usually a short engagement: an inventory of where versions float today, the pipeline changes to fix it, and a rehearsed rollback.


Work with CloudNala

CloudNala helps organisations move from technology ambition to practical execution across cloud, AI, data, platform engineering and digital services.

Whether you are exploring AI, modernising your cloud environment, building a public-sector digital service, or turning an idea into a working MVP, we can help you shape the roadmap and deliver the next step.

Book an AI Supply Chain Review or write to us at consult@cloudnala.co.za