The package manager in GitHub Actions might be the worst package manager in use today: https://nesbitt.io/2025/12/06/github-actions-package-manager.html
GitHub Actions Has a Package Manager, and It Might Be the Worst

GitHub Actions has a package manager that ignores decades of supply chain security best practices: no lockfile, no integrity verification, no transitive pinning

Andrew Nesbitt
@andrewnez I've been looking forward to this blog post. 😅

@andrewnez sometime I feel all these github action should have been just a python/bash script...

especially when you need 100 commits to get it right and had to trust the code of 2-4 contributors/organisations to do so.

this would make more sense to me : https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies
with https://github.com/astral-sh/uv/issues/6318

Running scripts | uv

A guide to using uv to run Python scripts, including support for inline dependency metadata, reproducible scripts, and more.

@mestachs @andrewnez Nothing prevents you from making a very simple workflow that checks out your repo and then calls a script you have full control over.

All actions are mostly just convenience.

Scripts shared by strangers you can run and don't have to copy/paste from gists or stackoverflow.

@andrewnez Yikes, it's worse than I thought. Thanks for the writeup.

Maybe they'll get around to fixing things after the AI bubble bursts... ?

@andrewnez cargo has such a vast degree of the problems you correctly assign to github actions and it is very specifically because any of the guarantees cargo provides for itself are completely unavailable to any build script. this means shared mutability retrofitted onto a subdir of ~/.cache and that is actually the best possible case. cargo specifically is the reason rustc remains so difficult to bootstrap because you absolutely 100% cannot send the output of one build script to another without shared mutable state
@andrewnez a lockfile is only as trustworthy as the things it had control over and with cargo that means checksums of source tarballs and even source checkouts aren't immutable. you absolutely can access the filesystem location cargo caches you in and use that as a release-specific mutable cache for your build script. no one has to know!

@andrewnez i think there's a slippage of terminology here too:

The core problem is the lack of a lockfile. Every other package manager figured this out decades ago: you declare loose constraints in a manifest, the resolver picks specific versions, and the lockfile records exactly what was chosen.

for one, the resolver is only as powerful as the constraints. if your constraints are limited to name and simple version matching, then a lockfile is indistinguishable from a manifest—we see this from pip freeze, which is actually quite notable for not being a lockfile.

@andrewnez python has per-platform and per-python-version constraints, for which you'll need something like pip install --report to get a "locked" experience. that feature was a generalization of my work here https://github.com/pantsbuild/pants/pull/8793 which was able to lazy-load python requirements upon execution.

the poetry package manager has a truly fascinating system where it resolves a dependency tree across several target platforms at once. it's able to do this though because python packaging protocols have had a lot of people caring about them for many years.

introduce --generate-ipex to (v1) python binary creation to lazy-load requirements by cosmicexplorer · Pull Request #8793 · pantsbuild/pants

Problem See pex-tool/pex#789 for a description of the issue, and https://docs.google.com/document/d/1B_g0Ofs8aQsJtrePPR1PCtSAKgBG1o59AhS_NwfFnbI/edit for a google doc with pros and cons of differen...

GitHub
@andrewnez contrast to cargo. you can trivially change the checksum of an output by running rustup for a different toolchain. cargo does not have any mechanism to enforce this besides msrv. undocumented resolution semantics abound whenever a build script is involved because cargo offers no way to document those resolution mechanics. and cargo lockfiles are i believe the only thing secured by checksum, because everything else is invalidated by modification time.
@andrewnez i applied to NGI Zero for a way to rewrite cargo https://circumstances.run/@hipsterelectron/114610077000401178 but that was before i thought fixing build scripts was actually achievable. my first approach to this was to overlay the spack build graph onto cargo's by making use of—you guessed it—a shared cache dir. this is how https://docs.rs/re2 is able to specify dependency ranges for c++ code in a Cargo.toml.
d@nny disc@ mc² (@[email protected])

Content warning: ngi app on capillary the rust build tool

GSV Sleeper Service
@andrewnez but one thing you need to completely redevelop from scratch in a cargo build script is ABI, because cargo always builds everything from source. spack on the other hand is able to resolve from-source and prebuilt packages because it has the most powerful constraint system https://spack.readthedocs.io/en/latest/spec_syntax.html, and it also distinguishes the spec hash of a package configured with the specified variants and platform from the output build hash.
Spec Syntax - Spack 1.2.0.dev0 documentation

