Editorial note
Git's submodules are a recurring engineering headache: handy when they work, brittle when they don't. Today's pick looks at a short, sharp post that reframes submodules as a kind of package manager — and then explains why that framing highlights fundamental limits and real security risks.
In Brief
Git Submodules as a Package Manager
Why this matters now: Many build systems and CI setups still use Git submodules; understanding their package-like semantics helps teams avoid brittle pins, duplicated storage, and supply-chain blind spots.
According to a recent post, a tiny real-world snag — Git refusing to move a worktree because it had submodules — drove a deeper realization: structurally, submodules behave like a package manager. The post argues that the gitlink entry (a 160000-mode tree entry that stores a commit SHA) is effectively a lockfile, and .gitmodules is the manifest mapping paths to fetch URLs. The author lays out the mismatch: submodules expose raw Git internals (detached HEADs, raw object IDs, per-worktree storage) instead of providing the resolution, caching and lifecycle guarantees you expect from a package manager.
“the gitlink in the superproject’s tree, a commit SHA recorded at a path with mode 160000, is the lockfile entry, and the .gitmodules file mapping paths to fetch URLs is the manifest.”
That comparison is useful because it surfaces concrete failure modes: pins that break when a remote repo is renamed, duplicated object stores across worktrees, confusing update semantics (are you installing a pinned commit or tracking "latest"?), and security gaps that have triggered CVEs during recursive clones. The post is worth reading closely for anyone who've cursed submodule behavior in CI or monorepos. (Source: the original post.)
Deep Dive
Git Submodules as a Package Manager
Why this matters now: Teams still relying on Git submodules for reproducible builds need to understand the inherent supply-chain and storage problems that submodules expose — fixes exist, but they are partial and affect tooling choices.
Start with the core framing: a superproject records a commit SHA for a submodule at a path (the gitlink). That single SHA is effectively a pinned version — like a lockfile entry. But crucial pieces that a package manager normally provides are missing: a resolver that maps a semantic version or spec to a distributable artifact, a durable cache that avoids repeated full fetches, and clear lifecycle commands (install, update, uninstall) that behave the same across environments.
Because submodules map directly to fetch URLs, pins are fragile. If the upstream repo moves or a hosting provider re-organizes projects, downstream builds that rely on the recorded URL and SHA can fail. There's no independent, canonical registry that will hand you the same commit if the source URL disappears. That makes submodules a poor fit if you need long-term reproducibility or third-party mitigation when upstream hosts are flaky.
Storage and checkout semantics are another recurring pain. Each worktree and each submodule path can result in separate checkouts or copies of objects unless you deliberately wire up sharing (Git alternates, reference repositories, or fs-level hardlinks). The post notes ongoing work — RFCs and patches — to allow per-worktree module directories and to hardlink shared objects, which would reduce duplication. Those changes help, but they don't change the model: Git still expects you to manage object storage policies yourself.
Submodules also expose confusing update semantics. Running a recursive clone or submodule update can leave submodules in detached HEAD states, where the checked-out commit is the pinned SHA, not a branch tip. That behavior is technically correct but surprising: it mixes the ideas of "install this exact revision" and "switch to latest on a channel." For teams, that ambiguity causes mistakes — CI might accidentally build newer code than local devs, or developers might unintentionally commit a detached HEAD change back into a superproject.
Security is the most acute consequence. Because submodules are a composition of URLs and raw commit IDs, they open specific attack paths: a maliciously crafted URL or a hosting compromise can alter what gets checked out during a recursive clone. The post references past CVEs that exploited submodule behaviors during recursion; if accurate, those incidents underscore that recursive fetch-and-checkout workflows are a distinct attack surface. Package managers typically mediate downloads, provide checksums, or a central registry so supply-chain controls can be enforced — Git submodules leave that mediation out.
Given these limits, what practical choices do teams have?
- Treat submodules as a thin, repository-level pointer: accept that they are not a full package system and add extra policies around mirroring, signed tags, and monitored remotes.
- Use repository mirroring or an internal proxy to ensure the fetch URL remains available and you control the artifact store.
- Use Git alternates, reference repos, or shared object stores (where supported) to reduce duplication between worktrees and CI runners.
- For true package semantics — version resolution, indirection, content-addressed artifact stores — prefer a package manager or artifact registry instead of layering more complexity on top of submodules.
None of those is a silver bullet. The patches that aim to reduce storage duplication and improve per-worktree behavior are constructive steps, but the fundamental problem remains: submodules expose raw Git primitives rather than offering the resolution and lifecycle guarantees teams expect from package managers. That mismatch is why the post's comparison is helpful: it shows what you lose when you lean on Git for dependency management.
Bold takeaway: Git submodules work as light-weight pointers, not as a replacement for a real package manager — treat them accordingly, and add mirroring, signatures, or a registry if you need reproducible, secure dependency resolution.
Closing Thought
If your CI or monorepo still uses submodules because they "just worked," this post is a timely nudge: understand the trade-offs and decide whether to invest in making submodules safer (mirrors, signing, shared object stores), or to migrate to tools that were built for dependency resolution and supply-chain controls. Either route beats being surprised by a moved repo, a detached HEAD, or a CVE in your recursive clone step.