Andrew Nesbitt

@andrewnez.mastodon.social.ap.brid.gy

Package Management Nerd, working on mapping the world of open source software https://ecosyste.ms and blogging about package managers at https://nesbitt.io 🌉 bridged from ⁂ https://mastodon.social/@andrewnez, follow @ap.brid.gy to interact

Shared Code Between Package Managers https://nesbitt.io/2026/08/11/package-manager-library-reuse.html

Shared Code Between Package Managers

Writing up the package manager CWEs list and then the `--end-of-options` survey, both of which come down to the same bug being fixed independently in tool after tool, left me wondering how much code these tools share with each other in the first place. So I went through the direct dependencies of twenty package managers1, each with at least two same-language peers in the set, looking for package-management libraries reused between them and setting aside the general-purpose stuff any CLI would use (serde, clap, requests). Three of the twenty depend directly on another manager’s own packages: Pixi declares twenty-eight uv crates as its PyPI backend (including `uv-resolver`, `uv-distribution`, `uv-client`, `uv-install-wheel`, `uv-pep440`, and `uv-git`), pnpm depends on five Yarn Berry packages (`@yarnpkg/core`, `@yarnpkg/lockfile`, `@yarnpkg/pnp`, `@yarnpkg/nm`, `@yarnpkg/extensions`), and uv takes `cargo-util` from the Cargo repository. Eight of the shared libraries are npm-org packages used across npm, Yarn Berry, and pnpm in varying combinations: `semver`, `ssri`, `hosted-git-info`, `validate-npm-package-name`, `npm-registry-fetch`, `libnpmpublish`, `node-gyp`, `bin-links`. PyPA publishes `packaging` (PEP 440 specifiers, used by pip, Poetry, and Conda) and `pyproject-hooks` (PEP 517 build-backend invocation, used by pip and Poetry), and in both the npm and PyPA cases the publisher also maintains the spec the library implements. uv and Pixi both declare Embark Studios’ `spdx` crate for licence-expression parsing, separately from Pixi’s use of uv’s crates. DNF5 and Mamba both use `libsolv`, the one solver library in the corpus adopted across unrelated registries. pip, Bundler, and Homebrew reuse code by vendoring it rather than declaring a dependency, for bootstrap reasons. pip’s `_vendor/` directory contains `packaging`, `pyproject_hooks`, `distro`, `platformdirs`, `requests`, and about a dozen more, so pip does use the same PEP 440 parser as Poetry and Conda, without the dependency edge. A CVE fix in a vendored library reaches each consumer as a re-vendor commit, and all three have tooling for that step (pip’s is `nox -s vendoring` against a pinned `vendor.txt`). The only overlap in the Ruby group is `ruby-macho`, for reading Mach-O headers, shared by Homebrew and CocoaPods. Until recently there was a second: CocoaPods’ Molinillo resolver was vendored by Bundler from 2014 and by RubyGems from 2015. Bundler 2.4.0 replaced it with pub_grub in December 2022, and RubyGems followed on master in June 2026. No two managers share a git subprocess wrapper except through Pixi’s use of `uv-git`. Outside JavaScript, no two share an archive extractor with path checks. Most of the twelve recurring client-side bug classes in that CWE list correspond to one of these reimplemented operations: path traversal in the archive extractor, argument injection in the git wrapper, ReDoS in the version-range parser, credential leaks in the registry HTTP client, integrity checks that fail open in the download path. npm’s `tar` package, which npm and Yarn Berry both depended on in 2021, had five path-traversal advisories that year (CVE-2021-32803, CVE-2021-32804, CVE-2021-37701, CVE-2021-37712, CVE-2021-37713): five fixes made in one codebase and taken by each consumer as a version bump. The same applied to `semver` (CVE-2022-25883) for npm, Yarn, and pnpm, and to `hosted-git-info` (CVE-2021-23362) for npm and pnpm. The `--end-of-options` survey traced eight git argument-injection CVEs across six tools: Bundler in 2021, Composer in 2021 and 2022, CocoaPods and Poetry in 2022, pip in 2023, Go in 2026. Each of those eight was reported and fixed independently, with the affected tool vulnerable to a publicly documented attack from the first disclosure of the pattern until its own patch was released, a gap of five years in Go’s case. npm ships inside Node, so npm’s bootstrap is handled by the Node installer. That lets npm’s internals be published as separate packages that competitors depend on. RubyGems and pip have no equivalent: a `git-source` gem published by the RubyGems team would be a dependency RubyGems itself couldn’t declare. `libsolv` is maintained outside any of the managers that use it, and the operations with per-tool advisory streams in the CWE dataset (git invocation, checked archive extraction, download-verify-cache) have no spec owner. 1. Dependency data is a July 2026 snapshot of each repository’s default branch. The twenty are Ruby ×3, JavaScript ×3, Rust ×3, Python ×5, C/C++ ×6. The `tar`, `semver`, and `hosted-git-info` edges cited against 2021–2022 CVEs are checked against npm 7.20, Yarn Berry 3.0.0, and pnpm’s `git-resolver` package.json at the time of each advisory. ↩

nesbitt.io

Shared Code Between Package Managers https://nesbitt.io/2026/08/11/package-manager-library-reuse.html

Shared Code Between Package Managers

Writing up the package manager CWEs list and then the `--end-of-options` survey, both of which come down to the same bug being fixed independently in tool after tool, left me wondering how much code these tools share with each other in the first place. So I went through the direct dependencies of twenty package managers1, each with at least two same-language peers in the set, looking for package-management libraries reused between them and setting aside the general-purpose stuff any CLI would use (serde, clap, requests). Three of the twenty depend directly on another manager’s own packages: Pixi declares twenty-eight uv crates as its PyPI backend (including `uv-resolver`, `uv-distribution`, `uv-client`, `uv-install-wheel`, `uv-pep440`, and `uv-git`), pnpm depends on five Yarn Berry packages (`@yarnpkg/core`, `@yarnpkg/lockfile`, `@yarnpkg/pnp`, `@yarnpkg/nm`, `@yarnpkg/extensions`), and uv takes `cargo-util` from the Cargo repository. Eight of the shared libraries are npm-org packages used across npm, Yarn Berry, and pnpm in varying combinations: `semver`, `ssri`, `hosted-git-info`, `validate-npm-package-name`, `npm-registry-fetch`, `libnpmpublish`, `node-gyp`, `bin-links`. PyPA publishes `packaging` (PEP 440 specifiers, used by pip, Poetry, and Conda) and `pyproject-hooks` (PEP 517 build-backend invocation, used by pip and Poetry), and in both the npm and PyPA cases the publisher also maintains the spec the library implements. uv and Pixi both declare Embark Studios’ `spdx` crate for licence-expression parsing, separately from Pixi’s use of uv’s crates. DNF5 and Mamba both use `libsolv`, the one solver library in the corpus adopted across unrelated registries. pip, Bundler, and Homebrew reuse code by vendoring it rather than declaring a dependency, for bootstrap reasons. pip’s `_vendor/` directory contains `packaging`, `pyproject_hooks`, `distro`, `platformdirs`, `requests`, and about a dozen more, so pip does use the same PEP 440 parser as Poetry and Conda, without the dependency edge. A CVE fix in a vendored library reaches each consumer as a re-vendor commit, and all three have tooling for that step (pip’s is `nox -s vendoring` against a pinned `vendor.txt`). The only overlap in the Ruby group is `ruby-macho`, for reading Mach-O headers, shared by Homebrew and CocoaPods. Until recently there was a second: CocoaPods’ Molinillo resolver was vendored by Bundler from 2014 and by RubyGems from 2015. Bundler 2.4.0 replaced it with pub_grub in December 2022, and RubyGems followed on master in June 2026. No two managers share a git subprocess wrapper except through Pixi’s use of `uv-git`. Outside JavaScript, no two share an archive extractor with path checks. Most of the twelve recurring client-side bug classes in that CWE list correspond to one of these reimplemented operations: path traversal in the archive extractor, argument injection in the git wrapper, ReDoS in the version-range parser, credential leaks in the registry HTTP client, integrity checks that fail open in the download path. npm’s `tar` package, which npm and Yarn Berry both depended on in 2021, had five path-traversal advisories that year (CVE-2021-32803, CVE-2021-32804, CVE-2021-37701, CVE-2021-37712, CVE-2021-37713): five fixes made in one codebase and taken by each consumer as a version bump. The same applied to `semver` (CVE-2022-25883) for npm, Yarn, and pnpm, and to `hosted-git-info` (CVE-2021-23362) for npm and pnpm. The `--end-of-options` survey traced eight git argument-injection CVEs across six tools: Bundler in 2021, Composer in 2021 and 2022, CocoaPods and Poetry in 2022, pip in 2023, Go in 2026. Each of those eight was reported and fixed independently, with the affected tool vulnerable to a publicly documented attack from the first disclosure of the pattern until its own patch was released, a gap of five years in Go’s case. npm ships inside Node, so npm’s bootstrap is handled by the Node installer. That lets npm’s internals be published as separate packages that competitors depend on. RubyGems and pip have no equivalent: a `git-source` gem published by the RubyGems team would be a dependency RubyGems itself couldn’t declare. `libsolv` is maintained outside any of the managers that use it, and the operations with per-tool advisory streams in the CWE dataset (git invocation, checked archive extraction, download-verify-cache) have no spec owner. 1. Dependency data is a July 2026 snapshot of each repository’s default branch. The twenty are Ruby ×3, JavaScript ×3, Rust ×3, Python ×5, C/C++ ×6. The `tar`, `semver`, and `hosted-git-info` edges cited against 2021–2022 CVEs are checked against npm 7.20, Yarn Berry 3.0.0, and pnpm’s `git-resolver` package.json at the time of each advisory. ↩

nesbitt.io

This Week in Package Management: 8 August 2026 https://nesbitt.io/2026/08/08/this-week-in-package-management.html

This Week in Package Management: 8 August 2026

