<?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: Rushabh Shah</title>
    <description>The latest articles on DEV Community by Rushabh Shah (@rushabh5000).</description>
    <link>https://dev.to/rushabh5000</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%2F3974218%2F8fd94442-cdb7-49c3-93f4-dc68a5af5320.jpg</url>
      <title>DEV Community: Rushabh Shah</title>
      <link>https://dev.to/rushabh5000</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rushabh5000"/>
    <language>en</language>
    <item>
      <title>What is a Software Bill of Materials (SBOM) and why your team needs one</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Thu, 24 Sep 2026 04:39:33 +0000</pubDate>
      <link>https://dev.to/rushabh5000/what-is-a-software-bill-of-materials-sbom-and-why-your-team-needs-one-2of7</link>
      <guid>https://dev.to/rushabh5000/what-is-a-software-bill-of-materials-sbom-and-why-your-team-needs-one-2of7</guid>
      <description>&lt;p&gt;When a critical vulnerability like Log4Shell drops at 11pm on a Friday, the first question every security team asks is: "which of our products use Log4j?" Without an SBOM, answering that question means grep-ing through build files, chasing down dependency declarations in ten different repos, and hoping nobody missed an indirect import. With an SBOM, it's a single query.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Software Bill of Materials (SBOM)&lt;/strong&gt; is a machine-readable inventory of every component — library, package, framework, dependency — that went into a piece of software. Think of it as a nutritional label for code: it tells you what's in there, where it came from, and which version you're running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an SBOM contains
&lt;/h2&gt;

&lt;p&gt;A complete SBOM includes, for each component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name and version&lt;/strong&gt; — the package name and the specific version shipped, not a range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supplier&lt;/strong&gt; — the organization or person who published it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PURL (Package URL)&lt;/strong&gt; — a standardized identifier like &lt;code&gt;pkg:npm/express@4.18.3&lt;/code&gt; or &lt;code&gt;pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1&lt;/code&gt; that tools can resolve without ambiguity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt; — the SPDX identifier of the license under which the component is distributed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashes&lt;/strong&gt; — cryptographic checksums (SHA-256 is standard) that let you verify you got what you expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies&lt;/strong&gt; — which other components this one depends on, forming a graph rather than a flat list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern SBOMs in the CycloneDX 1.7 format also embed &lt;strong&gt;VEX (Vulnerability Exploitability eXchange)&lt;/strong&gt; data — statements about which CVEs affect your specific build and what your analysis concluded about each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: one component in CycloneDX
&lt;/h2&gt;

&lt;p&gt;The clearest way to understand an SBOM is to look at one entry. Here's what a single component — the &lt;code&gt;express&lt;/code&gt; npm package — looks like inside a real CycloneDX 1.7 document:&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bom-ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pkg:npm/express@4.18.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.18.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pkg:npm/express@4.18.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"licenses"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&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;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hashes"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SHA-256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9d3b..."&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"externalReferences"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vcs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/expressjs/express"&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;span class="p"&gt;]&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;Every field earns its place: &lt;strong&gt;type&lt;/strong&gt; distinguishes a library from an application, framework or OS component. &lt;strong&gt;purl&lt;/strong&gt; (&lt;code&gt;pkg:npm/express@4.18.3&lt;/code&gt;) is what makes this machine-resolvable — a vulnerability scanner takes that exact string, queries OSV or another advisory database, and gets back CVEs for &lt;em&gt;this specific version&lt;/em&gt;, not a fuzzy name match. &lt;strong&gt;licenses&lt;/strong&gt; uses a real SPDX identifier (&lt;code&gt;MIT&lt;/code&gt;), not free-text, so a license-compliance tool can categorise it automatically. &lt;strong&gt;hashes&lt;/strong&gt; lets a consumer verify the artifact they actually received matches what the SBOM claims — the same integrity check a checksum file gives you, but standardised and attached to the inventory itself.&lt;/p&gt;

&lt;p&gt;A full SBOM is thousands of these entries plus a &lt;strong&gt;dependencies&lt;/strong&gt; section recording the graph — which component requires which — so a scanner can distinguish a direct dependency from one four levels deep in the tree. When VEX is embedded (CycloneDX 1.7), each vulnerability gets its own statement, for example:&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;"vulnerabilities"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CVE-2024-XXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"affects"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pkg:npm/express@4.18.3"&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;span class="nl"&gt;"analysis"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not_affected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"justification"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code_not_reachable"&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;span class="p"&gt;}]&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;That single VEX statement is the difference between a scanner flagging a CVE and a security team having already recorded — in a machine-readable, auditable way — that this specific build doesn't actually reach the vulnerable code path. Without VEX, every downstream consumer of your SBOM re-does that analysis themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two main formats: CycloneDX and SPDX
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CycloneDX&lt;/strong&gt; is an OWASP standard designed specifically for security use cases. It's JSON or XML, compact, and well-supported by tools that want to do something with the SBOM — like scanning it for vulnerabilities or generating VEX documents. CycloneDX 1.7 (the current version) includes full dependency graphs, license expressions, external references, compositions, and embedded VEX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPDX (Software Package Data Exchange)&lt;/strong&gt; is a Linux Foundation standard originally designed for license compliance and now widely used in government procurement. It's more verbose and supports more licensing metadata. U.S. government agencies increasingly require SPDX SBOMs from their software suppliers under the May 2021 executive order on cybersecurity.&lt;/p&gt;

