Vendoring¶
Vendoring is the practice of copying the source code of another project directly into your own project’s repository. Instead of relying on a package manager to fetch dependencies at build or install time, the dependency code is stored alongside the project itself and treated as part of the source tree.
Although the definition is simple, vendoring has long been controversial. Some engineers see it as a practical way to gain control and reliability, while others have historically described it as an anti-pattern. Both views exist for good reasons, and understanding the trade-offs matters more than choosing a side.
What People Mean by Vendoring¶
At a basic level, vendoring means that a project can be built using only what is present in its repository. No network access is required, no external registries need to be available, and no global package cache is assumed. Checking out a specific revision of the repository is sufficient to reproduce a build from that point in time.
The term originally became common in communities that explicitly placed third-
party code into directories named vendor. Over time, the word broadened to
describe any approach where dependency source code is copied into the project,
regardless of directory layout or tooling.
Vendoring does not necessarily imply permanent forks or heavy modification of third-party code. In many cases, vendored dependencies are kept pristine and updated mechanically from upstream sources.
Why vendoring can be helpful
When dependencies are fetched dynamically, builds implicitly depend on the availability of external services. Vendoring makes build inputs explicit and local — no registry, no CDN, no network required.
Dependency code lives in the same source tree. Easier to inspect, debug, and understand — no context switching, no tooling required to fetch sources on demand.
Vendoring shifts trust from live infrastructure to explicit review. The project decides when dependencies are updated and exactly what code is being included.
When adding a dependency requires consciously pulling in its source code, teams become more selective and more aware of the long-term cost of that decision.
The costs and risks of vendoring
When code is copied into another repository, its original commit history, tags, and branches are no longer directly visible. This can make updates harder, especially if vendored code has been locally modified.
When dependency code is nearby and easy to change, there is a temptation to patch it locally rather than contribute fixes upstream. Over time this can create silent forks that are difficult to reconcile.
Vendored dependencies increase clone size and can dominate diffs during updates. Large dependency refreshes can obscure meaningful changes to the project’s own code, making review and merging harder.
Vendored dependencies do not update themselves. Security fixes, bug fixes, and compatibility updates must be pulled in manually. Without a clear policy and tooling support, vendored code can easily become outdated.
Transitive Dependencies¶
Vendoring becomes significantly more complex once transitive dependencies are considered.
Vendoring a single library often requires vendoring everything that library depends on, and everything those dependencies rely on in turn. For ecosystems with deep or fast-moving dependency graphs, this can quickly become a substantial burden.
Package managers largely exist to manage this complexity by resolving dependency graphs, handling version conflicts, and sharing common dependencies across projects. Vendoring replaces that automation with explicit ownership, which is sometimes desirable and sometimes overwhelming.
A Brief History¶
Vendoring predates modern package managers.
Early C and C++ projects routinely shipped third-party libraries inline because there was no reliable way to depend on system-installed packages. Many Unix programs were distributed as self-contained source releases that included all required code.
As centralized package registries and dependency managers matured, vendoring became less common and was sometimes criticized as outdated or unprofessional. Automated updates, shared caches, and smaller repositories were seen as clear wins.
Interest in vendoring returned as software supply chain risks became more visible. Registry outages, dependency hijacks, and ecosystem fragility highlighted the costs of relying entirely on external infrastructure.
Today, vendoring is best understood as a trade-off rather than a relic.
Vendoring Across Languages¶
Different language ecosystems have adopted vendoring to very different degrees.
Strongly associated with vendoring.
First-class workflow via go mod vendor.
“A little copying is better than a little dependency.”
Supported but not the default. Common in embedded, regulated, and long-term-support projects where reproducibility is paramount.
Frequent vendoring due to the lack of a universal package manager, ABI compatibility concerns, and platform differences.
node_modules is local, but rarely committed due to size and churn.
Fully vendoring is possible but uncommon.
Mixed history. Common in small tools and embedded environments. Modern development more often relies on virtual environments and lock files.
Rarely vendored. Maven and Gradle ecosystems rely heavily on remote repositories and dependency resolution.
Conclusion¶
Vendoring is neither a best practice nor an anti-pattern.
It is a deliberate trade-off that exchanges convenience and automatic updates for control, predictability, and independence from external systems. In some contexts, that trade is clearly worthwhile. In others, it introduces more cost than benefit.
Used intentionally and with an understanding of its limitations, vendoring is simply one tool among many for managing dependencies.
Best Practices¶
The following practices are drawn from our own usage of Dfetch and real-world policies and respected guidelines. They mitigate vendoring risks; they do not eliminate them.
Every vendored dependency must be pinned to an explicit version, tag, or commit, and its source must be documented.
Rationale Vendored code is often added once and then forgotten. Without automation, vulnerabilities, license issues, and inconsistencies can persist unnoticed long after initial inclusion.
pip:
vendor.txttracks versions and sourcesGo/Kubernetes:
go.mod,go.sum,vendor/modules.txtCargo:
Cargo.lock+ vendored sourcesGuidelines: OWASP SCVS, OpenSSF, NIST SP 800-161, SLSA
Dfetch addresses this by having a declarative Manifest and the option to Freeze dependencies to make each revision explicit.
Vendoring must enable fully reproducible and offline builds.
Rationale The point of vendoring code is to remove external dependencies. By making sure an offline build succeeds, it is proven that builds are not dependent on external sources.
Go: committed
vendor/Cargo:
.cargo/config.toml+cargo vendorpip: vendored wheels and pure-Python dependencies
Guidelines: SLSA, OpenSSF
Dfetch doesn’t directly address this — this is a policy to follow.
Vendored dependencies are not reviewed line-by-line; upstream unit tests are not run. Do not auto-format vendored code.
Rationale Reviewing every line of external code is costly and rarely effective. By performing due diligence on upstream sources, you can trust their correctness and security while minimising maintenance burden.
Reviewers should verify:
Version changes and pinning
Changelogs and release notes for regressions or security issues
License compatibility
CI status and test results of the upstream project
Evidence of active maintenance and community support
Do not run upstream unit tests locally, apply formatting or style changes, or make cosmetic changes to vendored code.
Aligned with: OWASP SCVS · Google Open Source Security Guidelines · OpenSSF Best Practices.
Dfetch supports this approach via its manifest and metadata file
(.dfetch_data.yaml), which can be reviewed independently of the vendored code itself.
Vendored dependency updates must be isolated from functional code changes.
Separate PRs or commits
Clear commit messages documenting versions and rationale
Rationale Separation reduces review noise, letting maintainers focus on meaningful changes.
Dfetch doesn’t address this directly.
Vendored code must not be modified directly unless unavoidable.
If modifications are required:
Document patches explicitly
Prefer patch/overlay mechanisms
Upstream changes whenever possible
Cargo:
[patch]mechanismGuidelines: Google OSS, OWASP, OpenSSF
Rationale Keeping the vendored dependency identical to upstream makes it easy to follow upstream updates. Upstreaming any changes lets others in the wider community benefit from your fixes.
Dfetch addresses this by providing a dfetch diff (Diff) command and a
patch (Patch) attribute in the manifest. It also has a CI check to detect
local changes using Check.
Vendored dependencies must be continuously verified through automation.
CI verifies vendor consistency
Dependency and CVE scanning
SBOM generation
Rationale By copy-pasting a dependency, there may be silent security degradation since there are no automatic updates.
Dfetch addresses this by providing a dfetch check (Check) command to
see if vendored dependencies are out-of-date and various report formats (including
SBoM) to check vulnerabilities.
All vendored dependencies must have their license and legal information explicitly recorded.
Rationale Ensuring license compliance prevents legal issues and maintains compatibility with your project’s license.
Track license type for each vendored dependency
Use machine-readable formats where possible (e.g. SPDX identifiers)
Include license documentation alongside vendored code
Guidelines: OWASP, OpenSSF, Google OSS best practices
Dfetch addresses this by retaining the license file even when only a subfolder is fetched.
Minimise vendored code to what is strictly necessary for your project.
Rationale Vendoring unnecessary code increases maintenance burden, security risk, and potential for patch rot. Include only the specific modules, packages, subfolder, or components your project depends on.
Dfetch enables this by allowing you to fetch only a subfolder using the src:
attribute.
Vendored dependencies must be clearly isolated from first-party code.
Rationale Isolation prevents accidental coupling, avoids namespace conflicts, and makes audits, updates, and removals easier. Vendored code should be unmistakably identifiable as third-party code.
Place vendored dependencies in a clearly named directory (e.g.
vendor/,_vendor/)Avoid mixing vendored code with product or library sources
Use language-supported namespace or module isolation where available
Keep vendored code mechanically separable to enable future un-vendoring
Follows established practices in: Go (vendor/) · pip (pip/_vendor) · Cargo (vendor/) · Google OSS / OpenSSF.
Dfetch enables this by allowing you to store the vendored dependency in a folder
using the dst: attribute.
Real-world projects using vendoring
Python configuration management library.
Python’s own package installer vendors its dependencies.
The industry-standard container orchestrator.
Rust’s package manager supports vendoring natively.
Real-world projects using Dfetch
Dfetch uses itself to vendor its own test fixtures.
Industrial Modbus visualisation tool.
Dfetch in a Yocto/embedded Linux build.
Dfetch in a Zephyr RTOS project.
Internally we use Dfetch for various projects and uses.