Week twelve of the roundup, built from the package manager OPML feed collection and whatever I’ve posted or boosted on Mastodon. ## Releases pnpm 11.20 fixes a package-substitution risk in projects with multiple named registries by recording packages under registry-qualified lockfile keys such as `foo@work:1.0.0`. 12.0.0-rc.1, the Rust-engine release candidate, resolves git dependencies on known hosts through the canonical HTTPS URL so the lockfile never records an SSH URL, and refuses global commands run under `sudo`. mise 2026.8.0–8.3 turns `mise bootstrap` into a declarative host-provisioning system: alongside packages it now converges files, users, systemd units, Docker Compose projects and firewall rules, with `mise bootstrap plan` to preview and remote execution over SSH. 8.3 adds a `flatpak-user` package manager and Linux font-cask support. packaging 26.3 adds a `VersionRange` API that represents the versions a specifier set accepts as an interval set with intersection, union and difference operations, plus `is_subset`/`is_superset`/`is_disjoint` on `SpecifierSet`. It also accepts `Metadata-Version: 2.6` per PEP 808, which lets build backends extend list and table `[project]` fields that are also declared in `project.dynamic`. uv 0.12.2 adds a preview `uv tool audit` command that runs the vulnerability audit against installed tools, using the per-tool `uv.lock` files from the `tool-install-locks` preview. It also ships CPython 3.15.0rc1 as a managed interpreter. zizmor 1.29.0 is the first release to audit inputs beyond GitHub Actions: pre-commit configuration files and hook definitions are now supported, starting with a new `insecure-url-scheme` audit and pre-commit coverage in the existing `impostor-commit` and `forbidden-uses` audits. It also recognises GitHub’s new `uses: $/path/to/action` self-repository syntax. A discussion thread is collecting requests for further CI platforms. Dependabot Core 0.390.0 adds security-update support for vcpkg ports, beta support for pnpm 11, and has the `github_actions` updater relock `gh-actions-lock` lockfiles alongside workflow updates. The three-day cooldown default is now unconditional. Verdaccio 6.9.2 applies configured package access controls to the `GET /-/_view/starredByUser` endpoint, which previously returned a user’s starred packages without filtering by what the requesting client is authorised to see. Mamba 2.9.0 adds `--exclude-newer` to filter out packages built after a given timestamp, for reproducing an environment as it would have resolved at a past point in time, and an option to opt out of running link scripts during install. Also out: * RubyGems 4.0.18 * pip 26.2.1 * pipx 1.16.6 * Conan 2.31.2 * Gradle 9.7.0 * Homebrew 6.0.15 * Renovate 44.14.11 * pixi 0.76.1 * npm 11.19.0 * Deno 2.9.5 * Docker 29.7.2 * winget 1.30.90-preview ## Security NuGet.org is reducing the maximum API key lifetime from 365 days to 30 days from 17 August, with all keys created before that date expiring on 1 November. Publishers are pointed at OIDC-based Trusted Publishing, which issues a short-lived key per publish operation against a policy configured by the package owner. npm has restricted 2FA-bypass granular access tokens from token management, package access changes and organisation membership operations, which now require an interactive 2FA challenge. The tokens lose direct publish rights entirely in January 2027, with trusted publishing or staged publishing as the replacement for automated pipelines. sbt 1.12.15 and 2.0.6 fix a remote code execution in the sbt server (GHSA-m2pw-22cj-jq4v) reachable when `Global / serverConnectionType` is set to `Tcp`. The setting defaults to a local Unix domain socket, so only builds that opted into TCP are affected. ## Articles Your composer.lock knows what a carmaker only guesses (Sebastian Bergmann): a car manufacturer’s open-source attribution lists an Android botnet, giving away that the list came from a binary scanner rather than declared dependencies. Bergmann walks through how the PHPUnit PHAR discloses its own contents from `composer.lock` and argues an SBOM built from the lockfile is worth more than one reverse-engineered after the fact. Making RubyGems Guides friendly to humans and AI: guides.rubygems.org now serves `sitemap.xml`, `robots.txt` and per-page plain-text renderings so agents can fetch a guide without navigation and markup, prompted by the Evil Martians Ruby/Rails LLM discoverability scorecard. A Vision for Cargo (Ed Page): a Cargo team member sets out the workflows he wants to improve, covering dependency discovery and audit points in the crates.io ecosystem, build performance, plumbing commands and programmatic APIs for tools built on Cargo, and the state of the Cargo codebase itself. ## Elsewhere The Software Stewardship Lab launched on Thursday, a non-profit applied research lab for open source sustainability that I’m a director of. I’ve written it up separately, and there’s a Sustain podcast episode with executive director Vlad-Stefan Harbuz. The Nixpkgs core team has disbanded. Its two members cite workload incompatible with continued technical contribution and friction with the Steering Committee, which now takes over the team’s responsibilities directly. William Woodruff’s EuroPython 2026 keynote Securing Python for the next decade is online. htmx 4: the game (Seth Larson): htmx 4 shipped as a physical Game Boy cartridge, and the distribution mechanism for the library source is to finish the game and hand-type it from the screen. help wanted (Lake Hope): a maintainer responds to feedback from the community. jj v0.44.0 stabilises tag support: tags can be tracked or untracked like bookmarks, with tracked tags pushed by default. Stylometric Defenses Against Author Impersonation in Software Repositories (Ravich et al., arXiv) builds a patch-level authorship verifier from twenty years of Linux kernel commit history and applies it retroactively to real supply-chain incidents: the 2021 PHP backdoor commits surface within about 1% of the maintainer review queue and the 2026 ForceMemo spoofs at a median 0.8% per-repository review burden. The conda org’s June and July release roundup covers rattler 0.25.0 moving to resolvo 0.11.1 for roughly 40% faster large solves, conda-libmamba-solver 26.7.0 caching prefix records so a 50,000-record environment loads in seconds rather than timing out, and conda-pypi 0.11.0 building a conda channel from local wheel files. OpenAlex is adding research software as a first-class work type in its scholarly graph, funded by a two-year Schmidt Sciences grant. A mention-extraction pipeline over paper full text will link works to the software they cite, with outbound identifiers to package registries, repository URLs, DOIs and Software Heritage, plus a versioning model and per-author software contribution metrics. Who Will Keep Research Data Infrastructure Open and Running? (Jennifer Gibson and Kaitlin Thaney, Issues in Science and Technology): nearly 200 research data repositories have shut down since 2000, more than half of those since 2018, and the piece argues for sustained operational funding of open infrastructure rather than project-based grants. ## git-pkgs I tagged archives v0.5.0, clone v0.2.1, magic v0.2.0, manifests v0.7.0, proxy v0.6.1 and spdx v0.3.0. Send links for next week to @[email protected].

nesbitt.io

I updated my website to the latest oxcaml-5.2.0-minus39 and made some notes about how the opam packaging works https://anil.recoil.org/notes/oxcaml-opam-guards : this might be helpful if you're trying out the performance extensions with your own non-Jane Street codebases

Updating to the OxCaml 5.2.0-minus39 opam packaging

How the OxCaml overlay's guard packages keep incompatible releases out, and how to contribute to it with your own packages.

anil.recoil.org

This Week in Package Management: 8 August 2026 https://nesbitt.io/2026/08/08/this-week-in-package-management.html

This Week in Package Management: 8 August 2026

Week twelve of the roundup, built from the package manager OPML feed collection and whatever I’ve posted or boosted on Mastodon. ## Releases pnpm 11.20 fixes a package-substitution risk in projects with multiple named registries by recording packages under registry-qualified lockfile keys such as `foo@work:1.0.0`. 12.0.0-rc.1, the Rust-engine release candidate, resolves git dependencies on known hosts through the canonical HTTPS URL so the lockfile never records an SSH URL, and refuses global commands run under `sudo`. mise 2026.8.0–8.3 turns `mise bootstrap` into a declarative host-provisioning system: alongside packages it now converges files, users, systemd units, Docker Compose projects and firewall rules, with `mise bootstrap plan` to preview and remote execution over SSH. 8.3 adds a `flatpak-user` package manager and Linux font-cask support. packaging 26.3 adds a `VersionRange` API that represents the versions a specifier set accepts as an interval set with intersection, union and difference operations, plus `is_subset`/`is_superset`/`is_disjoint` on `SpecifierSet`. It also accepts `Metadata-Version: 2.6` per PEP 808, which lets build backends extend list and table `[project]` fields that are also declared in `project.dynamic`. uv 0.12.2 adds a preview `uv tool audit` command that runs the vulnerability audit against installed tools, using the per-tool `uv.lock` files from the `tool-install-locks` preview. It also ships CPython 3.15.0rc1 as a managed interpreter. zizmor 1.29.0 is the first release to audit inputs beyond GitHub Actions: pre-commit configuration files and hook definitions are now supported, starting with a new `insecure-url-scheme` audit and pre-commit coverage in the existing `impostor-commit` and `forbidden-uses` audits. It also recognises GitHub’s new `uses: $/path/to/action` self-repository syntax. A discussion thread is collecting requests for further CI platforms. Dependabot Core 0.390.0 adds security-update support for vcpkg ports, beta support for pnpm 11, and has the `github_actions` updater relock `gh-actions-lock` lockfiles alongside workflow updates. The three-day cooldown default is now unconditional. Verdaccio 6.9.2 applies configured package access controls to the `GET /-/_view/starredByUser` endpoint, which previously returned a user’s starred packages without filtering by what the requesting client is authorised to see. Mamba 2.9.0 adds `--exclude-newer` to filter out packages built after a given timestamp, for reproducing an environment as it would have resolved at a past point in time, and an option to opt out of running link scripts during install. Also out: * RubyGems 4.0.18 * pip 26.2.1 * pipx 1.16.6 * Conan 2.31.2 * Gradle 9.7.0 * Homebrew 6.0.15 * Renovate 44.14.11 * pixi 0.76.1 * npm 11.19.0 * Deno 2.9.5 * Docker 29.7.2 * winget 1.30.90-preview ## Security NuGet.org is reducing the maximum API key lifetime from 365 days to 30 days from 17 August, with all keys created before that date expiring on 1 November. Publishers are pointed at OIDC-based Trusted Publishing, which issues a short-lived key per publish operation against a policy configured by the package owner. npm has restricted 2FA-bypass granular access tokens from token management, package access changes and organisation membership operations, which now require an interactive 2FA challenge. The tokens lose direct publish rights entirely in January 2027, with trusted publishing or staged publishing as the replacement for automated pipelines. sbt 1.12.15 and 2.0.6 fix a remote code execution in the sbt server (GHSA-m2pw-22cj-jq4v) reachable when `Global / serverConnectionType` is set to `Tcp`. The setting defaults to a local Unix domain socket, so only builds that opted into TCP are affected. ## Articles Your composer.lock knows what a carmaker only guesses (Sebastian Bergmann): a car manufacturer’s open-source attribution lists an Android botnet, giving away that the list came from a binary scanner rather than declared dependencies. Bergmann walks through how the PHPUnit PHAR discloses its own contents from `composer.lock` and argues an SBOM built from the lockfile is worth more than one reverse-engineered after the fact. Making RubyGems Guides friendly to humans and AI: guides.rubygems.org now serves `sitemap.xml`, `robots.txt` and per-page plain-text renderings so agents can fetch a guide without navigation and markup, prompted by the Evil Martians Ruby/Rails LLM discoverability scorecard. A Vision for Cargo (Ed Page): a Cargo team member sets out the workflows he wants to improve, covering dependency discovery and audit points in the crates.io ecosystem, build performance, plumbing commands and programmatic APIs for tools built on Cargo, and the state of the Cargo codebase itself. ## Elsewhere The Software Stewardship Lab launched on Thursday, a non-profit applied research lab for open source sustainability that I’m a director of. I’ve written it up separately, and there’s a Sustain podcast episode with executive director Vlad-Stefan Harbuz. The Nixpkgs core team has disbanded. Its two members cite workload incompatible with continued technical contribution and friction with the Steering Committee, which now takes over the team’s responsibilities directly. William Woodruff’s EuroPython 2026 keynote Securing Python for the next decade is online. htmx 4: the game (Seth Larson): htmx 4 shipped as a physical Game Boy cartridge, and the distribution mechanism for the library source is to finish the game and hand-type it from the screen. help wanted (Lake Hope): a maintainer responds to feedback from the community. jj v0.44.0 stabilises tag support: tags can be tracked or untracked like bookmarks, with tracked tags pushed by default. Stylometric Defenses Against Author Impersonation in Software Repositories (Ravich et al., arXiv) builds a patch-level authorship verifier from twenty years of Linux kernel commit history and applies it retroactively to real supply-chain incidents: the 2021 PHP backdoor commits surface within about 1% of the maintainer review queue and the 2026 ForceMemo spoofs at a median 0.8% per-repository review burden. The conda org’s June and July release roundup covers rattler 0.25.0 moving to resolvo 0.11.1 for roughly 40% faster large solves, conda-libmamba-solver 26.7.0 caching prefix records so a 50,000-record environment loads in seconds rather than timing out, and conda-pypi 0.11.0 building a conda channel from local wheel files. OpenAlex is adding research software as a first-class work type in its scholarly graph, funded by a two-year Schmidt Sciences grant. A mention-extraction pipeline over paper full text will link works to the software they cite, with outbound identifiers to package registries, repository URLs, DOIs and Software Heritage, plus a versioning model and per-author software contribution metrics. Who Will Keep Research Data Infrastructure Open and Running? (Jennifer Gibson and Kaitlin Thaney, Issues in Science and Technology): nearly 200 research data repositories have shut down since 2000, more than half of those since 2018, and the piece argues for sustained operational funding of open infrastructure rather than project-based grants. ## git-pkgs I tagged archives v0.5.0, clone v0.2.1, magic v0.2.0, manifests v0.7.0, proxy v0.6.1 and spdx v0.3.0. Send links for next week to @[email protected].

