<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubham Chaudhary</title>
    <description>The latest articles on DEV Community by Shubham Chaudhary (@shubham_works).</description>
    <link>https://dev.to/shubham_works</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4221684%2F8687eae6-28b0-4117-b4f4-53ed6e141686.png</url>
      <title>DEV Community: Shubham Chaudhary</title>
      <link>https://dev.to/shubham_works</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubham_works"/>
    <language>en</language>
    <item>
      <title>Your node_modules Is an Attack Surface: A Practical npm Security Checklist for 2026</title>
      <dc:creator>Shubham Chaudhary</dc:creator>
      <pubDate>Tue, 03 Nov 2026 07:33:47 +0000</pubDate>
      <link>https://dev.to/shubham_works/your-nodemodules-is-an-attack-surface-a-practical-npm-security-checklist-for-2026-4akf</link>
      <guid>https://dev.to/shubham_works/your-nodemodules-is-an-attack-surface-a-practical-npm-security-checklist-for-2026-4akf</guid>
      <description>&lt;p&gt;On March 31, 2026, someone hijacked the npm account of axios's lead maintainer. Axios is an HTTP client with over 100 million weekly downloads. The attacker skipped the project's CI pipeline and published two malicious versions directly from the npm CLI.&lt;/p&gt;

&lt;p&gt;Those versions didn't touch axios's own code. They added one new dependency, &lt;code&gt;plain-crypto-js&lt;/code&gt;, which existed only to run a &lt;code&gt;postinstall&lt;/code&gt; script. That script contacted a command-and-control server and dropped a remote access trojan on the machine.&lt;/p&gt;

&lt;p&gt;All of this happened during &lt;code&gt;npm install&lt;/code&gt;, before any application code ran.&lt;/p&gt;

&lt;p&gt;The malicious versions were live for about four hours. Any fresh install or auto-update in that window could have pulled them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💀 &lt;strong&gt;You didn't install malware. You ran&lt;/strong&gt; &lt;code&gt;npm install&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post covers what happened in 2026, how these attacks work, and a checklist you can apply to your project in about 30 minutes. 👇&lt;/p&gt;




&lt;h2&gt;
  
  
  📅 2026 So Far: A Quick Recap
&lt;/h2&gt;