&lt;p&gt;Both formats are useful and most modern tooling can ingest either. DepWarden accepts both as input and exports CycloneDX 1.7 with embedded VEX.&lt;/p&gt;

&lt;h2&gt;
  
  
  When an SBOM is required vs when it's useful
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When it's required:&lt;/strong&gt; U.S. federal agencies and their suppliers are increasingly mandated to produce SBOMs. The EU Cyber Resilience Act will require them for CE-marked products. Defense and critical infrastructure contracts commonly specify them. If your customer is a government agency or a large enterprise with a mature vendor security program, you'll be asked for one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it's useful regardless:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incident response&lt;/strong&gt; — when a new CVE drops, you query the SBOM rather than auditing every codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merger and acquisition due diligence&lt;/strong&gt; — assess the security and license risk of a target's software before the deal closes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License compliance&lt;/strong&gt; — know every copyleft dependency before you ship, not after legal flags it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change tracking&lt;/strong&gt; — diff two SBOMs across releases to see exactly what components changed, and audit the new risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common SBOM mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generating one, once, and calling it done.&lt;/strong&gt; An SBOM captures a point-in-time inventory. The moment you bump a dependency or add a new one, it's stale — and a stale SBOM that says "no known vulnerabilities" is worse than no SBOM at all, because it's actively misleading. The fix is mechanical: generate the SBOM as a CI/CD build step, not a periodic manual export.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating name+version as unambiguous.&lt;/strong&gt; Two different ecosystems can publish a package with the same name (&lt;code&gt;request&lt;/code&gt; exists on both npm and as an unrelated project elsewhere), and a name alone doesn't tell a tool which registry to check. This is exactly why PURLs exist — &lt;code&gt;pkg:npm/request@2.88.2&lt;/code&gt; is unambiguous in a way "request 2.88.2" is not. An SBOM built from name+version pairs instead of PURLs quietly reintroduces the ambiguity the format was designed to remove.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping the dependency graph.&lt;/strong&gt; A flat component list without the &lt;code&gt;dependencies&lt;/code&gt; relationships tells you &lt;em&gt;what&lt;/em&gt; is in your software but not &lt;em&gt;how&lt;/em&gt; it got there — which means when a transitive vulnerability shows up, you can't answer "which of my direct dependencies do I need to bump to fix this?" without redoing the resolution by hand. A flat SBOM is still useful for inventory purposes, but it can't drive remediation on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assuming SPDX and CycloneDX are interchangeable in every field.&lt;/strong&gt; Both describe the same underlying facts, but CycloneDX's security-first fields (VEX, vulnerability references) have no direct SPDX equivalent, and SPDX's deeper license-relationship expressions don't map cleanly back either. If your downstream consumer specifically needs VEX, generating SPDX alone won't satisfy that requirement even though it's a valid SBOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating and consuming SBOMs
&lt;/h2&gt;

&lt;p&gt;Generating an SBOM from a manifest is what DepWarden does when you scan: it resolves the dependency tree, assigns PURLs, fetches license data, and exports a CycloneDX 1.7 document. Tools like Syft, cdxgen, and the CycloneDX Maven/Gradle plugins can also generate SBOMs directly from build artifacts.&lt;/p&gt;

&lt;p&gt;Consuming an SBOM means running it through a vulnerability scanner (DepWarden accepts CycloneDX and SPDX uploads), a license checker, or a policy engine. The round-trip — generate → enrich → scan → VEX → export — is the compliance workflow most organizations need.&lt;/p&gt;

&lt;p&gt;The key insight is that an SBOM is most valuable when it's generated automatically in CI/CD and kept up to date with every release. A one-time SBOM is better than nothing; a stale one gives false confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating an SBOM before you trust it
&lt;/h2&gt;

&lt;p&gt;An SBOM being well-formed JSON doesn't mean it's &lt;em&gt;complete&lt;/em&gt;. Before relying on one — your own, or one a vendor handed you — check three things. First, does the component count look plausible for the project's size? A Java service reporting 12 components when its build log shows 400 resolved artifacts means the generator only captured direct dependencies, not the full transitive tree. Second, do a sample of PURLs actually resolve — pick five components and confirm the ecosystem, name and version in the PURL match a real package at that registry. Third, check whether the dependency graph is present at all, or whether every component is listed with no relationships; a flat SBOM (see above) will validate against the CycloneDX or SPDX schema perfectly while still being far less useful than a graph-complete one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance and signing
&lt;/h2&gt;