nesbitt.io

The Software Stewardship Lab https://nesbitt.io/2026/08/07/the-software-stewardship-lab.html

The Software Stewardship Lab

The Software Stewardship Lab launches today, a Scottish non-profit set up to do applied research on the open source ecosystem, and I’m one of its directors. Vlad-Stefan Harbuz, who runs the Open Source Pledge, is the executive director and did the bulk of the work getting it incorporated. I’ve been trying to do this kind of research for about a decade, starting with Libraries.io in 2015 as a side project indexing every package registry I could find, which was acquired when the hosting bills outgrew what I could cover on my own. ecosyste.ms is the second attempt at the same idea, kept running by a sequence of grants and contracts that each cover a year or two of server costs, with most of my own time on it either unpaid or subsidised by other contracting work. The organisations set up to address open source sustainability have the same funding problem as the maintainers they are trying to help, often a worse version of it because a maintainer at least produces software that people want to use, and almost everyone I know doing this kind of research is patching together an income the same way I am. Grant programmes fund a project for a fixed term and then stop, which is also roughly how the academic contracts work for researchers studying maintainer burnout, while the tools built to measure the ecosystem need servers and bandwidth that someone has to keep paying for year after year regardless. The funding pattern also pushes the research towards one-off snapshots rather than systems that keep collecting and reporting, so each new grant redoes much of the same data collection from scratch, and a large part of why ecosyste.ms exists is to be a continuously running layer underneath that later work can build on. I’ve watched a fair number of good efforts wind down when a grant ended or a sponsor changed priorities, and the questions they were working on are all still open. The Lab is an attempt to give that work an institutional home with a horizon longer than a single grant cycle, structured as a research lab that contracts researchers, publishes open-access papers and open datasets, and builds tools that stay open source, on the basis that instruments for monitoring critical infrastructure are themselves critical infrastructure and shouldn’t sit behind a licence fee. Work is also under way with solicitors to register it as a charity, which if granted would make it the first UK charity working on the sustainability of the open source ecosystem, and would mean a regulator accepting that as a charitable purpose in its own right. The initial research themes are supply chain security, maintainer wellbeing, funding models, and governance, which overlap heavily with what I’ve been writing about here for the past year, and a chunk of that writing is already syndicated on the Lab’s site alongside work from the others. I’ve been reading and citing the rest of the board for years, which is a large part of why I said yes: Miranda Heath, a psychologist at the University of Edinburgh, wrote the most thorough study of burnout in open source I’ve come across, and Dawn Foster brings a PhD in software metrics along with board seats at CHAOSS and OpenUK. Daniel Roe leads Nuxt, Matias Capeletto is a core Vite developer, and the two of them are behind npmx, which I wrote about back in April, so the board includes people who maintain a sizeable share of the modern JavaScript toolchain and know first-hand what being on the receiving end of sustainability advice is like. Mike McQuaid, who I worked with at GitHub and now maintain Homebrew alongside, is advising, and I’m lucky to be working with that group on this. The stated long-term goal in the pitch document is to raise enough recurring funding to offer open-ended stipends to people doing this research, so the work can outlast whichever grant happens to be paying for it in a given year, and after a decade without anything like that available it’s the part I most want to see exist. That funding will come from company sponsorships and individual contributions through Open Collective, and there’s a Discord open to anyone working on these problems who wants to compare notes.

nesbitt.io

The Software Stewardship Lab https://nesbitt.io/2026/08/07/the-software-stewardship-lab.html

The Software Stewardship Lab

The Software Stewardship Lab launches today, a Scottish non-profit set up to do applied research on the open source ecosystem, and I’m one of its directors. Vlad-Stefan Harbuz, who runs the Open Source Pledge, is the executive director and did the bulk of the work getting it incorporated. I’ve been trying to do this kind of research for about a decade, starting with Libraries.io in 2015 as a side project indexing every package registry I could find, which was acquired when the hosting bills outgrew what I could cover on my own. ecosyste.ms is the second attempt at the same idea, kept running by a sequence of grants and contracts that each cover a year or two of server costs, with most of my own time on it either unpaid or subsidised by other contracting work. The organisations set up to address open source sustainability have the same funding problem as the maintainers they are trying to help, often a worse version of it because a maintainer at least produces software that people want to use, and almost everyone I know doing this kind of research is patching together an income the same way I am. Grant programmes fund a project for a fixed term and then stop, which is also roughly how the academic contracts work for researchers studying maintainer burnout, while the tools built to measure the ecosystem need servers and bandwidth that someone has to keep paying for year after year regardless. The funding pattern also pushes the research towards one-off snapshots rather than systems that keep collecting and reporting, so each new grant redoes much of the same data collection from scratch, and a large part of why ecosyste.ms exists is to be a continuously running layer underneath that later work can build on. I’ve watched a fair number of good efforts wind down when a grant ended or a sponsor changed priorities, and the questions they were working on are all still open. The Lab is an attempt to give that work an institutional home with a horizon longer than a single grant cycle, structured as a research lab that contracts researchers, publishes open-access papers and open datasets, and builds tools that stay open source, on the basis that instruments for monitoring critical infrastructure are themselves critical infrastructure and shouldn’t sit behind a licence fee. Work is also under way with solicitors to register it as a charity, which if granted would make it the first UK charity working on the sustainability of the open source ecosystem, and would mean a regulator accepting that as a charitable purpose in its own right. The initial research themes are supply chain security, maintainer wellbeing, funding models, and governance, which overlap heavily with what I’ve been writing about here for the past year, and a chunk of that writing is already syndicated on the Lab’s site alongside work from the others. I’ve been reading and citing the rest of the board for years, which is a large part of why I said yes: Miranda Heath, a psychologist at the University of Edinburgh, wrote the most thorough study of burnout in open source I’ve come across, and Dawn Foster brings a PhD in software metrics along with board seats at CHAOSS and OpenUK. Daniel Roe leads Nuxt, Matias Capeletto is a core Vite developer, and the two of them are behind npmx, which I wrote about back in April, so the board includes people who maintain a sizeable share of the modern JavaScript toolchain and know first-hand what being on the receiving end of sustainability advice is like. Mike McQuaid, who I worked with at GitHub and now maintain Homebrew alongside, is advising, and I’m lucky to be working with that group on this. The stated long-term goal in the pitch document is to raise enough recurring funding to offer open-ended stipends to people doing this research, so the work can outlast whichever grant happens to be paying for it in a given year, and after a decade without anything like that available it’s the part I most want to see exist. That funding will come from company sponsorships and individual contributions through Open Collective, and there’s a Discord open to anyone working on these problems who wants to compare notes.

nesbitt.io

A year of AI disclosure in critical packages: https://nesbitt.io/2026/08/06/a-year-of-ai-disclosure-in-critical-packages.html

A year of AI disclosure in critical packages