&lt;p&gt;Axios wasn't a one-off. Here are some of the year's major incidents:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mar 31&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axios&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maintainer account hijacked; malicious dependency with a postinstall script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 22&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@bitwarden/cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preinstall script downloaded the Bun runtime and ran a credential stealer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 29&lt;/td&gt;
&lt;td&gt;SAP CAP packages (&lt;code&gt;@cap-js/*&lt;/code&gt;, &lt;code&gt;mbt&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;"Mini Shai-Hulud" worm, preinstall hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 11&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@tanstack/*&lt;/code&gt; and others&lt;/td&gt;
&lt;td&gt;Abused a &lt;code&gt;pull_request_target&lt;/code&gt; workflow and poisoned the GitHub Actions cache to steal an OIDC token; published with valid provenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 19&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@antv/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compromised maintainer account; ~639 malicious versions across 323 packages in about an hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jun 1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@redhat-cloud-services/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compromised employee GitHub account pushed commits that bypassed code review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jun 3&lt;/td&gt;
&lt;td&gt;57+ packages&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;binding.gyp&lt;/code&gt; campaign; 286+ malicious versions in under two hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jul 14&lt;/td&gt;
&lt;td&gt;AsyncAPI packages&lt;/td&gt;
&lt;td&gt;Commits pushed to unprotected pre-release branches; CI exposed &lt;code&gt;NPM_TOKEN&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;📈 The pattern is clear: attacks are faster, automated, and increasingly target the CI pipeline instead of just individual developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 How These Attacks Actually Work
&lt;/h2&gt;

&lt;p&gt;Most incidents combine a few of these techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 🔓 Account Takeover
&lt;/h3&gt;

&lt;p&gt;A maintainer gets phished or has a token leaked, and the attacker publishes a "new version" of a trusted package.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;^1.2.0&lt;/code&gt; range happily accepts it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 📜 Install Scripts
&lt;/h3&gt;

&lt;p&gt;npm runs &lt;code&gt;preinstall&lt;/code&gt;, &lt;code&gt;install&lt;/code&gt;, and &lt;code&gt;postinstall&lt;/code&gt; scripts automatically.&lt;/p&gt;

&lt;p&gt;That means arbitrary code on your machine or CI runner, with access to your environment variables, SSH keys, and cloud credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 🪱 Self-Replication (Worms)
&lt;/h3&gt;

&lt;p&gt;The Shai-Hulud family steals npm tokens and GitHub tokens, then uses them to publish infected versions of other packages the victim maintains.&lt;/p&gt;

&lt;p&gt;One compromised developer becomes dozens of compromised packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 🏭 CI/CD Compromise
&lt;/h3&gt;

&lt;p&gt;The TanStack attack needed no stolen password.&lt;/p&gt;

&lt;p&gt;The attacker abused a GitHub Actions workflow to extract a publishing token from the runner. Because the package was built by the real pipeline, it carried valid provenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 🎭 Typosquatting and Dependency Confusion
&lt;/h3&gt;

&lt;p&gt;Look-alike package names (&lt;code&gt;reacct-dom&lt;/code&gt;), or a public package with the same name as your private one.&lt;/p&gt;

&lt;h3&gt;
  
  
  🕵️ What Do Attackers Steal?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔑 npm and GitHub tokens&lt;/li&gt;
&lt;li&gt;☁️ Cloud credentials (AWS, GCP, Azure)&lt;/li&gt;
&lt;li&gt;☸️ Kubernetes tokens&lt;/li&gt;
&lt;li&gt;🗝️ SSH keys&lt;/li&gt;
&lt;li&gt;⚙️ CI/CD secrets&lt;/li&gt;
&lt;li&gt;📄 &lt;code&gt;.env&lt;/code&gt; contents&lt;/li&gt;
&lt;li&gt;🔐 Password manager data&lt;/li&gt;
&lt;li&gt;🤖 AI tool configuration files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of the 2026 attack waves targeted credentials and configuration data beyond traditional development secrets.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 The Checklist
&lt;/h2&gt;

&lt;p&gt;Here are practical steps you can take to reduce your exposure.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 💻 Local Development
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔒 Commit Your Lockfile, and Review It
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;package-lock.json&lt;/code&gt; (or &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; / &lt;code&gt;yarn.lock&lt;/code&gt;) pins exact versions of your whole dependency tree.&lt;/p&gt;

&lt;p&gt;Treat lockfile changes in a PR as code changes.&lt;/p&gt;

&lt;p&gt;A new, unfamiliar transitive dependency (like &lt;code&gt;plain-crypto-js&lt;/code&gt;) is a red flag. 🚩&lt;/p&gt;

&lt;h3&gt;
  
  
  🚫 Turn Off Install Scripts by Default
&lt;/h3&gt;

&lt;p&gt;Most attacks this year relied on &lt;code&gt;preinstall&lt;/code&gt; or &lt;code&gt;postinstall&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Disable them:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.npmrc&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ignore-scripts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some packages genuinely need build scripts (native modules like &lt;code&gt;sharp&lt;/code&gt; or &lt;code&gt;bcrypt&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Run their scripts explicitly after install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm rebuild sharp &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use pnpm, it already doesn't run dependency build scripts by default (since v10). Approve only the ones you trust with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm approve-builds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bun also skips lifecycle scripts unless a package is in &lt;code&gt;trustedDependencies&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏳ Add a Release Cooldown
&lt;/h3&gt;

&lt;p&gt;Malicious versions are usually caught within hours.&lt;/p&gt;

&lt;p&gt;The axios versions were flagged about six minutes after publishing and contained within roughly four hours.&lt;/p&gt;

&lt;p&gt;If your package manager refuses versions newer than a day or two, you skip most of that window.&lt;/p&gt;

&lt;p&gt;All major package managers now support this, but each uses a different unit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 npm 11.10+ (&lt;/strong&gt;&lt;code&gt;.npmrc&lt;/code&gt;&lt;strong&gt;) — value in DAYS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;min-release-age&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;📦 pnpm 10.16+ (&lt;/strong&gt;&lt;code&gt;pnpm-workspace.yaml&lt;/code&gt;&lt;strong&gt;) — value in MINUTES&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pnpm 11 defaults to 1440 (24 hours)&lt;/span&gt;
&lt;span class="na"&gt;minimumReleaseAge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2880&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;📦 Yarn 4.10+ (&lt;/strong&gt;&lt;code&gt;.yarnrc.yml&lt;/code&gt;&lt;strong&gt;) — value in MINUTES&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;npmMinimalAgeGate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2880&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;📦 Bun (&lt;/strong&gt;&lt;code&gt;bunfig.toml&lt;/code&gt;&lt;strong&gt;) — value in SECONDS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;minimumReleaseAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;172800&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need an urgent hotfix? 🔥&lt;/p&gt;

&lt;p&gt;Override for one install, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--min-release-age&lt;/span&gt; 0 package@version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎯 Be Deliberate With Version Ranges
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt; let new versions in automatically.&lt;/p&gt;

&lt;p&gt;For critical or high-privilege dependencies, consider exact versions and upgrade on purpose.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. 🔍 Auditing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🩺 Run &lt;code&gt;npm audit&lt;/code&gt; Regularly
&lt;/h3&gt;

&lt;p&gt;It catches known vulnerabilities, not fresh malware, but it's still a baseline.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✍️ Verify Signatures and Provenance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm audit signatures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks registry signatures and provenance attestations for your installed packages.&lt;/p&gt;

&lt;p&gt;The axios malicious releases were missing the GitHub Actions provenance that legitimate releases had, which was a key signal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🚨 But don't treat provenance as proof of safety.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The TanStack and Red Hat packages had valid provenance because the attackers compromised the real build pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  📡 Add a Dependency Scanner
&lt;/h3&gt;

&lt;p&gt;Tools like Socket, Snyk, or GitHub's Dependabot alerts watch for suspicious behavior (new install scripts, network calls, obfuscated code) and known-malicious packages.&lt;/p&gt;

&lt;p&gt;Socket flagged the axios release within minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Make Your Update Bots Wait Too
&lt;/h3&gt;

&lt;p&gt;Dependabot and Renovate can open PRs, and trigger CI installs, before a human looks.&lt;/p&gt;

&lt;p&gt;Give them a cooldown:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.github/dependabot.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm"&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly"&lt;/span&gt;
    &lt;span class="na"&gt;cooldown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;renovate.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minimumReleaseAge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2 days"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both still let security updates through without the delay.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. 🏗️ CI/CD Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🧊 Use Deterministic Installs
&lt;/h3&gt;

&lt;p&gt;Use the appropriate install command for your package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci                              &lt;span class="c"&gt;# npm&lt;/span&gt;
pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;      &lt;span class="c"&gt;# pnpm&lt;/span&gt;
yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--immutable&lt;/span&gt;            &lt;span class="c"&gt;# yarn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These fail if the lockfile and &lt;code&gt;package.json&lt;/code&gt; disagree, instead of silently resolving new versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  🙈 Keep Secrets Away From the Install Step
&lt;/h3&gt;

&lt;p&gt;Don't expose deploy keys or cloud credentials as environment variables during &lt;code&gt;npm ci&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inject them only into the steps that need them.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏱️ Use Short-Lived, Least-Privilege Tokens
&lt;/h3&gt;

&lt;p&gt;Avoid long-lived npm tokens in CI.&lt;/p&gt;

&lt;p&gt;If you publish packages, use npm Trusted Publishing (OIDC) and enable 2FA on your npm account.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧱 Harden GitHub Actions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⚡ Be very careful with &lt;code&gt;pull_request_target&lt;/code&gt;. It runs with your repo's secrets, and checking out PR code inside it is how the TanStack attack started.&lt;/li&gt;
&lt;li&gt;📌 Pin third-party actions to a full commit SHA, not a tag.&lt;/li&gt;
&lt;li&gt;🌿 Protect all branches that can trigger publishing, including &lt;code&gt;next&lt;/code&gt; or pre-release branches (the AsyncAPI attack used one).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌐 Restrict Outbound Network Access
&lt;/h3&gt;

&lt;p&gt;Restrict outbound network access from CI runners where you can, so a payload can't reach its command-and-control server.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. 🚑 Have a Response Plan
&lt;/h2&gt;

&lt;p&gt;If a package you use is compromised:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;🔎 &lt;strong&gt;Identify exposure.&lt;/strong&gt; Check whether any lockfile in your projects resolved to an affected version, and when your machines or CI ran an install.&lt;/li&gt;
&lt;li&gt;🧯 &lt;strong&gt;Isolate before rotating.&lt;/strong&gt; Some 2026 payloads (the TanStack wave) installed a background service that deletes your home directory if it detects the stolen GitHub token was revoked. Clean or reimage affected machines first, then rotate.&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Rotate everything the install could see:&lt;/strong&gt; npm tokens, GitHub tokens, cloud keys, SSH keys, and CI/CD secrets.&lt;/li&gt;
&lt;li&gt;🧹 &lt;strong&gt;Rebuild from a clean lockfile&lt;/strong&gt; pinned to a known-good version.&lt;/li&gt;
&lt;li&gt;🎣 &lt;strong&gt;Hunt for unexpected repos, workflows, or package versions&lt;/strong&gt; published from your accounts.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🧰 Putting It Together: A Hardened Starter Setup
&lt;/h2&gt;

&lt;p&gt;Here's a minimal setup you can drop into a project today.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ &lt;code&gt;.npmrc&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ignore-scripts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;min-release-age&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="py"&gt;save-exact&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔁 &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt; (Relevant Parts)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;# not pull_request_target&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;       &lt;span class="c1"&gt;# least privilege by default&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@&amp;lt;full-commit-sha&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@&amp;lt;full-commit-sha&amp;gt;&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install (no secrets in this step)&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify signatures&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm audit signatures&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rebuild trusted native deps&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm rebuild sharp --ignore-scripts=false&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build &amp;amp;&amp;amp; npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It isn't bulletproof. Nothing is.&lt;/p&gt;

&lt;p&gt;But it closes the paths that most 2026 attacks actually used: install scripts, brand-new malicious versions, silent version drift, and over-privileged CI. 💪&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚪 &lt;strong&gt;Install scripts are the #1 entry point.&lt;/strong&gt; Disable them by default and allowlist the few you need.&lt;/li&gt;
&lt;li&gt;⌛ &lt;strong&gt;Time is your cheapest defense.&lt;/strong&gt; A 1–2 day cooldown would have skipped most of this year's malicious releases.&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;Lockfiles plus&lt;/strong&gt; &lt;code&gt;npm ci&lt;/code&gt; &lt;strong&gt;stop silent changes&lt;/strong&gt; to your dependency tree.&lt;/li&gt;
&lt;li&gt;🧾 &lt;strong&gt;Provenance helps, but it isn't proof.&lt;/strong&gt; Compromised pipelines can produce validly signed malware.&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Your CI is a target.&lt;/strong&gt; Least-privilege tokens, careful workflow triggers, and SHA-pinned actions matter.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The next time you run &lt;code&gt;npm install&lt;/code&gt;, remember: you're not just installing code. You're trusting every package in your dependency tree to execute safely.&lt;/p&gt;

&lt;p&gt;🛡️ &lt;strong&gt;Secure your dependencies before attackers turn them into your weakest link.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>javascript</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>A must-read for every Next.js developer! Understanding Server Components, Client Components, and the RSC rendering pipeline makes a huge difference in building performant applications. 🚀</title>
      <dc:creator>Shubham Chaudhary</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:31:14 +0000</pubDate>
      <link>https://dev.to/shubham_works/-23oo</link>
      <guid>https://dev.to/shubham_works/-23oo</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/smtahosin/nextjs-doesnt-render-pages-anymore-it-streams-something-stranger-11ij" class="crayons-story__hidden-navigation-link"&gt;Next.js Doesn't Render Pages Anymore. It Streams Something Stranger.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/smtahosin" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4126369%2Fe2b7c44b-92cf-47ae-a628-32599bd06777.webp" alt="smtahosin profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/smtahosin" class="crayons-story__secondary fw-medium m:hidden"&gt;
              S M Tahosin
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                S M Tahosin
                
                
              
              &lt;div id="story-author-preview-content-4825846" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/smtahosin" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4126369%2Fe2b7c44b-92cf-47ae-a628-32599bd06777.webp" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;S M Tahosin&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/smtahosin/nextjs-doesnt-render-pages-anymore-it-streams-something-stranger-11ij" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 2&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/smtahosin/nextjs-doesnt-render-pages-anymore-it-streams-something-stranger-11ij" id="article-link-4825846"&gt;
          Next.js Doesn't Render Pages Anymore. It Streams Something Stranger.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/java"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;java&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/smtahosin/nextjs-doesnt-render-pages-anymore-it-streams-something-stranger-11ij" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;18&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/smtahosin/nextjs-doesnt-render-pages-anymore-it-streams-something-stranger-11ij#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JavaScript: The Only Language Where [] + [] Feels Like a Philosophical Question 😂</title>
      <dc:creator>Shubham Chaudhary</dc:creator>
      <pubDate>Mon, 02 Nov 2026 10:39:26 +0000</pubDate>
      <link>https://dev.to/shubham_works/javascript-the-only-language-where-feels-like-a-philosophical-question-53cp</link>
      <guid>https://dev.to/shubham_works/javascript-the-only-language-where-feels-like-a-philosophical-question-53cp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favx5x5hcbfmhf3q3mp9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favx5x5hcbfmhf3q3mp9q.png" alt="A frustrated developer trying to make sense of JavaScript's weird behaviour, surrounded by confusing code snippets and unexpected outputs. Because sometimes, JavaScript keeps developers humble! 😂" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every developer has that one moment when JavaScript makes them question their career choices and life.&lt;/p&gt;

&lt;p&gt;You write a simple expression, expect a reasonable result, and JavaScript responds like it has its own interpretation of reality.&lt;/p&gt;

&lt;p&gt;Let's look at some JavaScript moments that feel less like programming and more like negotiating with a very unpredictable roommate.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Empty arrays are apparently very social
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;span class="c1"&gt;// ""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two empty arrays meet.&lt;/p&gt;

&lt;p&gt;They have nothing to say.&lt;/p&gt;

&lt;p&gt;A completely silent conversation.&lt;/p&gt;

&lt;p&gt;But wait...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="c1"&gt;// "[object Object]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the array meets an object, and suddenly JavaScript starts introducing people by their full legal names.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The famous equality crisis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript's loose equality operator is basically a matchmaking service.&lt;/p&gt;

&lt;p&gt;It can find relationships between values that have absolutely nothing in common.&lt;/p&gt;

&lt;p&gt;But apparently, &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; have decided to remain strangers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Developer&lt;/em&gt;: Why?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was prepared with&lt;/em&gt;: It's complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. NaN is not even equal to itself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;NaN&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine introducing yourself to yourself and getting rejected.&lt;/p&gt;

&lt;p&gt;That's &lt;code&gt;NaN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thankfully, JavaScript gives us a way to check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At least someone understands NaN.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The array that refuses to be empty
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We have items!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We have items!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there are no items!&lt;/p&gt;

&lt;p&gt;JavaScript treats empty arrays as truthy.&lt;/p&gt;

&lt;p&gt;Remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty shopping cart is apparently full of confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Addition has an identity crisis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 3&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// "52"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Subtraction&lt;/em&gt;: Let's do some mathematics.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Addition&lt;/em&gt;: Let's start a string concatenation business.&lt;/p&gt;

&lt;p&gt;Same language. Different personalities.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Floating-point mathematics: A tiny betrayal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I asked for 0.3.&lt;/p&gt;

&lt;p&gt;JavaScript delivered 0.30000000000000004.&lt;/p&gt;

&lt;p&gt;That extra number wasn't in the requirements.&lt;/p&gt;

&lt;p&gt;This happens because JavaScript uses IEEE 754 binary floating-point numbers for ordinary numeric calculations. Some decimal fractions cannot be represented exactly in binary.&lt;/p&gt;

&lt;p&gt;For money calculations, use integer minor units (like paise or cents), or an appropriate decimal arithmetic library.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Bonus: The classic typeof surprise
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// "object"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a historical quirk in JavaScript.&lt;/p&gt;

&lt;p&gt;It isn't a useful way to check for null. Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the best solution is simply to be explicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, is JavaScript actually bad?
&lt;/h3&gt;

&lt;p&gt;Not really.&lt;/p&gt;

&lt;p&gt;JavaScript is incredibly useful, flexible, and powerful. These behaviors are mostly consequences of its history, design decisions, and type-coercion rules.&lt;/p&gt;

&lt;p&gt;But understanding them can save us from some very confusing debugging sessions.&lt;/p&gt;

&lt;p&gt;A few habits worth keeping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer &lt;code&gt;===&lt;/code&gt; over &lt;code&gt;==&lt;/code&gt; unless you intentionally need coercion.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Number.isNaN()&lt;/code&gt; to check for NaN.&lt;/li&gt;
&lt;li&gt;Remember that empty arrays and objects are truthy.&lt;/li&gt;
&lt;li&gt;Be careful with floating-point calculations.&lt;/li&gt;
&lt;li&gt;Use explicit type conversions when working with external data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final thoughts
&lt;/h3&gt;

&lt;p&gt;JavaScript isn't necessarily trying to confuse us.&lt;/p&gt;

&lt;p&gt;It's just a language with a very interesting sense of humor.&lt;/p&gt;

&lt;p&gt;And somewhere, a developer is still debugging why &lt;code&gt;[] + {}&lt;/code&gt; returned something that looks like an object introducing itself at a networking event.&lt;/p&gt;

&lt;p&gt;What's your favorite JavaScript WTF moment?&lt;/p&gt;

&lt;p&gt;Share it in the comments. Let's laugh, learn, and debug together. 😂&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. This is my first post here on dev.to. I thought there couldn't be a better way to start than talking about the language that has given me both a career and countless debugging headaches. 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
