Why JavaScript is the Worst Choice for Web3 Applications
JavaScript was never designed for mission-critical or trustless systems, yet much of Web3 relies on it. Its inherent features — dynamic code execution,...

TL;DR
JavaScript was never designed for mission-critical or trustless systems, yet much of Web3 relies on it. Its inherent features — dynamic code execution, mutable global state, sprawling dependency chains — create systemic vulnerabilities that make supply chain attacks almost inevitable. In Web2, these risks mostly led to downtime or data leaks; in Web3, they directly cause asset theft with no recourse. Web3 requires a paradigm shift, characterized by minimal dependencies, deterministic runtimes (such as Rust/WASM), and verifiable stateless-clients to validate all data and interactions. Convenience can never outweigh security. Don’t trust. Verify. Always. Everything.
Introduction
I spent nearly 20 years in the automotive industry, developing a range of software applications. In this field, any software deployed in vehicles must meet extremely high safety requirements, and the standards become even stricter when it touches safety-critical components. The same rigor applies in the banking sector, and in critical infrastructure software — for example, the control systems of nuclear power plants — which are subject to equally strict or even stricter regulations.
Would it ever be acceptable to use, in a critical control system such as automotive braking or nuclear reactor safety monitoring, a language that is insecure by design — where updating a simple user interface function could enable injected malicious code to compromise an entirely different part of the system, relying on a long chain of dependencies — each with their own dependencies — that can be modified at any time by other developers?
Now consider Web3. Applications here directly handle financial assets. In many cases, they safeguard substantial value — for example, multisig wallets managing a startup’s treasury, decentralized exchanges processing millions of Euros in daily trading volume, or lending protocols securing billions of Euros in collateral. And yet, despite these high stakes, much of Web3 is built on JavaScript: a language originally designed for quick browser scripting, not for secure, mission-critical systems. This reliance on JavaScript is a fundamental design flaw — one that makes Web3 applications dangerously vulnerable.

How Security-Critical Software Is Developed
Developing security-critical software requires an entirely different mindset and discipline compared to general-purpose application development. The goal is not just functionality, but provable safety and reliability.
- Strict QA and Process Requirements : Every change must undergo rigorous testing, peer review, and often external audits.
- Minimal and Fully Verified Dependencies : External libraries are used sparingly, and only when they can be thoroughly tested and verified to be safe.
- Restricted Connectivity : In many cases, systems are isolated or operate under controlled environments to minimize the attack surface.
- No Dynamic Code Execution : Techniques like eval or runtime code generation are considered unacceptable, as they open the door to unpredictable behavior.
- No Runtime Overwriting of Core Logic : Self-modifying code or monkey-patching is strictly forbidden, since it undermines system integrity.
- Adherence to Standards : Industries follow strict standards such as ISO 26262 (automotive), DO-178C (aerospace), IEC 61508 (functional safety), or PCI DSS (banking).
- Formal Verification and Fault Tolerance : In many cases, mathematical methods are employed to prove correctness, and systems are designed to fail safely in the event of errors.
- Strict Change Management : Every modification must be traceable and documented to ensure accountability and maintain system integrity.
- Long-Term Maintainability : Security-critical software is often maintained for decades, making minimal dependencies and conservative updates essential.
This is the standard in industries such as banking, critical infrastructure, and control software for automotive and aerospace systems, where a single flaw can have catastrophic consequences.

Why JavaScript Became Popular
When JavaScript was first introduced, it was never meant to power mission-critical applications. Its original purpose was to enable simple interactivity in web browsers — validating forms, creating small animations, or enhancing the user experience without requiring a page reload. This lightweight, flexible design made it easy to adopt quickly.
As the internet grew, so did the role of JavaScript. With the rise of Node.js, developers could use the same language on both the frontend and backend, creating a unified development stack. The NPM package ecosystem exploded, offering millions of ready-to-use modules. For Web2, this was a considerable advantage: developers could move fast, ship features quickly, and rely on a vast open-source community.
Speed and flexibility were the main drivers of JavaScript’s popularity. In traditional web applications, the cost of a bug was often limited to broken functionality or poor user experience. In that context, the trade-off between safety and convenience seemed acceptable.
Despite its weaknesses, some arguments are often made in favor of JavaScript for Web3 applications, including its wide popularity and strong community support, fast iteration cycles, an established Node.js/NPM ecosystem, ongoing efforts to improve security practices, and a low entry barrier due to developer familiarity.