Stephen O’Grady’s RedMonk analysis of who is writing open source code looked at commits to fifteen large projects during the first half of 2026 and counted two forms of declared AI involvement: a known autonomous agent as the commit author, or a known AI identity in a `Co-Authored-By` trailer. The result was under one percent, framed as a floor. I ran a wider version of the same measurement over the packages.ecosyste.ms critical set: 5,682 GitHub repositories behind the most-depended-on packages across sixteen registries, using the CHAOSS disclosure library to detect four kinds of explicit signal instead of two. Over the same six months the rate was 4.13%. Over the year ending 29 July 2026 it was 2.93% (17,279 of 589,798 non-merge commits), rising from 0.48% last August to 5.32% this July. These are counts of commits where someone left an explicit marker in git metadata. Undeclared use is not measured, and a commit is one unit regardless of whether it changed one line or ten thousand. ## Sample selection versus detector choice Running my scanner against RedMonk’s fifteen repositories with only their two signals found 94 matches in 23,346 first-half commits including merges, or 0.40%, against RedMonk’s “~24K commits” and a match count “in the dozens”. Excluding merges leaves 17,323 commits and the same 94 matches, or 0.54%; `espressif/esp-idf` and `openssl/openssl` supply 71 of them, matching RedMonk’s reported 73% concentration in two projects. sample and signals | commits | marked | share ---|---|---|--- RedMonk 15, agent author or known AI co-author | 17,323 | 94 | 0.54% RedMonk 15, all validated disclosure signals | 17,323 | 182 | 1.05% Critical GitHub set, agent author or known AI co-author | 308,354 | 11,002 | 3.57% Critical GitHub set, all validated disclosure signals | 308,354 | 12,720 | 4.13% Adding the two extra signal types moved the rate by about half a percentage point on either sample. Changing the sample moved it by three points. RedMonk’s fifteen were chosen by contributor-base size with, in O’Grady’s words, a deliberate bias towards C; the critical package set is whatever sits at the top of each registry’s dependency graph, which pulls in a lot of smaller, newer, company-run repositories. ## What I counted The critical snapshot contained 8,605 packages, with repository URLs and metadata pulled from the same package cache I built for Weekend at Bernie’s. Merging packages that share a repository, following renames, restricting to GitHub, and dropping malformed URLs left 5,707 candidates. 5,682 cloned successfully; the other 25 were deleted or private. 3,533 had at least one non-merge commit in the year ending 29 July 2026. Each repository was cloned bare with a tree filter and a shallow date boundary, streamed through the disclosure library, and deleted. The full pass transferred about 1 GB and the retained checkpoint is 16 MB of per-repository summaries and matched commit SHAs. Rename following checks GitHub’s stable repository ID as well as the redirect. The npm package `base` still lists `node-base/base` as its repository. GitHub reused the org name, so that path now redirects to the Base blockchain monorepo, which would have contributed 3,135 commits and 273 AI signals to a nine-year-old npm utility. The ID check excluded it. Every non-merge commit was checked for: * a known AI agent as author or committer * a known AI identity in `Co-Authored-By` * an `Assisted-By` trailer naming an AI tool or model * a tool-specific attribution format that disclosure supports Merges are excluded so projects that squash, rebase, or merge count on the same basis. Commits are bucketed by committer time, when the change landed on the current branch. Mentions of tool names in ordinary commit prose are ignored. `Assisted-By` values are validated because the trailer is also used for people: raw matches included `Assisted-By: Daniel Stenberg` and `Assisted-By: Automated Tooling, Human Reviewed.` The clones did not fetch `refs/notes/ai`, so declarations recorded as git notes are absent. ## Over the year The monthly rate passed 3% in February and 5% in March, then held between 4.58% and 5.32% through July. Counting repositories instead of commits, a signal appeared in 41 of the 1,734 repositories with commits in August 2025 (2.4%) and 276 of 1,793 in July 2026 (15.4%). Of the 17,279 findings, 4,625 carry only an autonomous-agent identity, 12,628 carry only a declared-assistance signal, and 26 carry both. Declared assistance went from 0.08% of commits in August to 4.92% in July. Agent authorship started at 0.40% and ended at 0.41%, peaking at 1.33% in between; 4,613 of those commits have GitHub Copilot’s agent as author, 38 have Devin’s, and Claude, Cursor, Codex, and Amazon Q account for 25 between them. Copilot agent commits reached 745 in March across 85 repositories and fell to 208 across 35 in July, with individual projects running the agent in short bursts: `pycqa/isort` had 49 in March and none after, `azure/azure-sdk-for-net` had 275 in February and 28 in March. The February and March step in the total is Claude Code `Co-Authored-By` trailers. Those went from 97 commits in December to 325, 753, and 2,037 over the following three months, and from 39 distinct repositories to 190. Cursor’s co-author trailers rose from 1 to 48 over the same months and Copilot’s from 1 to 2, so the step is specific to one tool rather than a general change in disclosure practice. Anthropic released Claude Opus 4.6 on 5 February and Sonnet 4.6 on 17 February; March is the first full month with both available. The findings carry 231 distinct declared tool strings across 17,392 occurrences. Grouping them by client family, and separately by model or provider where no client is named: declared as | occurrences | share ---|---|--- Claude Code | 9,974 | 57.35% GitHub Copilot | 4,857 | 27.93% Cursor | 773 | 4.44% Codex | 236 | 1.36% OpenCode | 69 | 0.40% Claude or Anthropic (model only) | 1,135 | 6.53% OpenAI or GPT (model only) | 118 | 0.68% Gemini or Google (model only) | 70 | 0.40% The raw declared strings are in the summary JSON; the grouping is mine and a value naming two clients counts in both. At the repository level, 687 of the 3,533 active repositories recorded at least one signal over the year, so the median active repository’s rate is zero. The ten repositories with the most findings account for 40.8% of the total and the top hundred for 84.9%. ## By ecosystem `go-git` shows what the extra detectors add in one repository: 269 of its 731 commits carry a validated signal, 12 of which match RedMonk’s narrow rules. The rest are `Assisted-By` trailers and tool attributions. Nine of the sixteen ecosystems had at least 30,000 commits in the window: package ecosystem | commits | validated share | repositories | with instructions ---|---|---|---|--- NuGet | 41,523 | 6.84% | 74 | 40.54% npm | 87,357 | 3.72% | 1,578 | 3.11% RubyGems | 40,889 | 3.59% | 670 | 6.12% Conda | 144,227 | 3.31% | 264 | 12.12% Go | 34,117 | 3.07% | 545 | 5.87% PyPI | 124,641 | 2.57% | 451 | 12.42% Cargo | 30,620 | 1.96% | 570 | 2.46% Packagist | 37,267 | 1.73% | 547 | 10.24% Maven | 100,539 | 1.60% | 273 | 16.12% The other seven, from CocoaPods at 13,581 commits down to Julia at 682, are in the summary JSON. Julia’s 51 findings in 682 commits give it the highest rate in the set at 7.47%, on the smallest sample. NuGet’s 6.84% is a Microsoft deployment. Repositories under `aspnet`, `azure`, `azuread`, `dotnet`, `microsoft`, and `nuget` supplied 2,716 of the 2,842 NuGet findings (95.6%), and 2,634 of those are autonomous-agent identities. Remove those owners and NuGet falls to 126 findings in 14,283 commits, or 0.88%, below Maven. I have only run that owner exclusion for NuGet; the per-repository CSV has what’s needed to do it for the others. ## Instruction files A separate pass over the same 5,682 default-branch heads checked for committed instructions to coding agents: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and the documented Copilot, Cursor, Cline, Windsurf, and Continue rule paths. 353 repositories (6.21%) have at least one, holding 1,091 files between them. A file’s presence records that someone set up guidance for an agent; it attributes nothing to any commit. instruction type | repositories | share of scanned repositories | files ---|---|---|--- `AGENTS.md` | 240 | 4.22% | 571 `CLAUDE.md` | 204 | 3.59% | 340 GitHub Copilot instructions | 84 | 1.48% | 154 Cursor rules | 11 | 0.19% | 14 `GEMINI.md` | 8 | 0.14% | 8 Cline rules | 1 | 0.02% | 4 Windsurf rules | 0 | 0% | 0 Continue rules | 0 | 0% | 0 Each repository is counted once, in the month its earliest surviving instruction file was added, so Cypress with 118 files counts the same as a project with one. Only files present on current heads are visible, so anything added and later deleted is absent from the timeline. 228 of the 353 repositories also have a disclosed commit in the year. 94 of those added their earliest instruction file before their first disclosed commit, 98 added it after, and 36 on the same day; the median gap is zero. The other 125 have an instruction file and no disclosed commit in the window. ## Data The scanner and report generator are at andrew/critical-ai-scan. The summary JSON has the overall, monthly, ecosystem, signal, tool, and leading-repository counts. The repository CSV has one row per successful scan with the exact default-branch head used, so individual cases can be checked without recloning. The instruction-file report lists every matched path with its category and the commit that added it.

nesbitt.io

A year of AI disclosure in critical packages: https://nesbitt.io/2026/08/06/a-year-of-ai-disclosure-in-critical-packages.html

A year of AI disclosure in critical packages