&lt;p&gt;An SBOM answers "what's in this software," but not "can I trust that this SBOM accurately describes what's really running." That's a separate, complementary concern usually handled by signing the SBOM itself (so a consumer can verify it wasn't tampered with in transit) and by attaching build provenance (SLSA-style attestations describing which CI pipeline, which commit, and which build steps produced the artifact the SBOM describes). A signed SBOM with strong provenance is what a mature vendor security review is actually asking for when it requests "your SBOM" — the document alone, unsigned and with no link back to a specific build, is a good start but not the full answer regulators and enterprise buyers are increasingly expecting.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>opensource</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Transitive dependencies explained: why a package you never installed can still break your build</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:22:42 +0000</pubDate>
      <link>https://dev.to/rushabh5000/transitive-dependencies-explained-why-a-package-you-never-installed-can-still-break-your-build-6hn</link>
      <guid>https://dev.to/rushabh5000/transitive-dependencies-explained-why-a-package-you-never-installed-can-still-break-your-build-6hn</guid>
      <description>&lt;p&gt;You run a scan and get a HIGH severity finding in &lt;code&gt;minimist@1.2.0&lt;/code&gt;. You grep your &lt;code&gt;package.json&lt;/code&gt; — it isn't there. So why is it in your build?&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct vs transitive
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;direct dependency&lt;/strong&gt; is one you declared yourself, in &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;pom.xml&lt;/code&gt;, or whatever manifest your ecosystem uses. A &lt;strong&gt;transitive dependency&lt;/strong&gt; is one of &lt;em&gt;those&lt;/em&gt; packages' own dependencies — and their dependencies' dependencies, recursively, however deep the chain goes. A typical modern JavaScript project has a handful of direct dependencies and hundreds of transitive ones. &lt;code&gt;minimist&lt;/code&gt; is a good example: it's an argument parser almost nobody installs directly, but it sits underneath dozens of popular build tools, so it shows up in an enormous number of dependency trees without anyone choosing it.&lt;/p&gt;

&lt;p&gt;This matters because the fix is different depending on which one you have. A vulnerable direct dependency: bump the version yourself. A vulnerable transitive dependency: you can't &lt;code&gt;npm install minimist@latest&lt;/code&gt; and expect it to matter — you have to find &lt;em&gt;which of your direct dependencies&lt;/em&gt; pulls it in, and bump that one (or use your package manager's override mechanism) instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why lockfile "depth" is not the same as "distance from root"
&lt;/h2&gt;

&lt;p&gt;If you've looked at a &lt;code&gt;package-lock.json&lt;/code&gt;, you might assume the nesting level of &lt;code&gt;node_modules/&lt;/code&gt; tells you how many hops a package is from your project. It doesn't, reliably. Package managers hoist dependencies — they lift a package as high in the &lt;code&gt;node_modules&lt;/code&gt; tree as they can without creating a version conflict, purely to save disk space and installation time. A package can be physically hoisted to the top level of &lt;code&gt;node_modules&lt;/code&gt; (nesting depth 0) while still being a transitive dependency three levels deep in your actual logical dependency graph. Depth-in-the-filesystem and depth-in-the-dependency-graph are two different numbers, and conflating them is a common source of confusion when triaging findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracing a package back to its root
&lt;/h2&gt;

&lt;p&gt;The question that actually matters when you see a vulnerable transitive package is: &lt;strong&gt;which direct dependency do I need to upgrade to make this go away?&lt;/strong&gt; Answering that requires walking the real dependency graph — the parent-to-child edges recorded in the lockfile — backward from the vulnerable package until you reach something declared in your manifest.&lt;/p&gt;