The Inherent Risks of JavaScript (Design Problems)
JavaScript’s design decisions, while enabling fast adoption and flexibility, introduce significant risks when the language is used in financial or trustless contexts, such as Web3. These are not implementation flaws — they are characteristics of the language itself.
- Dynamic Code Execution : Functions like eval or new Function allow arbitrary code to run at runtime. This makes it trivial for attackers to inject and execute malicious logic.
- Lack of Static Typing : Without compile-time checks, incorrect or malicious calldata can flow through applications unnoticed. A single wrong parameter can redirect funds.
- Transitive Dependencies : Applications often rely on hundreds of NPM packages, each with its own dependencies. A compromise in any link of this chain can infect the entire project.
- Overwritable Globals : Core objects, such as window.ethereum or fetch , can be monkey-patched, allowing attackers to intercept API calls or alter transaction data.
- Prototype Pollution : Attackers can modify JavaScript prototypes globally, hijacking logic across unrelated parts of the application.
- No Sandboxing for Packages : Installed modules run with full access to the filesystem, network, and environment variables. Secrets, such as API keys or private data, can be exfiltrated.
- Environment Access : NPM packages can directly read process variables, RPC credentials, and even wallet secrets.
- No Verification of RPC or Blockchain Data : DApps typically trust RPC responses blindly. Fake balances, token metadata, or calldata can be displayed and then signed without verification.
In Web2, these risks often lead to downtime or data leaks. In Web3, they result in drained wallets and irreversible loss of assets.

Real-World Example: Supply Chain Attacks
Supply chain attacks have become one of the most dangerous threats in the Web3 ecosystem. The reason is simple: modern JavaScript development relies heavily on external dependencies. A single compromised library can cascade into thousands of applications.
One striking example occurred in September 2025, when several popular NPM packages — including chalk and debug, and more than 15 others — were compromised after an attacker gained access to a maintainer’s account. Malicious code was inserted and distributed through the standard NPM update process. Any application that pulled in the new versions automatically received the backdoor.
Important to note: none of these dependencies were web3, cryptography, or similar libraries.
The injected code hooked into core browser and provider APIs such as fetch , XMLHttpRequest , and even window.ethereum.request. This allowed the attacker to intercept transaction data, replace recipient addresses, and manipulate calldata just before the transaction was signed. To the user, everything looked normal: the UI displayed the intended transaction, but the signed transaction was silently redirected to attacker-controlled wallets.
The attack was multi-chain in scope, targeting Ethereum, Solana, Bitcoin, Tron, and others by swapping addresses across different formats. The malicious logic even recognized specific function signatures such as ERC20 transfer or DEX interactions, ensuring maximum impact.
This case illustrates the systemic weakness of JavaScript-based Web3 development: by trusting a vast and mutable dependency tree, applications expose themselves to catastrophic compromise with no warning. A single compromised dependency is enough to drain funds at scale. What makes this even worse is that JavaScript’s own design choices — dynamic code execution, the ability to overwrite core logic at runtime, and unrestricted dependency chains — actively enable and amplify these kinds of attacks.

Why This Was “Okay” in Web2 but Fatal in Web3
In the Web2 world, the consequences of JavaScript’s weaknesses were often limited. A compromised dependency or a runtime error might lead to broken functionality, data leaks, or service downtime. These issues were serious, but they were usually reversible: patches could be deployed, systems could be rolled back, and users rarely lost direct control of their money as a result.
The critical difference lies in where business logic is executed. In Web2, core logic typically runs on secure backend servers or cloud infrastructure. Even if the user interface is compromised, the backend still enforces critical rules, limiting the damage.
In Web3, much of the application logic runs locally on the client. This removes dependence on intermediaries, but it also means that security-critical logic operates in the same environment as the UI. When JavaScript is used, injected or malicious code, that is used in the UI, can directly influence transaction flow and application logic. This collapse of boundaries makes Web3 uniquely vulnerable because security logic and UI code share the same runtime environment.
Furthermore, the trustless nature of blockchain applications requires that every component be verifiable. Opaque dependency chains, dynamic code, or mutable global state are incompatible with this principle. The very properties that made JavaScript convenient in Web2 directly undermine the foundations of security and verifiability in Web3.
In Web2, backend systems are usually hardened with sandboxing, containerization, and patch management. In Web3, dApps often execute directly in the browser, relying on JavaScript from CDNs or NPM dependencies. This code runs in the same context as wallet interactions, without a trusted backend layer in between. As a result, compromising only the client can be enough to steal funds — an especially critical risk in immutable blockchain systems where rollback is impossible.