Stephen O’Grady’s RedMonk analysis of who is writing open source code looked at commits to fifteen large projects during the first half of 2026 and counted two forms of declared AI involvement: a known autonomous agent as the commit author, or a known AI identity in a `Co-Authored-By` trailer. The result was under one percent, framed as a floor. I ran a wider version of the same measurement over the packages.ecosyste.ms critical set: 5,682 GitHub repositories behind the most-depended-on packages across sixteen registries, using the CHAOSS disclosure library to detect four kinds of explicit signal instead of two. Over the same six months the rate was 4.13%. Over the year ending 29 July 2026 it was 2.93% (17,279 of 589,798 non-merge commits), rising from 0.48% last August to 5.32% this July. These are counts of commits where someone left an explicit marker in git metadata. Undeclared use is not measured, and a commit is one unit regardless of whether it changed one line or ten thousand. ## Sample selection versus detector choice Running my scanner against RedMonk’s fifteen repositories with only their two signals found 94 matches in 23,346 first-half commits including merges, or 0.40%, against RedMonk’s “~24K commits” and a match count “in the dozens”. Excluding merges leaves 17,323 commits and the same 94 matches, or 0.54%; `espressif/esp-idf` and `openssl/openssl` supply 71 of them, matching RedMonk’s reported 73% concentration in two projects. sample and signals | commits | marked | share ---|---|---|--- RedMonk 15, agent author or known AI co-author | 17,323 | 94 | 0.54% RedMonk 15, all validated disclosure signals | 17,323 | 182 | 1.05% Critical GitHub set, agent author or known AI co-author | 308,354 | 11,002 | 3.57% Critical GitHub set, all validated disclosure signals | 308,354 | 12,720 | 4.13% Adding the two extra signal types moved the rate by about half a percentage point on either sample. Changing the sample moved it by three points. RedMonk’s fifteen were chosen by contributor-base size with, in O’Grady’s words, a deliberate bias towards C; the critical package set is whatever sits at the top of each registry’s dependency graph, which pulls in a lot of smaller, newer, company-run repositories. ## What I counted The critical snapshot contained 8,605 packages, with repository URLs and metadata pulled from the same package cache I built for Weekend at Bernie’s. Merging packages that share a repository, following renames, restricting to GitHub, and dropping malformed URLs left 5,707 candidates. 5,682 cloned successfully; the other 25 were deleted or private. 3,533 had at least one non-merge commit in the year ending 29 July 2026. Each repository was cloned bare with a tree filter and a shallow date boundary, streamed through the disclosure library, and deleted. The full pass transferred about 1 GB and the retained checkpoint is 16 MB of per-repository summaries and matched commit SHAs. Rename following checks GitHub’s stable repository ID as well as the redirect. The npm package `base` still lists `node-base/base` as its repository. GitHub reused the org name, so that path now redirects to the Base blockchain monorepo, which would have contributed 3,135 commits and 273 AI signals to a nine-year-old npm utility. The ID check excluded it. Every non-merge commit was checked for: * a known AI agent as author or committer * a known AI identity in `Co-Authored-By` * an `Assisted-By` trailer naming an AI tool or model * a tool-specific attribution format that disclosure supports Merges are excluded so projects that squash, rebase, or merge count on the same basis. Commits are bucketed by committer time, when the change landed on the current branch. Mentions of tool names in ordinary commit prose are ignored. `Assisted-By` values are validated because the trailer is also used for people: raw matches included `Assisted-By: Daniel Stenberg` and `Assisted-By: Automated Tooling, Human Reviewed.` The clones did not fetch `refs/notes/ai`, so declarations recorded as git notes are absent. ## Over the year The monthly rate passed 3% in February and 5% in March, then held between 4.58% and 5.32% through July. Counting repositories instead of commits, a signal appeared in 41 of the 1,734 repositories with commits in August 2025 (2.4%) and 276 of 1,793 in July 2026 (15.4%). Of the 17,279 findings, 4,625 carry only an autonomous-agent identity, 12,628 carry only a declared-assistance signal, and 26 carry both. Declared assistance went from 0.08% of commits in August to 4.92% in July. Agent authorship started at 0.40% and ended at 0.41%, peaking at 1.33% in between; 4,613 of those commits have GitHub Copilot’s agent as author, 38 have Devin’s, and Claude, Cursor, Codex, and Amazon Q account for 25 between them. Copilot agent commits reached 745 in March across 85 repositories and fell to 208 across 35 in July, with individual projects running the agent in short bursts: `pycqa/isort` had 49 in March and none after, `azure/azure-sdk-for-net` had 275 in February and 28 in March. The February and March step in the total is Claude Code `Co-Authored-By` trailers. Those went from 97 commits in December to 325, 753, and 2,037 over the following three months, and from 39 distinct repositories to 190. Cursor’s co-author trailers rose from 1 to 48 over the same months and Copilot’s from 1 to 2, so the step is specific to one tool rather than a general change in disclosure practice. Anthropic released Claude Opus 4.6 on 5 February and Sonnet 4.6 on 17 February; March is the first full month with both available. The findings carry 231 distinct declared tool strings across 17,392 occurrences. Grouping them by client family, and separately by model or provider where no client is named: declared as | occurrences | share ---|---|--- Claude Code | 9,974 | 57.35% GitHub Copilot | 4,857 | 27.93% Cursor | 773 | 4.44% Codex | 236 | 1.36% OpenCode | 69 | 0.40% Claude or Anthropic (model only) | 1,135 | 6.53% OpenAI or GPT (model only) | 118 | 0.68% Gemini or Google (model only) | 70 | 0.40% The raw declared strings are in the summary JSON; the grouping is mine and a value naming two clients counts in both. At the repository level, 687 of the 3,533 active repositories recorded at least one signal over the year, so the median active repository’s rate is zero. The ten repositories with the most findings account for 40.8% of the total and the top hundred for 84.9%. ## By ecosystem `go-git` shows what the extra detectors add in one repository: 269 of its 731 commits carry a validated signal, 12 of which match RedMonk’s narrow rules. The rest are `Assisted-By` trailers and tool attributions. Nine of the sixteen ecosystems had at least 30,000 commits in the window: package ecosystem | commits | validated share | repositories | with instructions ---|---|---|---|--- NuGet | 41,523 | 6.84% | 74 | 40.54% npm | 87,357 | 3.72% | 1,578 | 3.11% RubyGems | 40,889 | 3.59% | 670 | 6.12% Conda | 144,227 | 3.31% | 264 | 12.12% Go | 34,117 | 3.07% | 545 | 5.87% PyPI | 124,641 | 2.57% | 451 | 12.42% Cargo | 30,620 | 1.96% | 570 | 2.46% Packagist | 37,267 | 1.73% | 547 | 10.24% Maven | 100,539 | 1.60% | 273 | 16.12% The other seven, from CocoaPods at 13,581 commits down to Julia at 682, are in the summary JSON. Julia’s 51 findings in 682 commits give it the highest rate in the set at 7.47%, on the smallest sample. NuGet’s 6.84% is a Microsoft deployment. Repositories under `aspnet`, `azure`, `azuread`, `dotnet`, `microsoft`, and `nuget` supplied 2,716 of the 2,842 NuGet findings (95.6%), and 2,634 of those are autonomous-agent identities. Remove those owners and NuGet falls to 126 findings in 14,283 commits, or 0.88%, below Maven. I have only run that owner exclusion for NuGet; the per-repository CSV has what’s needed to do it for the others. ## Instruction files A separate pass over the same 5,682 default-branch heads checked for committed instructions to coding agents: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and the documented Copilot, Cursor, Cline, Windsurf, and Continue rule paths. 353 repositories (6.21%) have at least one, holding 1,091 files between them. A file’s presence records that someone set up guidance for an agent; it attributes nothing to any commit. instruction type | repositories | share of scanned repositories | files ---|---|---|--- `AGENTS.md` | 240 | 4.22% | 571 `CLAUDE.md` | 204 | 3.59% | 340 GitHub Copilot instructions | 84 | 1.48% | 154 Cursor rules | 11 | 0.19% | 14 `GEMINI.md` | 8 | 0.14% | 8 Cline rules | 1 | 0.02% | 4 Windsurf rules | 0 | 0% | 0 Continue rules | 0 | 0% | 0 Each repository is counted once, in the month its earliest surviving instruction file was added, so Cypress with 118 files counts the same as a project with one. Only files present on current heads are visible, so anything added and later deleted is absent from the timeline. 228 of the 353 repositories also have a disclosed commit in the year. 94 of those added their earliest instruction file before their first disclosed commit, 98 added it after, and 36 on the same day; the median gap is zero. The other 125 have an instruction file and no disclosed commit in the window. ## Data The scanner and report generator are at andrew/critical-ai-scan. The summary JSON has the overall, monthly, ecosystem, signal, tool, and leading-repository counts. The repository CSV has one row per successful scan with the exact default-branch head used, so individual cases can be checked without recloning. The instruction-file report lists every matched path with its category and the commit that added it.

nesbitt.io

brew install actions/checkout - Using Homebrew's tap machinery as a curated distribution layer for GitHub Actions. https://nesbitt.io/2026/08/04/brew-install-actions-checkout.html

brew install actions/checkout

