AI dependency pinning checklist for app builders
A checklist for the Python, JavaScript, CI and image dependencies behind an AI app. Each item ends as detected, unknown or confirmed by owner evidence.
Published Last updated
To pin Python dependencies for an AI app, commit a lockfile that records file hashes, install from it in a mode that fails when it is out of date, and add a release cooldown so brand-new versions wait. Repeat the idea for npm, GitHub Actions and Docker images. Pinning stops silent upgrades. It does not review the version you pinned.
Why does pinning matter for an AI app in 2026?
Three incidents reached developers through installs that took the newest version, or whatever a tag pointed to that day. Dates and times below come from the primary sources, checked October 24, 2026.
| Date | What happened | What an unpinned install did | Source |
|---|---|---|---|
| March 19 to 20, 2026 | 76 of 77 trivy-action version tags, and all setup-trivy tags, were replaced with malicious commits | A workflow that referenced a tag ran the replaced code | GHSA-69fq-xp46-6x23 |
| March 2026 | Malicious LiteLLM and Telnyx releases on PyPI after an API token exposure from an exploited Trivy dependency. LiteLLM was live 2 hours 32 minutes, with 119,000+ downloads | Any install that resolved the newest version took it | PyPI blog |
| October 21, 2026 | LiteLLM 1.107.0 on PyPI from 11:39:51 to 13:23 UTC. Docker tags v1.107.0, latest, main-latest and main-stable resolved to the bad image from 11:46:56 to 14:20 UTC | An install of the newest version, or a pull of a moving tag, inside those windows | LiteLLM |
The maintainer's report, which was still marked investigation in progress at our check, tells users to pin 1.106.2 or any explicit version published before the incident. PyPI's report adds the limit of that advice: a lock freezes what you run today, and a compromise can still arrive the next time you update. So this checklist has two halves. Freeze what you run, and slow down what you take next.
If you want the incident facts and what to rotate, read the LiteLLM page for app builders. The wider pattern is in supply-chain attacks on AI tooling. This page is the hands-on part.
Where does my project choose which version to install?
Newly builds a real iPhone and Android app with React Native and Expo, and you can download the project code or connect your own repository. Once you have the repository, four places decide what gets installed. A pin in one place does nothing for the others.
Open each one and write down what you find. The table in the next section tells you how to read it.
What does pinned look like, layer by layer?
| Layer | Pinned looks like | Floating looks like |
|---|---|---|
| Python | A committed uv.lock, installed with uv sync --locked. Or a requirements file where every dependency has == and a hash, installed with --require-hashes | A range such as litellm>=1, no lockfile, or a plain pip install litellm in a build script |
| JavaScript | A committed lockfile for your package manager, and npm ci or its equivalent in CI | ^ ranges with no lockfile in the repository, or npm install in CI |
| Expo builds | The default: EAS installs with the frozen lockfile | EAS_NO_FROZEN_LOCKFILE set to 1 in eas.json |
| GitHub Actions | uses: owner/action@ followed by a full 40-character commit SHA | @v1, @main or any other tag |
| Images | FROM name:tag@sha256:..., and the same digest in the deploy config | latest, main-stable or a bare tag |
| Update speed | uv exclude-newer set to a duration, and a Dependabot cooldown block | Every new release is eligible the minute it appears |
| Publishing | Trusted Publishing with short-lived tokens | A long-lived publish token stored as a CI secret |
A row that matches the floating column is detected. A row you have not opened is unknown. A row stays unknown until you hold the file or log line that shows the pin.
How do I pin Python dependencies, step by step?
- Find every place Python installs. Look at
requirements*.txt,pyproject.toml, Dockerfiles, CI files, setup scripts and the environment any AI coding agent created on your machine. - Write a lock that records hashes. PyPI's report says a lock file needs checksums of the package archives to be reproducible. With uv, the
uv.lockfile should be committed. With pip, the hash-checking mode requires requirements pinned with==and a hash for every dependency, and it errors on any that is not spelled out. The standard lock format, PEP 751, is final and requires at least one hash per file entry. - Install in a mode that fails on drift.
uv sync --lockedraises an error instead of updating an out-of-date lockfile. The--frozenflag skips that check, so use the first in CI and images. For pip, add--require-hashes. - Read the lock's history. Run
git log --format=%cI -- uv.lock(or your requirements file). A change inside the 11:39 to 13:23 UTC window on October 21, 2026, that records litellm 1.107.0, is detected. Go to the LiteLLM page. No change in the window is a good sign and still leaves other packages unknown. - Add a cooldown. uv's
exclude-newersetting accepts a duration such as 7 days and compares it with the upload time of each file. LiteLLM 1.107.0 was on PyPI for under two hours, so a delay of days would have excluded it. A cooldown cannot help with a bad release that stays unnoticed longer than the delay.
One more reason to pin and to rebuild from scratch after an incident: Python's site module runs lines that begin with import in .pth files when the interpreter starts. Code placed there runs without your project importing it, which is why turning off install scripts does not cover Python.
What about the JavaScript and Expo side?
Commit the lockfile your package manager writes, and install from it in CI. npm ci removes any existing node_modules, never writes to package.json or the lockfile, and fails when they disagree.
For Expo, the EAS Build documentation says Node packages are installed by default with the package manager's immutable lockfile command, and that setting EAS_NO_FROZEN_LOCKFILE to 1 in eas.json turns that off. Search your project for that name. Finding it set to 1 is detected. Not finding it means the default applies.
If a package worm hit your npm dependencies, Red's ChainDrop check covers that case. An AI provider key never belongs in the app bundle; the leaked AI key page shows where those keys escape. AI inside your Newly app uses credits, so a leaked key is also a cost problem.
How do I pin GitHub Actions and Docker images?
Actions
GitHub's hardening guide tells you to pin actions to a commit SHA and to set the default GITHUB_TOKEN permission to read access for repository contents. The Trivy case shows why: a tag can be moved, a full commit SHA cannot. Red's page on fake security-audit workflows covers a related GitHub Actions campaign.
Images
Docker's guidance is that pinning to a digest keeps you on the same image even if the publisher replaces the tag. Its cost is stated in the same place: you opt out of automated security fixes, so let Dependabot's Docker support propose digest updates and read them. A digest copied from a registry identifies fixed content. Copy yours from a time you trust. A digest taken inside an incident window pins the bad image. If you use uv in a container, its Docker guide recommends pinning the uv image by SHA256 and running uv sync --locked.
Add a cooldown to the update bot as well. Dependabot's cooldown option delays version updates by a number of days and does not apply to security updates.
Do I need Trusted Publishing?
Only if you publish a package of your own, such as a shared helper for several apps. PyPI's Trusted Publishers exchange short-lived OpenID Connect tokens that are valid for 15 minutes, in place of a long-lived API token. In GitHub Actions the job needs the id-token: write permission. For npm, the trusted publishing docs require npm CLI 11.5.1 or later and generate provenance automatically. PyPI's report recommends the same move for maintainers, since the March releases followed an exposed API token. To check yours, look for a publish token in the repository's CI secrets. If one is there, it is detected.
What does pinning leave open?
- A bad version you chose. A hash proves the file is the one you locked. It says nothing about whether that version is good.
- A lock written on the wrong day. Regenerating a lock inside an incident window records the bad version with a matching hash.
- Keys already read. If a bad version ran, the fix is rotation. The rotation order for app teams and Red's rotation guide cover it.
Keep the three states apart
Spotting a floating range is detected. Not having looked is unknown. Confirmed by owner evidence means the lockfile, the install line in a CI log and the date of the last lock change are all in your hands. Nothing outside your repository, our scan included, can move an item to confirmed. For the first fifteen minutes of a wider check, start with is my AI-built app secure, and see the security hub for the rest.
Where do you stand?
Mark each item. Everything starts as unknown, because an unchecked item is unknown. Move one to confirmed only when you hold the evidence listed under it.
Your pinning check
- Detected: You found the problem.
- Unknown: Not checked yet, or the check could not tell.
- Confirmed by owner evidence: You checked and kept the proof.
Your answers stay in this browser tab. Nothing is saved or sent.
Frequently asked questions
I only build with Newly and never touch Python. Does this apply?
The JavaScript half does. Newly builds a real React Native and Expo app, and if you download your project code or connect your own repository, the package manager lockfile in it decides which versions your builds install. The Python half applies only if you also run server code, an AI gateway, scripts or CI that install Python packages.
Would pinning have stopped LiteLLM 1.107.0?
A project that installed from a lockfile written before 11:39 UTC on October 21, 2026 would not have fetched 1.107.0 from PyPI. A project that refreshed its lock or ran an unpinned install inside the window could have. The maintainer's own advice at our check on October 24 was to pin 1.106.2 or any explicit version published earlier. Check your lockfile history rather than assuming.
Is putting == in requirements.txt enough?
No. It fixes the packages you listed, but the packages they depend on can still float. pip's hash-checking mode closes that gap: it requires every dependency to be pinned and hashed, and it fails on anything not spelled out in the file.
Should I then stop updating dependencies?
No. A cooldown delays new releases for a set number of days, so updates still arrive. Dependabot's cooldown setting applies to version updates and not to security updates, so a published fix is not held back by it. Read each update before you merge it.
Do I need Trusted Publishing if I never publish a package?
No. It protects the people who publish to PyPI or npm. If your app project only installs packages, skip that section and mark the self-test item as having no publish step.
Can the red.run.cloud scan see my lockfile or CI?
No. The scans work from outside your live site. They cannot read your repository, workflows or build machines, so everything on this page stays unknown until you hold the evidence listed in the self-test. They can show readable .env and .git paths and keys in shipped bundles.
Sources
Checked October 24, 2026. Vendor statements are the vendor's own.
- Security incident, October 21, 2026, LiteLLM
- Incident report: LiteLLM and Telnyx supply chain attack, Python Package Index blog, April 2, 2026
- GHSA-69fq-xp46-6x23, Trivy supply chain compromise, Aqua Security on GitHub
- Secure installs and hash-checking mode, pip documentation
- Syncing the environment: --locked and --frozen, uv documentation
- Project layout and the uv.lock file, uv documentation
- Resolution: the exclude-newer option, uv documentation
- Using uv in Docker, uv documentation
- PEP 751, a file format to record Python dependencies, Python Enhancement Proposals
- site: site-specific configuration hook, .pth files, Python documentation
- npm ci, npm documentation
- Caching dependencies: immutable lockfile installs, Expo docs
- Security hardening for GitHub Actions, GitHub Docs
- Dependabot options reference: cooldown, GitHub Docs
- Dockerfile best practices: pin base image versions, Docker documentation
- Tags and digests, Docker documentation
- Trusted Publishers, PyPI documentation
- Using a trusted publisher, PyPI documentation
- Trusted publishing for npm packages, npm documentation