What Web3 Actually Needs
If JavaScript is ill-suited for Web3, what would a secure foundation look like? Building applications that manage real value requires languages, frameworks, and architectures designed with safety and verifiability at their core.
- Deterministic, Minimal Runtimes : Web3 applications should run in environments that minimize the attack surface. Technologies like Rust or WebAssembly (WASM) provide deterministic execution and strong memory safety guarantees.
- Verifiable Clients : Instead of blindly trusting RPC endpoints, applications must verify blockchain data locally to ensure integrity. Stateless stateless-clients, such as colibri.stateless, enable applications to request proofs and validate results without relying on intermediaries.
- Proof-Based Systems : Security in Web3 should be based on cryptographic proofs rather than trust in infrastructure or package maintainers. Zero-knowledge proofs and Merkle proofs can guarantee the integrity of the state and transactions.
- Auditable and Transparent Code : Dependencies should be minimal, thoroughly reviewed, and ideally formally verified. Every component that touches transaction flow must be open to inspection and testing.
- Isolation of Critical Logic : Security-critical code must be separated from UI logic to ensure security. The same process should never handle both rendering animations and preparing transaction calldata.
By moving towards verifiable execution, minimal dependencies, and cryptographic assurance, Web3 can align its technical foundations with the trustless principles it aspires to uphold. Convenience arguments such as “because it’s easier,” “because others do it this way,” or “because users expect it” must never dictate choices of development environment, programming language, libraries, or dependencies. Web3 software is, in most cases, high-security software, and architectural decisions must prioritize security above all else.

Conclusion
JavaScript, by design, is one of the worst languages for building security-critical applications. Its features — dynamic code execution, mutable global state, and sprawling dependency chains — make it fundamentally unsuitable for environments where integrity and trustlessness are essential. Web3 must urgently rethink this foundation.
If JavaScript continues to be used, strict rules must apply: dependencies must be kept to an absolute minimum, and every single one must be thoroughly reviewed and vetted. Automatic updates are unacceptable, since they open the door to supply chain compromise. Critical components should be implemented in WebAssembly (WASM) with zero external dependencies, ensuring deterministic execution and a minimal attack surface.
Equally important is the principle that all information must be verified and accurate. Data from external providers, such as RPC endpoints, cannot be trusted blindly. Internal workflows, such as signing operations with a wallet app, must also be subject to validation. Even internal modules must not assume trust among themselves. The rule is simple and absolute:
Don’t trust. Verify! Always. Everything.
Stateless-clients, such as colibri.stateless or modern light-clients like Helios represent the next stage of Web3 application development. They not only verify blockchain data independently but can also simulate and validate interactions before they are signed. By integrating such clients, applications can achieve true self-sovereignty and eliminate hidden trust assumptions that otherwise exist in today’s JavaScript-centric ecosystems.
Ultimately, the use of JavaScript in dApps must be fundamentally questioned. As Web3 applications move toward mainstream adoption with growing user bases, security must become the highest priority. In Web2, we traded convenience for a loss of privacy; in Web3, we must not repeat a similar mistake by sacrificing security for the sake of convenience.
While some arguments in favor of JavaScript — such as its popularity, developer familiarity, and ecosystem support — are understandable, they cannot outweigh the fundamental security risks that it poses. In Web3, where real value and trustless execution are at stake, convenience must never take precedence over robust security foundations.
For more information about** colibri.stateless** , and how to use it or to contribute, visit our website or follow us on social media (x ,telegram ,LinkedIn ).

Why JavaScript is the Worst Choice for Web3 Applications was originally published in Corpus.Core Insights on Medium, where people are continuing the conversation by highlighting and responding to this story.
Try Colibri — Ethereum stateless client
Verify blockchain data locally with cryptographic proofs. No trusted RPC required.