In December I went through why `uses:` is a package manager with no lockfile, no integrity hashes and no transitive visibility, and in April through the run of incidents that followed from that. GitHub’s 2026 security roadmap has since committed to a lockfile, now in preview as `gh-actions-lock`, and made immutable actions the preferred resolution path. Neither of those changes adds any review between an action author tagging a release and the runner executing it. Homebrew has run that kind of curated index for fifteen years and, as of the immutable-actions rollout, stores its artifacts as OCI manifests on ghcr.io alongside the actions themselves, so I spent some time working out how much of a GitHub Actions registry you could assemble from Homebrew parts. ### Shared storage Immutable actions and Homebrew bottles are both OCI artifacts on ghcr.io: `actions/publish-immutable-action` tars the action directory, pushes it as a layer with `artifactType: application/vnd.github.actions.package.v1+json`, attaches a sigstore bundle through the OCI referrers API, and tags the manifest with the semver, after which a workflow referencing `actions/[email protected]` resolves through `pkg.actions.githubusercontent.com` instead of the git tarball. `brew pr-pull` pushes bottles under the same manifest schema at `ghcr.io/homebrew/core/<name>` with a `com.github.package.type: homebrew_bottle` annotation and a sigstore attestation that `brew verify` checks against Homebrew’s CI identity, so `crane manifest ghcr.io/homebrew/core/jq:1.7.1` and `crane manifest ghcr.io/actions/checkout:4.2.2` return the same document type, as you’d expect from last week’s post. The comparison table in the December post marked Actions ✗ on integrity hashes, transitive visibility, dependency-tree inspection and immutable versions, and homebrew-core provides all four for its 8,400 formulae through the index rather than the storage: each formula pins a source URL to a sha256, declares dependencies that `brew deps --tree` can walk, passes `brew audit` and human review on every change, gets autobumped by `livecheck` when upstream tags a release, and can carry `deprecate!` or `disable!` when it shouldn’t be installed. ### A tap of actions The index can be a tap, with each formula pinning an action tarball by SHA-256: class ActionsCheckout < Formula desc "Checks out a repository for a GitHub Actions workflow" homepage "https://github.com/actions/checkout" url "https://github.com/actions/checkout/archive/refs/tags/v4.2.2.tar.gz" sha256 "63e9c07ff6c9ddf3a3b39d30e59f0bf3a..." license "MIT" livecheck do url :stable strategy :github_latest end def install prefix.install Dir.children(".") end end For a JavaScript action that ships a built `dist/` in its release tarball, that’s sufficient: the tarball is pinned to a content hash, `brew audit` and `brew verify` apply as they would to any formula, and `brew bump-formula-pr` opens a reviewed PR when checkout tags v4.2.3. Everything above `def install` is already static data. Homebrew is in the middle of migrating install hooks to declarative steps so that bottle and cask installs need no Ruby evaluation at all, at which point an actions tap could be `.json` files with no code execution on install. The transitive problem is specific to composite actions, whose `action.yml` carries its own `uses:` lines that the runner re-resolves at execution time regardless of how the outer action was pinned. In a formula those become `depends_on` entries plus an `inreplace` at build time. For a composite that internally calls `actions/cache@v4`: depends_on "actions-cache" def install inreplace "action.yml", "uses: actions/cache@v4", "uses: ./.brew-actions/actions-cache" prefix.install Dir.children(".") end The resulting bottle has no floating refs left in it, `brew deps --tree` prints the transitive graph that no runner command exposes today, and the tap’s git log records which `actions-cache` revision the composite was built against. Moving that pin requires a reviewed PR; an action author cannot change it with `git tag -f` in someone else’s repository. Every incident in the weakest-link post would have required a reviewed change to that index before reaching downstream users, where an npm-style per-project lockfile would only have reduced the number of downstream repositories exposed. A workflow that pins `@v4` today has already delegated the version decision to whoever can push a tag to the action repo, and a tap moves that delegation to a reviewer instead. It also matches Homebrew’s rolling-release design, where `Brewfile.lock.json` was removed in November 2024 and per-project pinning is currently out of scope. I’d like to see the lockfile come back this year, and until it does a workflow that needs stricter reproducibility than the tap’s HEAD can pin the tap itself to a commit. An `audit_formula` extension for the tap would run zizmor over the extracted `action.yml` and reject anything that trips `dangerous-triggers` or `template-injection`, and reject composites whose internal `uses:` lines aren’t fully covered by `depends_on`. The Marketplace’s “verified creator” badge checks the publisher’s identity and nothing about the action’s contents, so a static-analysis gate at index time would be new. Bottles built from the tap are attested by the tap’s CI the same way homebrew-core bottles are. Each formula’s `url` points at a GitHub repository, which is the input `brew vulns` already keys OSV lookups on, so an advisory against `actions/download-artifact` surfaces through the same path as one against `openssl`. ### Getting the runner to use it The runner has three `ActionSourceType` values in `ActionStepDefinitionReference.cs` (repository, container registry, script) and none of them is “an installed package on disk”, so consuming a Homebrew-installed action means picking one of three integration points at increasing cost. The runner accepts `uses: ./path/to/action` relative to `$GITHUB_WORKSPACE`. The prototype tap at andrew/homebrew-actions packages `actions/checkout`, `actions/cache`, `pre-commit/action` and `actions/first-interaction`. Its setup action runs `brew bundle --file .github/Actionfile` and copies each keg into `./.brew-actions/<name>`, which is where the formula’s `inreplace` above pointed the composite’s dependency. The Actionfile is a normal Brewfile: tap "andrew/actions" brew "andrew/actions/pre-commit-action" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - uses: andrew/homebrew-actions@7def8ee0f83dbb7850e9029c9e0e6ccbafdd209e - uses: ./.brew-actions/pre-commit-action The four formulae pass `brew audit`, install and test on hosted macOS and Ubuntu runners, and the integration job runs the copied `pre-commit/action` composite through to its rewritten `actions/cache` step. It still costs one round-trip to fetch the setup action itself the old way, and `checkout` stays on a plain SHA pin because it runs before the workspace has anything in it to `uses: ./` from. Because `./` is anchored at `$GITHUB_WORKSPACE`, the setup action has to copy each keg there and `checkout` has to run first. Runner 2.336.0 added a `$/` prefix that anchors at the repository containing the defining file, resolved at the running commit: in a workflow `$/` is readable before any step has run, and it’s also valid for reusable workflows (`uses: $/.github/workflows/foo.yml`), which `./` never supported. `gh-actions-lock` rewrites existing `./` references to `$/` by default and treats the result as inherently pinned, so no lockfile entry is generated for it. Inside a composite loaded via `uses: ./path`, though, `$/` still resolves against the workflow’s repository rather than the copied directory: an earlier iteration of the prototype rewrote the composite’s `uses:` to `$/../actions-cache` and the runner attempted to fetch `andrew/homebrew-actions/../actions-cache@<sha>`. So the setup step can’t use `$/` to point at `$(brew --prefix)/opt`, and the `.brew-actions` destination has to be baked into the formula’s `inreplace`. On self-hosted runners, `ActionManager.cs` reads `ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE` and, if `<owner>_<repo>/<resolved-sha>.tar.gz` exists there, skips the download. A `brew bundle` on the runner host could populate that directory from the tap. GitHub’s server-side resolver has already chosen the SHA used as the cache key, so this only controls where its bytes come from. It provides an offline mirror but cannot override `@v4` resolution. A fourth `ActionSourceType` could read a formula’s JSON from `formulae.brew.sh` (or any tap’s API endpoint), verify the bottle attestation, and extract to `_actions/`. Hosted runners resolve refs to tarballs through the server-side `ResolveActionsDownloadInfoAsync` call, so a client-side patch would affect self-hosted runners and compatible forks such as act and Forgejo’s runner, with no GitHub backend changes. `$/` covers the same-repo case. The setup-step option would also need a similar anchor rooted in a runner-side directory outside any repository. Reusable workflows referenced across repositories (`uses: org/repo/.github/workflows/foo.yml@ref`) go through a different loader and have no local-path form even with `$/`, so the setup step cannot load them. They require the runner patch. Docker actions already resolve through a container registry and get whatever pinning the image reference carries. ### Built in GitHub could integrate the tap into its resolver and leave `uses: actions/checkout@v4` unchanged. An organisation or repository setting alongside the existing action allowlists would name the index the resolver consults, and that index would map `actions/checkout` to a bottle manifest digest instead of a git ref. The runner would pull the layer from ghcr.io, verify the attestation against the tap’s CI identity, and extract to `_actions/`. Because a composite’s internal refs were rewritten at bottling time, the index response could carry the resolved dependency closure, making the whole transitive tree one index query plus N content-addressed blob fetches. Re-running last week’s job against an unchanged tap commit would produce identical bytes. Switching an organisation from GitHub’s default index to a community tap or an internal one with stricter audit rules would be a settings change, roughly the choice apt users make between Debian stable and a private mirror. An alternative to a settings toggle is package URLs in the `uses:` line itself (`pkg:brew/actions-setup-python`, or `pkg:oci/...@sha256:...` for a direct digest pin), with org policy allowlisting which types and namespaces are permitted. Either way Homebrew’s role is producing one index in the formulae.brew.sh JSON schema, and once the declarative-install work above lands the tap and the index are the same JSON, so any other curator could produce a compatible one without going near Homebrew’s Ruby. ### Prototype I generated draft formulae for the 70 actions in ecosyste.ms’ current critical set. The pinned sources are 55 JavaScript actions, nine composites and six Docker actions; the JavaScript group is 30 Node 24, 19 Node 20 and six still on Node 12, and all 71 declared `pre`, `main` and `post` entrypoints were present in the archives. Seven of the composites declare external actions, giving seven dependency edges into five repositories, four of which are already in the 70. The two Gradle compatibility actions both depend on subdirectories of `gradle/actions`, so closing the first-level graph takes one extra formula for 71 in all. Running zizmor 1.28.0 over the extracted `action.yml` files, as the audit extension above would, reported 61 findings across seven of the 70. 45 are high-severity, high-confidence findings in five actions: 40 template-injection and five unpinned-uses. Five of the six Docker actions also reference their image or Dockerfile base image by tag rather than digest. The generator hit a few ecosyste.ms data issues on the way (two conda-forge packages whose latest release is recorded as `master` with codeload URLs that now 404 though the stored commit SHAs still fetch, and `conda-forge/webservices-dispatch-action` classified as composite when the pinned source is a Docker action), and one action, `Platane/snk`, has no license file for `brew audit` to accept. The parser, formula emitter and dependency rewrite are in `bin/generate-formulae` and stayed short because the community lockfile tools already do the same `action.yml` walk. Getting from the generated drafts to a usable tap needs the `audit_formula` hook wrapping zizmor and the `depends_on` coverage check, plus a Windows path for the setup step, which can’t run `brew` and would have to pull the bottles as plain OCI blobs instead. Gitea’s `act_runner` and Forgejo’s runner implement the same `uses:` semantics with no closed server-side resolution call, so the setup-step path works on them today and either of the built-in forms above could land there without a GitHub backend change. Codeberg, Forgejo and Gitea each already maintain an `actions` org (codeberg.org/actions, code.forgejo.org/actions, gitea.com/actions) that mirrors a hand-picked subset of upstream actions so their users’ workflows resolve without touching github.com. Those orgs are the curated index a tap would produce, maintained by hand, and the Forgejo maintainers have been vocal about wanting something better than inheriting GitHub’s resolution model.

nesbitt.io

brew install actions/checkout - Using Homebrew's tap machinery as a curated distribution layer for GitHub Actions. https://nesbitt.io/2026/08/04/brew-install-actions-checkout.html

brew install actions/checkout