A detailed guide to the Spack spec syntax for describing package constraints, including versions, variants, and dependencies.

@andrewnez there's a spectrum that goes from github actions to spack and cargo is much closer to the former than i'd like. i'm planning to write a tiny version of pants https://pantsbuild.org to replace cargo entirely in the rustc build system but the same tool should be able to make cargo build scripts much much more auditable if cargo continues to abdicate that responsibility for itself
Pantsbuild

@andrewnez thank you for documenting all of this. The scope of the problem, and GH’s lack of addressing it despite being reported, is horrifying. Very relieved to see gitlab understands the issue and has put in some reasonable mitigation and warnings.

@andrewnez Just read through your post on Actions and it has a lot of valid criticism.

It's also still missing a few things which irked me for a while.

AND. Actions can be quite secure and reproducible... But it requires work, just like most NPM users run `npm install` instead of `npm ci` and never get reproducible builds either.

For extra security, add:
* Sha pinning
* fork actions
* limit actions to your own fork org
* only rely on script-based composite actions or sha pinned actions.

This requires some review work, but is quite doable.

This gives you reproducible builds, pinning, some level of integrity. And it's all enforcable by polity.

It it great? No. But when I compare it to other CI/CD tools, they are generally equally bad. Azure Pipelines? Yuk. TeamCity? Nope. Jenkins? Brrr?

Are there technically better solutions? Absolutely! But one of the powers of the actions platform is the ease at which people were able to grow the ecosystem. I've seen more innovation on GitHub Actions than ever on any other CI/CD patform, because anyone can contribute and because everything is possible :D.

@andrewnez Some feedback on the post:

Because of the way GitHub Actions resolves the version when using a "wildcard version", immutable releases add ZERO improvement. Action maintainers are required to force-push the major version tag every time they release a minor release anyway.

There is a funny extra behavior when it comes to tags *and* branches, when a v1 tag exists, and a v1 branch is pushed, the branch will take precedence over the tag. I've always considered this a security bug, but GitHub disagrees.

The reason there are so many unverified actions, is because GitHub won't verify individuals, only GitHub Organizations. So any action published to a personal namespace is by definition unverified. Even if the owner of the namespace is well known, a GitHub Star or MVP. Even some actions owned by GitHub employees are published to personal namespaces.

There are a few extras you can enable, especially on public repos, Automatic Dependency Submissions, Code Scanning for GitHub Actions etc which will add a layer of security to the environment.

Dependabot and RenovateBot offer features to auto-append a `# 4.1.7` version after the sha, adding both integrity and 'readability' to the reference.

You can control what Composite actions pull in, because you can set policy at the repo and org level controlling which actions (up to the sha if you want to) are allowed to run. And you can opt to not use composite actions at all. Since most actions will run scripts and often even download scripts and then run them, just making the action locked down, doesn't make the behavior locked down.

With automatic dependency submission, you can see all the actions and versions you rely on. But this requires you to opt in. And to get a cross repo overview you need a GitHub organization.

I've written a tool to try and build that tree.

@andrewnez Tool for submitting dependencies on actions and transitive dependencies for composite actions and callable workflows:

https://github.com/jessehouwing/actions-dependency-submission

GitHub - jessehouwing/actions-dependency-submission: Action to automatically report versions for pinned action dependencies

Action to automatically report versions for pinned action dependencies - jessehouwing/actions-dependency-submission

GitHub

@andrewnez Tool for ensuring a repo is actually doing its versioning correctly, cause many do not:

https://github.com/jessehouwing/actions-semver-checker/

GitHub - jessehouwing/actions-semver-checker: This action checks the version tags in your repository to ensure correct semantic versioning behavior.

This action checks the version tags in your repository to ensure correct semantic versioning behavior. - jessehouwing/actions-semver-checker

GitHub

@andrewnez There is a long-time coming feature called Immutable Actions which supposedly solves almost all of these issues.

It stores the action contents in a docker image style package, publishes that to github packages as immutable filesystem snapshots.

You should be able to add metadata to those images, which should cover most of your other requirements. including SBOM support, integrity, pinning and more.

Unfortunately, it has taken forever to get released, as the tradeoff between flexibility and ease of use collides with integrity and security. The use cases for Actions are many and the needs are even more diverse.