Zero Dependencies
depends on: supply-chain, zero-infra, threat-model
Every dependency is code you did not write, did not review, and cannot fully control. A zero-dependency default is a security posture, not an aesthetic preference.
Principles
- Start from zero and justify every addition.
- Build It Yourself (BIY) for straightforward features.
- Depend carefully when the domain is hard to implement safely (cryptography, HTML sanitization).
- No runtime CDN fetches: vendor what you need, serve it from your own origin.
- No build step required: the source is the artifact.
What zero-dependencies eliminates
Traditional web applications inherit risk from deep dependency trees. A typical node_modules folder contains hundreds of packages, each with install hooks, transitive dependencies, and implicit trust chains. Zero-dependency architecture eliminates:
- Install-time code execution: no
postinstallscripts, no arbitrary shell commands onnpm install. - Transitive dependency compromise: no chain of packages where one compromised leaf poisons the tree.
- Registry and CDN availability risk: no runtime failure because a third-party host is down.
- Unreviewed upgrades: no automatic major-version bumps pulling in new code paths.
- Build toolchain attack surface: no webpack, babel, or bundler plugins that execute during build.
What stays
Some problems are dangerous to reimplement. Acceptable dependencies are small, focused, auditable, and free of transitive imports:
| Library | Purpose | Why not BIY |
|---|---|---|
| TweetNaCl | Symmetric encryption (XSalsa20-Poly1305) | Cryptography must be correct; audited, zero-dependency |
| DOMPurify | HTML sanitization against XSS | Sanitization edge cases are extensive; battle-tested |
These are vendored into the repository as versioned, hashed files. No runtime fetch. No CDN. No transitive tree.
Vendoring pattern
/js/vendor/
tweetnacl-XX.min.js
dompurify-YY.min.js
<script src="/js/vendor/tweetnacl-XX.min.js"></script>
<script src="/js/vendor/dompurify-YY.min.js"></script>
Track expected hashes. Review every version update diff before replacing a vendored file.
The security argument
Fewer dependencies means fewer trust decisions. Fewer trust decisions means a smaller attack surface. A static-file agent with five vendored libraries has a reviewable, auditable codebase. A typical React application with 1,200 transitive dependencies does not.
This is not about avoiding all external code. It is about making every piece of external code a deliberate, visible choice.
For agents
- Default to zero dependencies for new features
- Implement UI components, state management, and application logic directly
- Depend on libraries where incorrect implementation creates security risk — cryptography, HTML sanitization, complex parsers. Do not trust an LLM to hand-roll these correctly; generative code is especially weak at cryptographic primitives and subtle edge cases that libraries have been audited against
- For UI-heavy projects, a framework like React is reasonable scaffolding — the trade-off between dependency weight and development speed depends on your chosen risk profile
- Vendor all runtime dependencies into the repository
- Verify hashes and review diffs on every dependency update
- Treat dependency count as a security metric, not a productivity metric