In December I went through why `uses:` is a package manager with no lockfile, no integrity hashes and no transitive visibility, and in April through the run of incidents that followed from that. GitHub’s 2026 security roadmap has since committed to a lockfile, now in preview as `gh-actions-lock`, and made immutable actions the preferred resolution path. Neither of those changes adds any review between an action author tagging a release and the runner executing it. Homebrew has run that kind of curated index for fifteen years and, as of the immutable-actions rollout, stores its artifacts as OCI manifests on ghcr.io alongside the actions themselves, so I spent some time working out how much of a GitHub Actions registry you could assemble from Homebrew parts. ### Shared storage Immutable actions and Homebrew bottles are both OCI artifacts on ghcr.io: `actions/publish-immutable-action` tars the action directory, pushes it as a layer with `artifactType: application/vnd.github.actions.package.v1+json`, attaches a sigstore bundle through the OCI referrers API, and tags the manifest with the semver, after which a workflow referencing `actions/[email protected]` resolves through `pkg.actions.githubusercontent.com` instead of the git tarball. `brew pr-pull` pushes bottles under the same manifest schema at `ghcr.io/homebrew/core/<name>` with a `com.github.package.type: homebrew_bottle` annotation and a sigstore attestation that `brew verify` checks against Homebrew’s CI identity, so `crane manifest ghcr.io/homebrew/core/jq:1.7.1` and `crane manifest ghcr.io/actions/checkout:4.2.2` return the same document type, as you’d expect from last week’s post. The comparison table in the December post marked Actions ✗ on integrity hashes, transitive visibility, dependency-tree inspection and immutable versions, and homebrew-core provides all four for its 8,400 formulae through the index rather than the storage: each formula pins a source URL to a sha256, declares dependencies that `brew deps --tree` can walk, passes `brew audit` and human review on every change, gets autobumped by `livecheck` when upstream tags a release, and can carry `deprecate!` or `disable!` when it shouldn’t be installed. ### A tap of actions The index can be a tap, with each formula pinning an action tarball by SHA-256: class ActionsCheckout < Formula desc "Checks out a repository for a GitHub Actions workflow" homepage "https://github.com/actions/checkout" url "https://github.com/actions/checkout/archive/refs/tags/v4.2.2.tar.gz" sha256 "63e9c07ff6c9ddf3a3b39d30e59f0bf3a..." license "MIT" livecheck do url :stable strategy :github_latest end def install prefix.install Dir.children(".") end end For a JavaScript action that ships a built `dist/` in its release tarball, that’s sufficient: the tarball is pinned to a content hash, `brew audit` and `brew verify` apply as they would to any formula, and `brew bump-formula-pr` opens a reviewed PR when checkout tags v4.2.3. Everything above `def install` is already static data. Homebrew is in the middle of migrating install hooks to declarative steps so that bottle and cask installs need no Ruby evaluation at all, at which point an actions tap could be `.json` files with no code execution on install. The transitive problem is specific to composite actions, whose `action.yml` carries its own `uses:` lines that the runner re-resolves at execution time regardless of how the outer action was pinned. In a formula those become `depends_on` entries plus an `inreplace` at build time. For a composite that internally calls `actions/cache@v4`: depends_on "actions-cache" def install inreplace "action.yml", "uses: actions/cache@v4", "uses: ./.brew-actions/actions-cache" prefix.install Dir.children(".") end The resulting bottle has no floating refs left in it, `brew deps --tree` prints the transitive graph that no runner command exposes today, and the tap’s git log records which `actions-cache` revision the composite was built against. Moving that pin requires a reviewed PR; an action author cannot change it with `git tag -f` in someone else’s repository. Every incident in the weakest-link post would have required a reviewed change to that index before reaching downstream users, where an npm-style per-project lockfile would only have reduced the number of downstream repositories exposed. A workflow that pins `@v4` today has already delegated the version decision to whoever can push a tag to the action repo, and a tap moves that delegation to a reviewer instead. It also matches Homebrew’s rolling-release design, where `Brewfile.lock.json` was removed in November 2024 and per-project pinning is currently out of scope. I’d like to see the lockfile come back this year, and until it does a workflow that needs stricter reproducibility than the tap’s HEAD can pin the tap itself to a commit. An `audit_formula` extension for the tap would run zizmor over the extracted `action.yml` and reject anything that trips `dangerous-triggers` or `template-injection`, and reject composites whose internal `uses:` lines aren’t fully covered by `depends_on`. The Marketplace’s “verified creator” badge checks the publisher’s identity and nothing about the action’s contents, so a static-analysis gate at index time would be new. Bottles built from the tap are attested by the tap’s CI the same way homebrew-core bottles are. Each formula’s `url` points at a GitHub repository, which is the input `brew vulns` already keys OSV lookups on, so an advisory against `actions/download-artifact` surfaces through the same path as one against `openssl`. ### Getting the runner to use it The runner has three `ActionSourceType` values in `ActionStepDefinitionReference.cs` (repository, container registry, script) and none of them is “an installed package on disk”, so consuming a Homebrew-installed action means picking one of three integration points at increasing cost. The runner accepts `uses: ./path/to/action` relative to `$GITHUB_WORKSPACE`. The prototype tap at andrew/homebrew-actions packages `actions/checkout`, `actions/cache`, `pre-commit/action` and `actions/first-interaction`. Its setup action runs `brew bundle --file .github/Actionfile` and copies each keg into `./.brew-actions/<name>`, which is where the formula’s `inreplace` above pointed the composite’s dependency. The Actionfile is a normal Brewfile: tap "andrew/actions" brew "andrew/actions/pre-commit-action" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - uses: andrew/homebrew-actions@7def8ee0f83dbb7850e9029c9e0e6ccbafdd209e - uses: ./.brew-actions/pre-commit-action The four formulae pass `brew audit`, install and test on hosted macOS and Ubuntu runners, and the integration job runs the copied `pre-commit/action` composite through to its rewritten `actions/cache` step. It still costs one round-trip to fetch the setup action itself the old way, and `checkout` stays on a plain SHA pin because it runs before the workspace has anything in it to `uses: ./` from. Because `./` is anchored at `$GITHUB_WORKSPACE`, the setup action has to copy each keg there and `checkout` has to run first. Runner 2.336.0 added a `$/` prefix that anchors at the repository containing the defining file, resolved at the running commit: in a workflow `$/` is readable before any step has run, and it’s also valid for reusable workflows (`uses: $/.github/workflows/foo.yml`), which `./` never supported. `gh-actions-lock` rewrites existing `./` references to `$/` by default and treats the result as inherently pinned, so no lockfile entry is generated for it. Inside a composite loaded via `uses: ./path`, though, `$/` still resolves against the workflow’s repository rather than the copied directory: an earlier iteration of the prototype rewrote the composite’s `uses:` to `$/../actions-cache` and the runner attempted to fetch `andrew/homebrew-actions/../actions-cache@<sha>`. So the setup step can’t use `$/` to point at `$(brew --prefix)/opt`, and the `.brew-actions` destination has to be baked into the formula’s `inreplace`. On self-hosted runners, `ActionManager.cs` reads `ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE` and, if `<owner>_<repo>/<resolved-sha>.tar.gz` exists there, skips the download. A `brew bundle` on the runner host could populate that directory from the tap. GitHub’s server-side resolver has already chosen the SHA used as the cache key, so this only controls where its bytes come from. It provides an offline mirror but cannot override `@v4` resolution. A fourth `ActionSourceType` could read a formula’s JSON from `formulae.brew.sh` (or any tap’s API endpoint), verify the bottle attestation, and extract to `_actions/`. Hosted runners resolve refs to tarballs through the server-side `ResolveActionsDownloadInfoAsync` call, so a client-side patch would affect self-hosted runners and compatible forks such as act and Forgejo’s runner, with no GitHub backend changes. `$/` covers the same-repo case. The setup-step option would also need a similar anchor rooted in a runner-side directory outside any repository. Reusable workflows referenced across repositories (`uses: org/repo/.github/workflows/foo.yml@ref`) go through a different loader and have no local-path form even with `$/`, so the setup step cannot load them. They require the runner patch. Docker actions already resolve through a container registry and get whatever pinning the image reference carries. ### Built in GitHub could integrate the tap into its resolver and leave `uses: actions/checkout@v4` unchanged. An organisation or repository setting alongside the existing action allowlists would name the index the resolver consults, and that index would map `actions/checkout` to a bottle manifest digest instead of a git ref. The runner would pull the layer from ghcr.io, verify the attestation against the tap’s CI identity, and extract to `_actions/`. Because a composite’s internal refs were rewritten at bottling time, the index response could carry the resolved dependency closure, making the whole transitive tree one index query plus N content-addressed blob fetches. Re-running last week’s job against an unchanged tap commit would produce identical bytes. Switching an organisation from GitHub’s default index to a community tap or an internal one with stricter audit rules would be a settings change, roughly the choice apt users make between Debian stable and a private mirror. An alternative to a settings toggle is package URLs in the `uses:` line itself (`pkg:brew/actions-setup-python`, or `pkg:oci/...@sha256:...` for a direct digest pin), with org policy allowlisting which types and namespaces are permitted. Either way Homebrew’s role is producing one index in the formulae.brew.sh JSON schema, and once the declarative-install work above lands the tap and the index are the same JSON, so any other curator could produce a compatible one without going near Homebrew’s Ruby. ### Prototype I generated draft formulae for the 70 actions in ecosyste.ms’ current critical set. The pinned sources are 55 JavaScript actions, nine composites and six Docker actions; the JavaScript group is 30 Node 24, 19 Node 20 and six still on Node 12, and all 71 declared `pre`, `main` and `post` entrypoints were present in the archives. Seven of the composites declare external actions, giving seven dependency edges into five repositories, four of which are already in the 70. The two Gradle compatibility actions both depend on subdirectories of `gradle/actions`, so closing the first-level graph takes one extra formula for 71 in all. Running zizmor 1.28.0 over the extracted `action.yml` files, as the audit extension above would, reported 61 findings across seven of the 70. 45 are high-severity, high-confidence findings in five actions: 40 template-injection and five unpinned-uses. Five of the six Docker actions also reference their image or Dockerfile base image by tag rather than digest. The generator hit a few ecosyste.ms data issues on the way (two conda-forge packages whose latest release is recorded as `master` with codeload URLs that now 404 though the stored commit SHAs still fetch, and `conda-forge/webservices-dispatch-action` classified as composite when the pinned source is a Docker action), and one action, `Platane/snk`, has no license file for `brew audit` to accept. The parser, formula emitter and dependency rewrite are in `bin/generate-formulae` and stayed short because the community lockfile tools already do the same `action.yml` walk. Getting from the generated drafts to a usable tap needs the `audit_formula` hook wrapping zizmor and the `depends_on` coverage check, plus a Windows path for the setup step, which can’t run `brew` and would have to pull the bottles as plain OCI blobs instead. Gitea’s `act_runner` and Forgejo’s runner implement the same `uses:` semantics with no closed server-side resolution call, so the setup-step path works on them today and either of the built-in forms above could land there without a GitHub backend change. Codeberg, Forgejo and Gitea each already maintain an `actions` org (codeberg.org/actions, code.forgejo.org/actions, gitea.com/actions) that mirrors a hand-picked subset of upstream actions so their users’ workflows resolve without touching github.com. Those orgs are the curated index a tap would produce, maintained by hand, and the Forgejo maintainers have been vocal about wanting something better than inheriting GitHub’s resolution model.

