If It Quacks Like a Package Manager

Published: (March 8, 2026 at 07:27 AM EDT)
7 min read

Source: Hacker News

I spend a lot of time studying package managers, and after a while you develop an eye for things that quack like one. Plenty of tools have registries, version pinning, code that gets downloaded and executed on your behalf. But flat lists of installable things aren’t very interesting.

The quacking that catches my ear is when something develops a dependency graph: your package depends on a package that depends on a package, and now you need resolution algorithms, lockfiles, integrity verification, and some way to answer “what am I actually running and how did it get here?”

Several tools that started as plugin systems, CI runners, and chart‑templating tools have quietly grown transitive dependency trees. Now they walk like a package manager, quack like a package manager, and have all the problems that npm, Cargo, and Bundler have spent years learning to manage—though most of them haven’t caught up on the solutions.

GitHub Actions

FeatureStatus
RegistryGitHub repos
LockfileNo
Integrity hashesNo
Resolution algorithmRecursive download, no constraint solving
Transitive pinningNo
Mutable versionsYes – git tags can be moved. Immutable releases lock tags after publication but can still be deleted.

I wrote about this at length already. When you write

uses: actions/checkout@v4

you’re declaring a dependency that GitHub resolves, downloads, and executes. The runner’s PrepareActionsRecursiveAsync walks the tree by:

  1. Downloading each action’s tarball.
  2. Reading its action.yml to find further dependencies.
  3. Recursing up to ten levels deep.

There’s no constraint solving at all. Composite‑in‑composite support was added in 2021, creating the transitive‑dependency problem, and a lockfile was requested (closed as “not planned” in 2022).

You can SHA‑pin the top‑level action, but Palo Alto’s “Unpinnable Actions” research showed that transitive dependencies remain unpinnable regardless. The tj‑actions/changed‑files incident (Mar 2025) started with reviewdog/action-setup (a dependency of a dependency) and cascaded outward when the attacker retagged all existing version tags to point at malicious code that dumped CI secrets to workflow logs, affecting > 23 000 repos.

GitHub has since added SHA‑pinning enforcement policies (top‑level only) — see the announcement.

Ansible Galaxy

FeatureStatus
Registrygalaxy.ansible.com
LockfileNo
Integrity hashesOpt‑in
Resolution algorithmresolvelib
Transitive pinningNo
Mutable versionsYes, no immutability guarantees

Ansible collections and roles install via ansible-galaxy from galaxy.ansible.com. Dependencies are declared in meta/requirements.yml. When you install a role, its declared dependencies are installed automatically, and those dependencies can have their own dependencies, forming a real transitive tree.

  • The resolver is resolvelib, the same back‑tracking constraint solver pip uses—more sophisticated than what Terraform or Helm use.
  • A lockfile was first requested in 2016 (archived) and re‑opened in 2018; the issue remains open.
  • The now‑archived Mazer tool implemented install --lockfile before being abandoned in 2020, so the feature existed briefly and then disappeared.

ansible-galaxy collection verify can check checksums against the server, and GPG‑signature verification exists, but both are opt‑in and off by default. Published versions on galaxy.ansible.com can be overwritten by the publisher, and roles sourced from git repos suffer the same mutable‑tag problem as GitHub Actions.

Roles execute with the full privileges of the Ansible process (with optional become escalation). There are long‑standing open issues about the inability to exclude or override transitive role dependencies (see #13215).

Terraform Providers and Modules

FeatureStatus
Registryregistry.terraform.io
Lockfile.terraform.lock.hcl
Integrity hashesYes
Resolution algorithmGreedy, newest match
Transitive pinningYes for providers; no for modules
Mutable versionsProviders immutable; modules use mutable git tags

Terraform learned from classic package managers:

  • .terraform.lock.hcl records exact provider versions and cryptographic hashes (multiple formats).
  • terraform init verifies downloads against those hashes, and providers are GPG‑signed.
  • Version‑constraint syntax (~> 4.0, >= 3.1, etc.) is supported.

Note: The lock file only tracks providers, not modules (GitHub issue #31301). Consequently, nested module dependencies require cascading version bumps with no lock‑file protection. Git tags used to pin modules are mutable, meaning a tag‑pinned module can be silently replaced with different content (GitHub issue #29867).

Researchers demonstrated registry typosquatting (hashic0rp/aws with a zero) (BoostSecurity article) and a live supply‑chain‑attack demo at NDC Oslo 2025 (Class Central video) showed this working in practice.

The provider side is solid, but the module side of the transitive tree suffers the same mutable‑reference problems as GitHub Actions.

Helm Charts

FeatureDetails
RegistryChart repos / OCI registries
LockfileChart.lock
Integrity hashesOpt‑in
Resolution algorithmGreedy, root precedence
Transitive pinningYes
Mutable versionsDepends on registry; OCI digests are immutable, chart‑repo tags are not

Kubernetes Helm has more package‑manager DNA than most tools here.

  • Chart.yaml declares dependencies with version constraints.
  • Chart.lock records the exact resolved versions.
  • Subcharts can have their own dependencies, building genuine transitive trees.

The resolver (source) picks the newest version matching each constraint, with versions specified closer to the root taking precedence when conflicts arise.

  • Chart repositories serve an index.yaml that works like a package index.
  • OCI registries also work, but mutability differs:
    • OCI digests – content‑addressed and immutable.
    • Traditional chart repos – publishers can overwrite a version by re‑uploading to the same URL; Chart.lock records version numbers, not content hashes, so it won’t catch the change.

Helm supports provenance files for chart signing, though adoption remains low (Helm provenance docs).

Known Limitations & Vulnerabilities

  • helm dependency build only resolves first‑level dependencies (GitHub issue #2247), not transitive ones.
  • You can’t set values for transitive dependencies without explicitly listing them (GitHub issue #8289).
  • There’s no way to disable a transitive subchart’s condition (GitHub issue #12020).
  • Symlink attack via Chart.lock allowed local code execution when running helm dependency update; fixed in v3.18.4 (GHSA‑557j‑xg8c‑q2mm).
  • Malicious Helm charts have been used to exploit Argo CD and steal secrets from deployments (APIiro blog).

If It Has Transitive Execution, It’s a Package Manager

Once a tool develops transitive dependencies, it inherits a specific set of problems—whether it acknowledges them or not:

ProblemDescription
ReproducibilityThe tree can resolve differently each time; a lockfile is needed to record the exact result.
Supply‑chain amplificationA single compromised package deep in the tree can cascade outward through every project that depends on it (example).
Override and exclusionUsers need mechanisms to deal with transitive dependencies they didn’t choose and don’t want.
Mutable referencesVersion tags that can be moved, rewritten, or force‑pushed mean the same identifier may point at different code tomorrow.
Full‑tree pinningPinning direct dependencies is ineffective if their dependencies use mutable references.
Integrity verificationYou must be able to confirm that what you’re running today is the same thing you ran yesterday.

If your tool exhibits these issues, it is a package manager. No amount of calling it a “plugin system” or “marketplace” will stop supply‑chain attacks from knocking at your door.

0 views
Back to Blog

Related posts

Read more »