&lt;p&gt;DepWarden builds this graph from your lockfile (npm, yarn, pnpm, Maven's effective POM resolution, Go's module graph, Cargo's resolver output, and more) and exposes it two ways. The &lt;strong&gt;dependency graph&lt;/strong&gt; view shows the whole tree so you can see how deep and wide your third-party surface actually is. The &lt;strong&gt;dependency tree / trace-to-root&lt;/strong&gt; search lets you type a package name and instantly see every direct dependency that pulls it in, with the actual path — so "why is this even installed" has a concrete answer instead of a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do about it
&lt;/h2&gt;

&lt;p&gt;Once you know the path, there are three real options: upgrade the direct dependency to a version that itself pulls a fixed version of the transitive package; use your package manager's override/resolution field (&lt;code&gt;overrides&lt;/code&gt; in npm, &lt;code&gt;resolutions&lt;/code&gt; in Yarn, dependency management in Maven) to force the transitive version directly; or, if neither is available yet, treat it as accepted risk with an expiring exception rather than silently ignoring it. DepWarden's minimal-fix solver checks the first option automatically — for a given transitive CVE, it tells you which direct dependency to bump and whether the latest release of that dependency actually resolves the issue, so you're not upgrading blind.&lt;/p&gt;

&lt;p&gt;Related: &lt;a href="https://depwarden.in/software-composition-analysis" rel="noopener noreferrer"&gt;software composition analysis&lt;/a&gt;, &lt;a href="https://depwarden.in/blog/cvss-epss-kev-guide" rel="noopener noreferrer"&gt;CVSS, EPSS and KEV explained&lt;/a&gt;, &lt;a href="https://depwarden.in/vs/snyk" rel="noopener noreferrer"&gt;a free Snyk alternative&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Catch typosquats and dependency confusion in CI (for free)</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:25:12 +0000</pubDate>
      <link>https://dev.to/rushabh5000/catch-typosquats-and-dependency-confusion-in-ci-for-free-146g</link>
      <guid>https://dev.to/rushabh5000/catch-typosquats-and-dependency-confusion-in-ci-for-free-146g</guid>
      <description>&lt;p&gt;Most "dependency scanners" only tell you about &lt;em&gt;known CVEs&lt;/em&gt;. But a huge class of real supply-chain attacks has no CVE at all: &lt;strong&gt;typosquatting&lt;/strong&gt; (publishing &lt;code&gt;expresss&lt;/code&gt; to catch people who fat-finger &lt;code&gt;express&lt;/code&gt;) and &lt;strong&gt;dependency confusion&lt;/strong&gt; (publishing a public package with your internal name so it gets installed instead).&lt;/p&gt;

&lt;p&gt;Trivy, Grype and Dependabot won't flag these — there's no advisory to match. By the time there is one, the malicious &lt;code&gt;postinstall&lt;/code&gt; has already run in your CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cheap, effective check
&lt;/h2&gt;

&lt;p&gt;A name that's a single edit away from a very popular package, but isn't that package, is almost always malicious or a mistake. That's exactly what DepWarden flags — for free, no account, and without uploading your source. Connect a GitHub, GitLab, Bitbucket or Azure DevOps repo branch with a PAT and schedule recurring scans, and every run checks your lockfile for these names again.&lt;/p&gt;

&lt;p&gt;A typosquat shows up as a &lt;strong&gt;HIGH&lt;/strong&gt; supply-chain finding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[HIGH] expresss :: Possible typosquat of "express"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Beyond typosquats
&lt;/h2&gt;

&lt;p&gt;The same scan surfaces what classic tools skip: &lt;strong&gt;OpenSSF Scorecard&lt;/strong&gt; health, &lt;strong&gt;deprecated&lt;/strong&gt; packages, and &lt;strong&gt;end-of-life&lt;/strong&gt; release lines — alongside OSV/KEV/EPSS-prioritized vulnerabilities.&lt;/p&gt;

&lt;p&gt;Try it on your own lockfile at &lt;a href="https://depwarden.in" rel="noopener noreferrer"&gt;depwarden.in&lt;/a&gt; — no sign-up required.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>security</category>
      <category>devops</category>
      <category>github</category>
    </item>
    <item>
      <title>How npm typosquatting and dependency confusion attacks work (and how to stop them)</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Thu, 10 Sep 2026 06:42:05 +0000</pubDate>
      <link>https://dev.to/rushabh5000/how-npm-typosquatting-and-dependency-confusion-attacks-work-and-how-to-stop-them-8pb</link>
      <guid>https://dev.to/rushabh5000/how-npm-typosquatting-and-dependency-confusion-attacks-work-and-how-to-stop-them-8pb</guid>
      <description>&lt;p&gt;In February 2021, security researcher Alex Birsan published a paper describing how he had successfully deployed malicious packages to the internal build systems of Apple, Microsoft, PayPal, Shopify, and 32 other companies. He did it without hacking a single server, exploiting a single CVE, or phishing a single developer. He published packages to npm, PyPI and RubyGems with names matching internal packages those companies used — and their build systems installed his public versions automatically.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;dependency confusion&lt;/strong&gt;. It doesn't have a CVE because the vulnerability isn't in the package — it's in the resolution logic of package managers. And it's just one flavour of supply-chain attack that pure CVE-based scanners completely miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typosquatting: catch the typo, own the build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Typosquatting&lt;/strong&gt; exploits the fact that developers type package names by hand. Publish &lt;code&gt;expresss&lt;/code&gt; to npm (with three s's) and anyone who makes that common typo in &lt;code&gt;npm install&lt;/code&gt; pulls down whatever you put in the package. Publish it with a convincing README, a few hundred weekly downloads from bots, and a malicious &lt;code&gt;postinstall&lt;/code&gt; script, and you have a stealthy credential-stealing payload reaching developer machines and CI pipelines.&lt;/p&gt;

&lt;p&gt;Real examples from npm's history:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;crossenv&lt;/code&gt; — a typosquat of &lt;code&gt;cross-env&lt;/code&gt; that stole environment variables (which often contain API keys and tokens).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getcookies&lt;/code&gt; — embedded in a legitimate-looking utility, exfiltrated browser cookies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loadsh&lt;/code&gt; — typosquat of &lt;code&gt;lodash&lt;/code&gt;, one of the most-downloaded npm packages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reqeusts&lt;/code&gt; / &lt;code&gt;reqests&lt;/code&gt; — multiple typosquats of the Python &lt;code&gt;requests&lt;/code&gt; library.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;postinstall&lt;/code&gt; script is the key mechanism: npm runs it automatically when the package installs, before you've had a chance to read the code. A malicious &lt;code&gt;postinstall&lt;/code&gt; can exfiltrate environment variables, install persistent backdoors, or modify other packages in &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency confusion: the namespace collision attack
&lt;/h2&gt;

&lt;p&gt;Dependency confusion exploits a different logic flaw. Many companies use a private npm registry (Artifactory, Nexus, GitHub Packages) alongside the public npm registry. When a package manager is told to look up a package, most configurations check the public registry if the private one doesn't have it — or check both and take the highest version.&lt;/p&gt;

&lt;p&gt;Birsan's attack: find the name of an internal package (often visible in job postings, leaked config files, or error messages), publish a public package with the same name at a version number higher than the internal one (e.g. &lt;code&gt;99.0.0&lt;/code&gt;), and wait for build systems to install the "newer" public version.&lt;/p&gt;

&lt;p&gt;The fix at the package manager level is to scope all internal packages under a private namespace (&lt;code&gt;@company/internal-lib&lt;/code&gt; rather than &lt;code&gt;internal-lib&lt;/code&gt;) and configure the registry to only allow scoped packages from the private registry. But this requires intentional configuration that many teams never got around to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What detection looks like
&lt;/h2&gt;

&lt;p&gt;CVE-based scanners can't catch these attacks because there's no CVE. The signals that matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typosquat detection&lt;/strong&gt; uses edit distance algorithms (Levenshtein, Damerau-Levenshtein) to find packages in your dependency tree whose names are suspiciously close to popular packages but aren't those packages. A package one character transposition away from &lt;code&gt;lodash&lt;/code&gt;, &lt;code&gt;express&lt;/code&gt; or &lt;code&gt;react&lt;/code&gt; with no established publishing history is a strong signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install-script presence&lt;/strong&gt; is a risk signal on its own. Legitimate packages rarely need to run code at install time. A package with a &lt;code&gt;postinstall&lt;/code&gt; script from an unknown publisher warrants scrutiny, especially if it appeared recently in your tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainer and publish history&lt;/strong&gt; signals: a package published 3 days ago with 0 dependents and a &lt;code&gt;postinstall&lt;/code&gt; script that matches a popular name is almost certainly malicious or a testing mistake. Package maintainer changes on established packages — the XZ Utils backdoor mechanism — are similarly high-signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version anomalies&lt;/strong&gt;: a version number that skips from &lt;code&gt;1.0.4&lt;/code&gt; to &lt;code&gt;99.0.0&lt;/code&gt; is a classic dependency confusion marker.&lt;/p&gt;

&lt;h2&gt;
  
  
  DepWarden's approach
&lt;/h2&gt;

&lt;p&gt;DepWarden flags typosquats using a Damerau-Levenshtein edit-distance check against a curated list of the most popular packages in each ecosystem. A package one keystroke away from a popular one that isn't that package gets a supply-chain finding (HIGH severity) with the specific match it resembles.&lt;/p&gt;

&lt;p&gt;Install-script presence, maintainer-change signals and version anomalies from the OpenSSF Scorecard and supply-chain metadata are also surfaced — the signals that have no CVE but matter enormously in the real attack landscape.&lt;/p&gt;

&lt;p&gt;The important point: these attacks happen before a CVE is ever filed, if one ever is. By the time there's a CVE for &lt;code&gt;loadsh&lt;/code&gt;, the attack has already run. The only defence is catching the suspicious signal upstream.&lt;/p&gt;

&lt;p&gt;Curious how many of these look-alike names are actually live on the registry right now? We checked every single-character typo of the 30 most popular npm and PyPI packages against the real registries and cross-referenced the hits against OSV's malicious-package database — see &lt;a href="https://depwarden.in/blog/npm-pypi-typosquatting-2026-report" rel="noopener noreferrer"&gt;the full data&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>node</category>
      <category>npm</category>
    </item>
    <item>
      <title>We checked every single-typo of the 30 most popular npm and PyPI packages. Here's what's actually registered.</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Mon, 07 Sep 2026 11:34:11 +0000</pubDate>
      <link>https://dev.to/rushabh5000/we-checked-every-single-typo-of-the-30-most-popular-npm-and-pypi-packages-heres-whats-actually-29j9</link>
      <guid>https://dev.to/rushabh5000/we-checked-every-single-typo-of-the-30-most-popular-npm-and-pypi-packages-heres-whats-actually-29j9</guid>
      <description>&lt;p&gt;Typosquatting is one of the best-documented supply-chain attack techniques — publish a package one keystroke away from something popular, wait for developers to fat-finger &lt;code&gt;npm install&lt;/code&gt;, and you have a foothold on their machine or CI pipeline with no CVE ever filed. It's been written about for years. What's less documented is a very simple question: &lt;strong&gt;right now, today, how many of those look-alike names are actually sitting on the registry?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So we checked. Not a sample, not an estimate — every single-character typo (omit a letter, double a letter, swap two adjacent letters — the three classic patterns) of the 30 most popular packages on npm and the 30 most popular on PyPI, queried live against each registry's real API, then cross-referenced every hit that's actually registered against OSV.dev's public malicious-package database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The headline numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm: 498 candidate typo names checked across 30 popular packages. 297 (59.6%) are currently registered, real packages.&lt;/strong&gt; Of those 297, &lt;strong&gt;97 (32.7%) are already documented as malicious in OSV's public database&lt;/strong&gt; — not "suspicious," not "flagged by us," but carrying a real &lt;code&gt;MAL-&lt;/code&gt; or &lt;code&gt;GHSA-&lt;/code&gt; advisory ID today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PyPI: 582 candidate typo names checked across an equally popular 30-package set. 47 (8.1%) are registered. Zero are flagged malicious in OSV.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same technique, same rigor, same day. One ecosystem where a third of the look-alikes you'd stumble into are already confirmed hostile; the other where the exposure — at least via this specific single-typo technique — is close to zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;This mirrors the exact detection method DepWarden uses in production: Damerau–Levenshtein edit-distance matching against a curated list of high-popularity package names. For this study we narrowed to the three typo patterns that account for the overwhelming majority of real-world typosquats (and match the classic examples — &lt;code&gt;expresss&lt;/code&gt;, &lt;code&gt;epxress&lt;/code&gt; — that show up in every writeup of this technique):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Omission&lt;/strong&gt; — drop one character (&lt;code&gt;express&lt;/code&gt; → &lt;code&gt;expres&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplication&lt;/strong&gt; — double one character (&lt;code&gt;express&lt;/code&gt; → &lt;code&gt;expresss&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjacent transposition&lt;/strong&gt; — swap two neighboring characters (&lt;code&gt;express&lt;/code&gt; → &lt;code&gt;epxress&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each of the 30 most popular packages per ecosystem, every candidate generated by these three rules was checked against the real registry API (&lt;code&gt;registry.npmjs.org&lt;/code&gt; for npm, &lt;code&gt;pypi.org/pypi/.../json&lt;/code&gt; for PyPI) — a 200 response means it's a real, currently-published package. Every hit was then queried against &lt;code&gt;api.osv.dev&lt;/code&gt; for any associated advisory. Nothing here is inferred or estimated; every count is a live HTTP response, checked on 25 August 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this deliberately doesn't claim:&lt;/strong&gt; a registered look-alike name is not automatically malicious. Plenty of short candidate strings (&lt;code&gt;vue&lt;/code&gt; → &lt;code&gt;ve&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt; → &lt;code&gt;net&lt;/code&gt;) collide with legitimate, completely unrelated packages that just happen to be short common words — npm alone has over 3 million packages, so some collision is inevitable. That's exactly why the OSV cross-reference matters: it's the difference between "this name exists" (weak signal) and "this specific package is a documented, named threat" (real finding). The 97/47 split above is the second kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which popular packages attract the most confirmed-malicious squats
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Popular package&lt;/th&gt;
&lt;th&gt;Confirmed-malicious typo variants found&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;request&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lodash&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;express&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chalk&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;commander&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;async&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;debug&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bluebird&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;moment&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;request&lt;/code&gt; — deprecated since 2020 but still one of the most-installed packages in npm history from years of accumulated dependents — tops the list by a wide margin. That's a pattern worth naming explicitly: &lt;strong&gt;an old, extremely popular, no-longer-actively-maintained package appears to be a more attractive squatting target than a young one&lt;/strong&gt;, plausibly because the volume of developers who've typed its name from memory (and therefore mistyped it) over the package's lifetime is simply larger. We're not claiming causation from an n=30 sample, but the shape of the data points that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full data, and how to re-run it yourself
&lt;/h2&gt;

&lt;p&gt;The top-10 table above is the interesting slice, but a study you cannot check is just an assertion. So here is all of it — every candidate name, whether the registry says it is published, and every advisory id attached to it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://depwarden.in/data/typosquat-study.csv" rel="noopener noreferrer"&gt;Download the dataset (CSV)&lt;/a&gt;&lt;/strong&gt; — one row per candidate, 1,098 rows across both ecosystems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://depwarden.in/data/typosquat-study.json" rel="noopener noreferrer"&gt;Same data as JSON&lt;/a&gt;&lt;/strong&gt; — includes the exact package lists used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run it yourself:&lt;/strong&gt; the generator is in the &lt;a href="https://github.com/Rushabh5000/dep-warden" rel="noopener noreferrer"&gt;DepWarden repo&lt;/a&gt; at &lt;code&gt;scripts/typosquat-study.mjs&lt;/code&gt;. The package lists are hard-coded in that file on purpose, so a re-run stays comparable rather than drifting with whatever is popular this week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Re-running the whole study on &lt;strong&gt;5 September 2026&lt;/strong&gt;, eleven days after the original pass, reproduced it closely: npm 483 candidates, 284 registered (58.8%), &lt;strong&gt;99 carrying an advisory (34.9% of registered)&lt;/strong&gt;; PyPI 615 candidates, 52 registered (8.5%), &lt;strong&gt;zero carrying an advisory&lt;/strong&gt;. The per-package ranking came back identical — &lt;code&gt;request&lt;/code&gt; still first by a wide margin at 17. Counts move by a few points between runs because packages get published and unpublished and OSV adds advisories; the finding does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second data point: how healthy are the real packages?
&lt;/h2&gt;

&lt;p&gt;Alongside the typosquat check, we pulled real &lt;a href="https://scorecard.dev" rel="noopener noreferrer"&gt;OpenSSF Scorecard&lt;/a&gt; scores — an independent, automated 0–10 supply-chain-hygiene rating covering branch protection, CI presence, signed releases and 15 other checks — for the same 60 popular packages (59 resolved; one npm package's GitHub mapping didn't return a score).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;npm: average score 6.24/10. 20.7% score below 5. 6.9% score below 3.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PyPI: average score 6.79/10. 10.0% score below 5. 0% score below 3.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lowest-scoring packages checked: &lt;strong&gt;bluebird (2.0)&lt;/strong&gt;, &lt;strong&gt;debug (2.6)&lt;/strong&gt;, and &lt;strong&gt;request (3.4)&lt;/strong&gt; on npm; &lt;strong&gt;psycopg2 (3.7)&lt;/strong&gt;, &lt;strong&gt;setuptools (4.4)&lt;/strong&gt;, and &lt;strong&gt;httpx (4.9)&lt;/strong&gt; on PyPI. Worth noting: &lt;code&gt;bluebird&lt;/code&gt; and &lt;code&gt;debug&lt;/code&gt; — two of the lowest Scorecard scores in the whole sample — also both appear in the confirmed-malicious-squat table above. Again, not a causal claim from this sample size, but a genuinely interesting adjacency: the same handful of enormously popular, comparatively lower-maintenance-signal packages show up on both lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you ship software
&lt;/h2&gt;

&lt;p&gt;The practical takeaway isn't "avoid npm" — it's that &lt;strong&gt;single-character typing mistakes in &lt;code&gt;npm install&lt;/code&gt; carry real, non-hypothetical, currently-live risk today&lt;/strong&gt;, at a rate (roughly 1 in 3 of the names you could plausibly typo into) that's higher than most developers would guess. PyPI's exposure, at least to this specific attack pattern, is measurably lower — worth knowing if you're weighing where a supply-chain review should focus first.&lt;/p&gt;

&lt;p&gt;Three concrete things that actually help: pin exact versions in a lockfile so a one-off typo in a scratch command doesn't become a permanent dependency; use a scanner that checks for typosquat-shaped names as a distinct signal from CVE matching, since (by definition) none of these 97 malicious packages have a CVE; and treat an unfamiliar package with a name suspiciously close to something you know — one character off, one character doubled — as worth a second look before installing, especially if it has few downloads and a recent publish date.&lt;/p&gt;

&lt;p&gt;DepWarden runs this same detection — Damerau-Levenshtein matching against a curated popular-package list per ecosystem — on every scan, free, no account required. Related reading: &lt;a href="https://depwarden.in/blog/npm-typosquatting-guide" rel="noopener noreferrer"&gt;how npm typosquatting and dependency confusion attacks work&lt;/a&gt;, &lt;a href="https://depwarden.in/blog/detect-typosquats-in-ci" rel="noopener noreferrer"&gt;detecting typosquats in CI&lt;/a&gt;, &lt;a href="https://depwarden.in/blog/openssf-scorecard-explained" rel="noopener noreferrer"&gt;what the OpenSSF Scorecard actually measures&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>SAST vs SCA: why your CI pipeline needs both</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Mon, 29 Jun 2026 11:13:44 +0000</pubDate>
      <link>https://dev.to/rushabh5000/sast-vs-sca-why-your-ci-pipeline-needs-both-52k0</link>
      <guid>https://dev.to/rushabh5000/sast-vs-sca-why-your-ci-pipeline-needs-both-52k0</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;When security teams talk about "scanning" code in CI/CD, they usually mean one of two very different things: scanning the code you wrote (SAST) or scanning the open-source code you imported (SCA). Both are called "security scanners." Both produce findings with severities and CVE-like identifiers. But they catch almost completely different vulnerability classes, and understanding that distinction determines whether you actually close your real risk gaps or just feel like you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SAST scans
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Static Application Security Testing (SAST)&lt;/strong&gt; reads your source code — the JavaScript, Python, Java, Go, Ruby files that your team writes — and looks for security flaws in the code itself. SQL injection in a database query. A hardcoded AWS key in a config file. An XSS vector in a template. A call to &lt;code&gt;MD5&lt;/code&gt; where &lt;code&gt;bcrypt&lt;/code&gt; should be. An HTTP endpoint that passes user input to a shell command.&lt;/p&gt;

&lt;p&gt;SAST doesn't know about or care about which npm packages you installed. It's analyzing your code logic. A SAST tool will flag this:&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`SELECT * FROM users WHERE id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...because the user-controlled value &lt;code&gt;req.params.id&lt;/code&gt; flows directly into a SQL query without parameterization. It doesn't matter which database library you're using or whether that library has any CVEs. The bug is in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SCA scans
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Software Composition Analysis (SCA)&lt;/strong&gt; reads your dependency manifest — &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;pom.xml&lt;/code&gt;, &lt;code&gt;go.mod&lt;/code&gt; — and checks every package (direct and transitive) against a vulnerability database like OSV. If you're running &lt;code&gt;lodash@4.17.4&lt;/code&gt;, which has a known prototype pollution CVE, SCA flags it.&lt;/p&gt;

&lt;p&gt;SCA doesn't analyze your code at all. It's checking the versions of the packages you depend on against a list of known-vulnerable versions. A perfect SAST run with zero findings can coexist with fifty SCA findings in your dependencies — and vice versa.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blind spots in each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What SAST can't see:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vulnerabilities introduced by the open-source packages you import&lt;/li&gt;
&lt;li&gt;Supply-chain attacks with no CVE (typosquatting, dependency confusion)&lt;/li&gt;
&lt;li&gt;Runtime configuration mistakes in cloud infrastructure&lt;/li&gt;
&lt;li&gt;Bugs that only manifest under specific execution conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What SCA can't see:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security bugs in the code your team writes&lt;/li&gt;
&lt;li&gt;Hardcoded credentials in your own source files&lt;/li&gt;
&lt;li&gt;Weak cryptographic choices in your own logic&lt;/li&gt;
&lt;li&gt;Framework misconfigurations (CSRF disabled, DEBUG mode on, CORS wildcard)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The blind spots are almost perfectly complementary. An attacker probing your application has two main paths: exploit a bug in your code (SAST territory) or exploit a bug in a library you imported (SCA territory). Both scanners, covering both paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example: two reports, two attack surfaces
&lt;/h2&gt;

&lt;p&gt;Imagine a Node.js API with these two problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem A (SAST territory):&lt;/strong&gt; In &lt;code&gt;controllers/user.js&lt;/code&gt;, you build a MongoDB query with string interpolation from &lt;code&gt;req.query.username&lt;/code&gt;. This is a NoSQL injection vulnerability. It's in your code. No CVE exists for it. SCA will never find it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem B (SCA territory):&lt;/strong&gt; You depend on &lt;code&gt;jsonwebtoken@8.5.1&lt;/code&gt;, which has a known algorithm confusion vulnerability (CVE-2022-23529). Your code calls &lt;code&gt;jwt.verify()&lt;/code&gt; correctly. SAST may not flag it because the call looks correct in isolation — the vulnerability is in the library's internal implementation.&lt;/p&gt;

&lt;p&gt;These are two real attack vectors. One scanner catches Problem A; the other catches Problem B. Running only SCA leaves NoSQL injection open. Running only SAST leaves the JWT library vulnerability open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The combined CI gate
&lt;/h2&gt;

&lt;p&gt;The most effective CI configuration runs both in parallel:&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;# GitHub Actions — combined SCA + SAST gate&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;Rushabh5000/dep-warden/cli@main&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;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;package-lock.json&lt;/span&gt;   &lt;span class="c1"&gt;# SCA: dependency scan&lt;/span&gt;
    &lt;span class="na"&gt;sast-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src&lt;/span&gt;           &lt;span class="c1"&gt;# SAST: static analysis&lt;/span&gt;
    &lt;span class="na"&gt;fail-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;sast-fail-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parses &lt;code&gt;package-lock.json&lt;/code&gt;, resolves the full transitive dependency tree, checks OSV/KEV/EPSS for vulnerabilities and typosquats&lt;/li&gt;
&lt;li&gt;Walks &lt;code&gt;./src&lt;/code&gt;, detects languages, runs pattern + taint analysis across all source files against 300+ security rules&lt;/li&gt;
&lt;li&gt;Fails the build if either scan produces a HIGH or CRITICAL finding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two scan types, one gate, no account required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritization across both
&lt;/h2&gt;

&lt;p&gt;When findings come from both SAST and SCA, the combined priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SCA: KEV-listed + direct dependency + fix available&lt;/strong&gt; — attackers are actively using this, you can fix it now&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAST: HIGH-confidence injection/RCE finding&lt;/strong&gt; — user input confirmed flowing to a dangerous sink&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SCA: HIGH CVSS + EPSS &amp;gt; 0.10 + fix available&lt;/strong&gt; — elevated exploitation probability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAST: hardcoded credential&lt;/strong&gt; — immediate exposure risk if it reaches a repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else&lt;/strong&gt; — triage by severity and confidence, don't block releases on MEDIUM/LOW&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What DepWarden gives you
&lt;/h2&gt;

&lt;p&gt;DepWarden combines both scanners in one tool, free and without an account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SCA&lt;/strong&gt; across 11 package ecosystems (npm, PyPI, Maven, Gradle, Go, Cargo, Composer, RubyGems, NuGet, Dart, Swift) with OSV/KEV/EPSS enrichment and exploitability-first prioritization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAST&lt;/strong&gt; across 15 languages and IaC formats, with 300+ security rules covering injection, XSS, secrets, weak crypto, framework-specific misconfigurations, and more&lt;/li&gt;
&lt;li&gt;A single &lt;strong&gt;GitHub Action&lt;/strong&gt; that gates pull requests with both scanners in one step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only scanner you need in CI to cover the two main attack paths against your application.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href="https://depwarden.in/blog/what-is-sast" rel="noopener noreferrer"&gt;what is SAST?&lt;/a&gt;, &lt;a href="https://depwarden.in/blog/cvss-epss-kev-guide" rel="noopener noreferrer"&gt;CVSS, EPSS and KEV guide&lt;/a&gt;, &lt;a href="https://depwarden.in/blog/free-snyk-alternative" rel="noopener noreferrer"&gt;free Snyk alternative&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sast</category>
      <category>security</category>
      <category>devsecops</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