nesbitt.io

RE: https://mastodon.social/@andrewnez/117032582823441702 Investigating this today, looks like there's a new residential proxy scraping the html of the various ecosyste.ms services, not the json api. Useragent is all last years chrome on desktop mac, all executing js and passing anubis, see if […]

Original post on mastodon.social

mastodon.social

Andrew Nesbitt@andrewnez.mastodon.social.ap.brid.gy · last wk.

92m requests to @ecosystems so far today, the biggest single day I've ever seen 🫠

This Week in Package Management: 1 August 2026 https://nesbitt.io/2026/08/01/this-week-in-package-management.html

This Week in Package Management: 1 August 2026

Week eleven of the roundup, built from the package manager OPML feed collection and whatever I’ve posted or boosted on Mastodon. ## Releases mise 2026.7.14–18: the default shell-argument settings are now global-only, so an untrusted repo’s local config can’t influence command execution before trust evaluation runs. Experimental task output caching restores a task’s declared outputs and replays logs when its sources, tools and environment are unchanged, and experimental monorepo tasks now infer Node workspace dependency edges and import `package.json` scripts as `node:<package>#<script>` tasks. Verdaccio 6.9.0 requires Node.js 22 as the minimum runtime and ships a dual CJS+ESM build with an `exports` field, so `import { runServer } from 'verdaccio'` resolves a real ES module. The bundled `@verdaccio/config` moves to js-yaml 4.3.0, resolving GHSA-52cp-r559-cp3m. setup-uv v9.0.0 flips the `prune-cache` default to `false` to reduce load on PyPI infrastructure. The major bump reflects that workflows may see higher GitHub Actions cache usage as a result; the reasoning is written up in #967. Renovate 43.282.0 has the mise manager run `mise lock` in the `MISE_SAFE=1` mode added last week, so lockfile updates against untrusted branches no longer need `allowedUnsafeExecutions`. 43.283.0 adds a `commitTrailers` option. 44.0.0 was an accidental major: a squash-merged PR carried a leftover `BREAKING CHANGE` footer that semantic-release acted on, so 44.x is being treated as a continuation of 43.x with no breaking change. uv 0.11.33 runs the malware check against locked tools before reusing them from cache, and the preview lockfile format can now be written and read without embedded `package.metadata`. 0.12.0 collects accumulated correctness changes: `uv init` defaults to a packaged `src/` layout with `uv_build`, wheels that would overwrite the Python interpreter are rejected, and `--require-hashes` directives in requirements files are now enforced with MD5-only hashes refused. 0.12.1 adds per-package pre-release policies via `--prerelease-package`, accepts local HTML files as flat indexes, and the preview `uv check` gains `--fix`. pip 26.2 adds `--only-deps` to install only a requirement’s dependencies, an experimental `--use-feature=venv-isolation` mode that uses a standard venv for build isolation instead of the `sitecustomize.py` overlay, and caches simple-index responses per their `Cache-Control` headers so repeated resolves against the same index are faster. The legacy resolver (`--use-deprecated=legacy-resolver`) is deprecated for removal in 2027. Richard Si has a write-up of the release. pixi 0.74.0 lets environments define dependencies and solve strategy inline without a separate feature block, adds an `--offline` mode, and `pixi global install --git` can build a tool from source given only `--build-backend` and no package manifest. 0.75.0 has `pixi publish` publish all opted-in workspace packages in dependency order, refusing if a required source dependency has not opted in, and restricts `--offline` solves to packages already in the local cache or on a `file://` channel. pnpm 11.15–11.19 on the stable branch: `pnpm update` and `pnpm outdated` now cover GitHub Actions references in workflow files, `pnpm update` can emit changesets for the bumps it makes, `pnpm self-update` ignores project-supplied configuration, web-based `pnpm login` works without a TTY, and peak resolution memory on large workspaces is cut several times over. pnpm 12.0.0-beta.0 is the first beta of the Rust-engine rewrite; it now reads `frozenLockfile`, `savePrefix`, `savePeer` and `saveCatalogName` from `pnpm-workspace.yaml` and `PNPM_CONFIG_*` rather than only accepting them as CLI flags. Maven 4.0.0-rc-6 fixes the RC-5 regressions: a globally-cached field-accessibility state that broke plugin configuration injection, a `ConcurrentModificationException` in the v4 API, and consumer POM conversion for BOM projects. Docker 29.7.0 adds an experimental `embedded-containerd` feature that runs containerd inside the daemon process rather than as a separate managed process, promotes the `image` mount type out of experimental, and updates go-archive to 0.3.0 for CVE-2026-17106. 29.7.1 fixes two regressions from 29.7.0: pulling images whose layers omit explicit parent-directory entries, and `CopyToContainer` rejecting paths that traverse absolute symlinks. Homebrew 6.0.14 attaches vulnerability data from the GitHub Advisory Database to the generated formula API and adds a `brew advisory-match` developer command (both from me), removes most subprocess forks from a no-op `brew` startup, and extends Landlock sandboxing to Linux kernel 6.1’s ABI 2. Also out: npm 12.0.2, Conda 26.7.0, pipx 1.16.5, sbt 2.0.4, snapd 2.77, vcpkg 2026-07-27, Dependabot Core 0.389.0, cabal-install 3.18.1.0, winget 1.30.80-preview, Yarn 4.18.0, Gradle 9.7.0-RC2, APT 3.3.2, Mamba 2.9.0.rc0, Podman 6.1.0-rc1, diffoscope 326. ## Security npm now scans packages at publish time before they become installable, holding or blocking uploads that trip the check. Packages with legitimate security-relevant behaviour declare it via a `contentPolicy` field in `package.json` plus a root `DISCLOSURE` file, and must then be published with 2FA or trusted publishing. Once declared, neither can be removed in later versions. GitHub Actions now holds workflow runs identified as potentially malicious until a collaborator with write access approves them through an authenticated web session. It applies automatically to public repositories on github.com with no configuration. Arch Linux has disabled orphaned-package adoption on the AUR after attackers adopted unmaintained packages to inject a Tor-based remote-access trojan, an escalation of the campaign that began with the alvr takeover in June. New account registration was suspended in June and reopened on 13 July with additional restrictions, which have not been sufficient. LWN has more context. ## Articles Open Source Must Be Fun or It Will Die (Mike McQuaid) argues that maintainer enjoyment is the scarce resource that keeps a project alive, and points at Homebrew’s numbers: 26 of last year’s 29 maintainers are still active, with automation and CI doing the pedantic review work. The Package Manager for Everywhere (Patrick Linnane) breaks down Homebrew’s public analytics by platform: about a quarter of events are on Linux, Universal Blue images (which ship brew by default) account for roughly 26% of non-CI Linux traffic, and WSL is at 3.7%. You Don’t Have a Supply Chain, You Have a Supply Soup (Josh Bressers) argues the supply-chain metaphor breaks down when the inputs to a single dependency include CI runners, developer workstations and everyone with commit access, none of which current tooling captures. ## Papers No Edges, No Verdict (Zięba-Kozarzewski, arXiv) analyses 78,000 SBOMs in the wild and finds 52.9% declare no dependency edges at all, and only 0.10% use CycloneDX compositions to flag that the graph is incomplete; treating edgeless SBOMs as degenerate raised KEV-detection recall from 0.600 to 0.950. No Snake Oil: Verifying Python Package Builds (Dietrich et al., arXiv) rebuilds 12,180 popular PyPI releases with macaron and oss-rebuild: only 15.4% and 19.1% of wheels come out byte-identical to the published artifact, but their daleq4py tool establishes semantic equivalence for 60.2% and 78.9% of source-equivalent rebuilds. ## Elsewhere The EuroPython 2026 Packaging Summit notes are up, following on from last week’s mention of the summit itself. Sessions covered wheel-variant provider trust (PEPs 817 and 825), whether PyPI should distribute application lockfiles separately from libraries, external build-dependency metadata via PURL (PEPs 725 and 804), and the Packaging Council election timeline. Most WASI phase 2 proposals now have OCI packages published and are indexed on wasm.directory, a meta-registry for WebAssembly components that is intended for eventual donation to the Bytecode Alliance. Composer and Packagist.org have launched a formal sponsorship programme with three annual tiers, funding operations, incident response and security-feature work such as malware detection and transparency logs. Ten companies signed on at launch alongside Sovereign Tech Agency funding routed via the PHP Foundation. Renovate maintainer Sebastian Poxhofer has released a Renovate Config Debugger that steps through parsing, migration, validation, preset resolution and `packageRules` matching using Renovate’s own code compiled to run in the browser. GitHub Actions workflows can now reference actions in the same repository with `uses: $/path/to/action`, which resolves to the workflow’s own repository at the commit that is running. It works in steps, composite actions and reusable workflow calls without a checkout, and satisfies policies that require full-length SHA pinning. Fedora’s Adopt PURL Metadata change was accepted for Fedora 45: RPMs built from language-ecosystem packages will carry virtual Provides such as `purl(pkg:cargo/[email protected])` across nine language ecosystems, with no spec file changes needed. ## git-pkgs I tagged 18 repos this week: * git-pkgs v0.18.2 * clone v0.1.2 (new), a Go library for keeping shallow local checkouts of HTTPS Git repositories by shelling out to `git` * cwe v0.1.0 (new), a Go library for looking up MITRE CWE entries by ID with the catalogue embedded at build time * licenses v0.3.0 (new), a Go library and CLI for matching license text against ScanCode’s rule corpus, with the corpus embedded so matching needs no network, cgo or Python * magic v0.1.0 (new), a pure-Go content-detection library that reports format, MIME type and text encoding for a byte slice with no cgo or third-party dependencies * archives v0.4.0 * brief v0.9.4 * capcheck v0.1.3 * distill v0.1.1 * enrichment v0.6.4 * forge v0.7.0 * manifests v0.6.1 * outline v0.1.8 * pin v0.1.1 * proxy v0.6.0 * registries v0.6.4 * sigstore v0.1.2 * spdx v0.2.0 Send links for next week to @[email protected].

nesbitt.io