<?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 Log4Shell taught us about open-source supply-chain security</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Wed, 07 Oct 2026 04:42:06 +0000</pubDate>
      <link>https://dev.to/rushabh5000/what-log4shell-taught-us-about-open-source-supply-chain-security-2leb</link>
      <guid>https://dev.to/rushabh5000/what-log4shell-taught-us-about-open-source-supply-chain-security-2leb</guid>
      <description>&lt;p&gt;On 9 December 2021, a security researcher publicly disclosed a critical remote code execution vulnerability in Apache Log4j 2, a Java logging library used by hundreds of millions of systems worldwide. Within hours, proof-of-concept exploits were circulating. Within days, CISA, the NSA, the FBI and security teams around the world were in full emergency response mode.&lt;/p&gt;

&lt;p&gt;CVE-2021-44228, quickly nicknamed &lt;strong&gt;Log4Shell&lt;/strong&gt;, became the defining supply-chain security incident of the decade. Four years later it's worth examining what it actually revealed about how software is built and consumed — because most of the underlying vulnerabilities in process and practice are still present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it was so bad
&lt;/h2&gt;

&lt;p&gt;Log4j is a transitive dependency. Most of the systems it ran on didn't import it directly. It was imported by the logging framework used by the web framework used by the application framework used by the actual product. Three, four, five levels deep in a dependency tree, in software that had been running unchanged for years.&lt;/p&gt;

&lt;p&gt;That's why the question "do we use Log4j?" was so hard to answer. Developers who had never heard of Log4j were running it. When you ask a developer what their application depends on, they'll tell you the 20 packages in their &lt;code&gt;pom.xml&lt;/code&gt;. They often can't tell you what those 20 packages depend on. The actual dependency tree of a medium-sized Java application routinely has 400–800 components.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;you cannot know your exposure to a vulnerability if you don't have a complete, machine-readable inventory of your dependencies — including transitives.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools weren't ready
&lt;/h2&gt;

&lt;p&gt;Many organizations discovered during Log4Shell that their vulnerability scanning tools were either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not covering transitives&lt;/strong&gt; — only checking direct dependencies against the CVE database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow to update&lt;/strong&gt; — commercial scanners that relied on vendor-maintained databases (rather than the OSV mirror) took days to get signatures for Log4Shell variants (there were four CVEs in two weeks).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not deployed&lt;/strong&gt; — teams had scanners available in theory but not integrated into CI/CD, so the check was manual and ad hoc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The open-source OSV project, which Log4Shell helped drive adoption of, operates a different model: advisories are submitted by security researchers and ecosystem owners, the database is open and machine-readable, and it covers transitives by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the patch cycle was so painful
&lt;/h2&gt;

&lt;p&gt;After Log4j 2.15.0 was released to fix the initial CVE, it was found to have a partial bypass (CVE-2021-45046), requiring 2.16.0. Then a DoS vulnerability was found (CVE-2021-45105), requiring 2.17.0. Then a separate RCE in the 1.x branch was found (CVE-2019-17571, which had been known but deprioritised) and a new one (CVE-2022-23302). Organizations that thought they were done patching kept getting pulled back.&lt;/p&gt;

&lt;p&gt;The lesson here is that &lt;strong&gt;knowing the version you're running is not enough&lt;/strong&gt; — you need to know the exact CVE history for each component you ship, and you need to be able to update that knowledge quickly when new advisories are published.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually protects you
&lt;/h2&gt;

&lt;p&gt;Log4Shell accelerated adoption of several practices that were already best-in-class:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated dependency scanning in CI.&lt;/strong&gt; If scanning runs on every PR, Log4Shell-class vulnerabilities are caught when they enter your dependency tree, not six months later during a security audit. The scan should cover the full transitive tree, not just direct dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SBOM generation in every build.&lt;/strong&gt; An SBOM that's generated and stored at build time means that when a vulnerability drops, you can query "which builds contain this PURL?" rather than re-scanning every repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency pinning in lockfiles.&lt;/strong&gt; Lockfiles (&lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;poetry.lock&lt;/code&gt;, &lt;code&gt;Cargo.lock&lt;/code&gt;) record exact resolved versions, so you know precisely which version of which transitive you're shipping — not a range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exploitability-first prioritisation.&lt;/strong&gt; When you have 400 vulnerable packages, you can't fix them all in a week. Log4Shell was a CVSS 10 with EPSS near 1.0 and almost immediately KEV-listed — the highest possible signal. Organizations that had already built exploitability-based triage were able to correctly identify it as the only thing that mattered that week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VEX documents for your customers.&lt;/strong&gt; Vendors who could quickly produce a Vulnerability Exploitability eXchange (VEX) document saying "we use Log4j but in a configuration where JNDI lookup is disabled and we are not vulnerable" saved enormous amounts of time on both sides of supplier relationships.&lt;/p&gt;

&lt;p&gt;The supply-chain attack surface didn't shrink after Log4Shell. The xz-utils backdoor in 2024 — a deliberately inserted backdoor in a widely-used compression library — showed that the problem extends beyond accidental vulnerabilities to deliberate supply-chain compromise. The practices Log4Shell accelerated remain the best defence.&lt;/p&gt;

</description>
      <category>security</category>
      <category>java</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Open-source license compliance for developers: MIT, Apache 2.0, GPL and beyond</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Sat, 03 Oct 2026 10:49:59 +0000</pubDate>
      <link>https://dev.to/rushabh5000/open-source-license-compliance-for-developers-mit-apache-20-gpl-and-beyond-2ne5</link>
      <guid>https://dev.to/rushabh5000/open-source-license-compliance-for-developers-mit-apache-20-gpl-and-beyond-2ne5</guid>
      <description>&lt;p&gt;Most developers have read the words "MIT License" at least a hundred times and couldn't explain what they actually permit or require. That's fine until the day someone in Legal asks you to audit the licenses in a product you're about to ship, or until you open-source your work and discover it's tangled up with a copyleft library.&lt;/p&gt;

&lt;p&gt;Open-source licenses are legal contracts. They grant you rights to use, modify and redistribute code — but with conditions. Understanding those conditions matters in three situations: when you're using open-source code commercially, when you're distributing software to customers, and when you're open-sourcing your own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The permissive licenses (low obligation)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MIT&lt;/strong&gt; is as permissive as it gets: you can use, copy, modify, merge, publish, distribute, sublicense, and sell the software with no restrictions beyond including the copyright notice and license text. A NOTICE file that lists the MIT dependencies you ship satisfies your obligations. MIT is compatible with everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BSD 2-Clause and 3-Clause&lt;/strong&gt; are nearly identical to MIT. BSD 3-Clause adds a non-endorsement clause (you can't use the project's name to promote your product) that rarely matters in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ISC License&lt;/strong&gt; is MIT-equivalent and used heavily in the npm ecosystem (OpenSSH, many Node.js core utilities).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache License 2.0&lt;/strong&gt; is permissive but adds two significant clauses over MIT:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Patent grant&lt;/strong&gt; — contributors explicitly grant you a license to any patents they hold that are necessarily infringed by the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patent retaliation&lt;/strong&gt; — if you sue anyone for patent infringement related to the software, your license terminates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NOTICE file requirement&lt;/strong&gt; — you must include the NOTICE file from Apache-licensed components in your distribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apache 2.0 is generally preferred over MIT for commercial use because the explicit patent grant protects you from patent claims by contributors. Apache 2.0 is &lt;em&gt;not&lt;/em&gt; compatible with GPL 2.0 (because of the additional conditions), but &lt;em&gt;is&lt;/em&gt; compatible with GPL 3.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  The weak copyleft licenses (medium obligation)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LGPL (GNU Lesser General Public License)&lt;/strong&gt; applies to libraries and requires that the LGPL library remain modifiable by the end user. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can use an LGPL library in a proprietary application &lt;em&gt;if&lt;/em&gt; users can replace the LGPL component (e.g. it's dynamically linked and you provide a mechanism to swap it).&lt;/li&gt;
&lt;li&gt;If you &lt;em&gt;statically link&lt;/em&gt; the LGPL library into your binary, you're effectively distributing it as part of your software and need to comply with the linking requirements.&lt;/li&gt;
&lt;li&gt;Modifications to the LGPL library itself must be published under LGPL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LGPL is the license for many widely-used libraries: Qt, glibc, FFmpeg.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mozilla Public License 2.0 (MPL-2.0)&lt;/strong&gt; is file-level copyleft: modifications to MPL-licensed &lt;em&gt;files&lt;/em&gt; must be shared under MPL, but you can combine them with proprietary code. It's a practical middle ground for commercial use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eclipse Public License (EPL)&lt;/strong&gt; is similar to MPL but at the module level. Contributions to EPL modules must be shared, but combining with proprietary code is permitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The strong copyleft licenses (high obligation)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GPL 2.0 and GPL 3.0&lt;/strong&gt; are the "viral" licenses that commercial developers fear. Any software that incorporates GPL-licensed code and is distributed to users must be distributed under the GPL as well, with source code. If your commercial product statically links a GPL library and ships to customers, your product must also be GPL.&lt;/p&gt;

&lt;p&gt;The critical caveat: &lt;strong&gt;network use is not distribution&lt;/strong&gt; under GPL 2.0/3.0. Running GPL software on a server and making it available over a network (SaaS) does not trigger copyleft. This is the so-called "ASP loophole" that the AGPL was designed to close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AGPL (Affero GPL)&lt;/strong&gt; extends GPL to cover network use. If your SaaS uses an AGPL library, users accessing it over the network are considered to receive the software, and you must provide the AGPL source. Many commercial organizations have an explicit policy against using AGPL dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical compliance steps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generate a NOTICE file.&lt;/strong&gt; For every permissive dependency you ship, include the copyright notice and license text. This is a legal requirement for MIT, BSD and Apache 2.0 — not optional. DepWarden generates this automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit before you ship.&lt;/strong&gt; The time to discover a GPL library deep in your transitive tree is before the customer agreement is signed, not after. A dependency scan with SPDX categorisation surfaces this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate concerns.&lt;/strong&gt; Keep copyleft code in clearly separated modules, invoked via a well-defined API rather than imported directly, if you must use it at all. Dynamic linking (rather than static) gives you more flexibility with LGPL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track license changes.&lt;/strong&gt; Libraries change licenses between versions. Redis went from BSD to SSPL. Elasticsearch moved from Apache 2.0 to Elastic License. A scanner that checks licenses at scan time, not just at adoption time, catches these changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual-licensed libraries.&lt;/strong&gt; Some libraries offer both an open-source license and a commercial license. MySQL (GPL + commercial), Qt (LGPL + commercial), and many others follow this model. If you need to avoid the open-source obligations, purchasing a commercial license is the legitimate path.&lt;/p&gt;

&lt;p&gt;The bottom line: permissive licenses (MIT, Apache 2.0, BSD) allow commercial use with attribution. Weak copyleft (LGPL, MPL) requires sharing modifications to the library but allows proprietary combination. Strong copyleft (GPL, AGPL) requires distributing your entire work under the same terms. Knowing which category each dependency falls into, before you ship, is non-negotiable for any commercial product.&lt;/p&gt;

&lt;p&gt;Run this check on your own project with DepWarden's free &lt;a href="https://depwarden.in/open-source-license-compliance-checker" rel="noopener noreferrer"&gt;open-source license compliance checker&lt;/a&gt; — SPDX categorisation and a generated NOTICE file, no account needed.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>legal</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>OpenSSF Scorecard explained: catching risk in packages that don't have a CVE yet</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Wed, 30 Sep 2026 16:52:51 +0000</pubDate>
      <link>https://dev.to/rushabh5000/openssf-scorecard-explained-catching-risk-in-packages-that-dont-have-a-cve-yet-5efh</link>
      <guid>https://dev.to/rushabh5000/openssf-scorecard-explained-catching-risk-in-packages-that-dont-have-a-cve-yet-5efh</guid>
      <description>&lt;p&gt;Most dependency scanners only tell you about vulnerabilities that already have an advisory filed against them. That's useful, but it's inherently reactive — it only catches risk after someone has found and disclosed it. &lt;strong&gt;OpenSSF Scorecard&lt;/strong&gt; takes a different angle: instead of asking "has this package been proven vulnerable," it asks "does this package show the hygiene signals of a project that's actively maintained and hard to compromise." That second question catches risk earlier, before an incident, not after one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Scorecard actually measures
&lt;/h2&gt;

&lt;p&gt;Scorecard runs a set of automated checks against a project's public repository and CI configuration and produces a score from 0–10 across roughly 18 checks. The ones that matter most for supply-chain risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintained&lt;/strong&gt; — has the project had commits or releases in the last 90 days, or is it effectively abandoned?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch protection&lt;/strong&gt; — can a single compromised maintainer account push directly to the default branch, or does it require review?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code review&lt;/strong&gt; — are changes actually reviewed by someone other than the author before merging?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dangerous workflow&lt;/strong&gt; — does the CI configuration have script-injection patterns or run untrusted code with write access to secrets?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinned dependencies&lt;/strong&gt; — are the project's own build dependencies pinned to a hash, or can a compromised upstream silently change what gets built?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerabilities&lt;/strong&gt; — does the project have known, unfixed vulnerabilities of its own?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token permissions&lt;/strong&gt; — does CI use the principle of least privilege for its tokens, or does everything run with full write access?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each check is a proxy for a real incident pattern. Branch protection and code review are the checks that would have made the XZ Utils backdoor (introduced via a subtly malicious commit from a trusted-looking maintainer account) meaningfully harder to slip in. Dangerous workflow and token permissions are the checks that catch the GitHub Actions script-injection class of attack that's been used to steal CI secrets from dozens of popular projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters even without a CVE
&lt;/h2&gt;

&lt;p&gt;A package with a low Scorecard score isn't necessarily compromised right now — it's a package where, &lt;em&gt;if&lt;/em&gt; something goes wrong, there's less standing between an attacker and your build. An unmaintained package with no branch protection and a single maintainer is a soft target: if that maintainer's account is phished, or they sell the package to someone with bad intent (a real, repeated pattern in the npm ecosystem), there's no review process to catch a malicious release before it reaches you. None of that shows up as a CVE until after it happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a score in context
&lt;/h2&gt;

&lt;p&gt;A 10/10 score is rare and not the bar to hold every dependency to — plenty of excellent, safe packages score lower simply because they're small, stable, and don't need elaborate CI. What matters is using the score as a &lt;em&gt;relative&lt;/em&gt; signal: a sudden drop in a package you depend on, a very low score on a package with broad reach in your tree, or a low score combined with a package that hasn't been touched in years, are the combinations worth actually looking at. Scorecard is a triage signal, not a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  How DepWarden uses it
&lt;/h2&gt;

&lt;p&gt;DepWarden surfaces Scorecard health alongside every dependency in a scan, so a package's maintenance risk sits next to its vulnerability findings instead of requiring a separate lookup. Combined with deprecation markers and end-of-life release-line detection, it's part of the "risk that has no CVE yet" picture DepWarden shows before it bites — the same category of signal that catches typosquatting and dependency confusion.&lt;/p&gt;

&lt;p&gt;Related: &lt;a href="https://depwarden.in/blog/detect-typosquats-in-ci" rel="noopener noreferrer"&gt;detect typosquats and dependency confusion in CI&lt;/a&gt;, &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/transitive-dependencies-explained" rel="noopener noreferrer"&gt;transitive dependencies explained&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>devsecops</category>
      <category>github</category>
    </item>
    <item>
      <title>A free, anonymous Snyk alternative for dependency scanning</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Sun, 27 Sep 2026 10:19:47 +0000</pubDate>
      <link>https://dev.to/rushabh5000/a-free-anonymous-snyk-alternative-for-dependency-scanning-1mc5</link>
      <guid>https://dev.to/rushabh5000/a-free-anonymous-snyk-alternative-for-dependency-scanning-1mc5</guid>
      <description>&lt;p&gt;If you've shopped for Software Composition Analysis (SCA), you've hit the same wall: the good tools (Snyk, Black Duck, Mend, JFrog Xray) want an account, gate the useful parts behind a paywall, and upload your project to their cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DepWarden&lt;/strong&gt; is a free, &lt;strong&gt;anonymous&lt;/strong&gt; alternative. You paste a manifest, a lockfile, or an SBOM — or point it at a public GitHub repo — and it runs in a private, session-isolated workspace. It never receives your source code, only the dependency manifest text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerabilities, prioritized by real exploitability
&lt;/h2&gt;

&lt;p&gt;Every component is matched against the full OSV mirror, then enriched with &lt;strong&gt;CISA KEV&lt;/strong&gt; (actively exploited) and &lt;strong&gt;FIRST EPSS&lt;/strong&gt; (exploit probability). Instead of a wall of CVSS scores, you get a "Fix these first" list that explains &lt;em&gt;why&lt;/em&gt; — "actively exploited + fix available + direct dependency." That's the noise reduction the paid tools charge for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supply-chain risk CVE-only scanners miss
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typosquats / dependency confusion&lt;/strong&gt; — packages one keystroke from a popular one (&lt;code&gt;expresss&lt;/code&gt;, &lt;code&gt;loadsh&lt;/code&gt;) before they reach your build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency health&lt;/strong&gt; — OpenSSF Scorecard, deprecation, and &lt;strong&gt;end-of-life&lt;/strong&gt; release lines (e.g. "django 2.2 stopped receiving security fixes in 2022").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Actionable remediation
&lt;/h2&gt;

&lt;p&gt;Not just "a CVE exists" but a batch plan — "upgrading 3 packages clears 7 findings" — plus a copy-paste fix command for your ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance round-trip
&lt;/h2&gt;

&lt;p&gt;Ingest an existing CycloneDX or SPDX SBOM; export a CycloneDX SBOM, an &lt;strong&gt;OpenVEX&lt;/strong&gt; document, and a &lt;strong&gt;NOTICE / license-obligations&lt;/strong&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeat scans with no account
&lt;/h2&gt;

&lt;p&gt;Connect a GitHub, GitLab, Bitbucket or Azure DevOps repo branch with a personal access token and attach a schedule — daily, weekly or monthly — and DepWarden re-scans it automatically and emails a report. No token retained beyond the scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystems
&lt;/h2&gt;

&lt;p&gt;npm, PyPI, Maven, Gradle, Go, Cargo, Composer, RubyGems, NuGet, Dart, Swift — manifests, lockfiles and SBOMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately doesn't do
&lt;/h2&gt;

&lt;p&gt;No call-graph reachability and no auto-fix PRs — both need access to your source or a GitHub app with repo write, which would break the "no account, no source upload" promise. DepWarden trades that for privacy.&lt;/p&gt;

&lt;p&gt;Try it: paste a &lt;code&gt;package-lock.json&lt;/code&gt; with an old &lt;code&gt;lodash&lt;/code&gt; and a typo'd package name and you'll see the prioritized fixes, the typosquat flag, and the one-command remediation in seconds. No sign-up, at &lt;a href="https://depwarden.in" rel="noopener noreferrer"&gt;depwarden.in&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devsecops</category>
      <category>tools</category>
      <category>devops</category>
    </item>
    <item>
      <title>What is SAST? A developer's guide to static application security testing</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Sun, 27 Sep 2026 10:17:15 +0000</pubDate>
      <link>https://dev.to/rushabh5000/what-is-sast-a-developers-guide-to-static-application-security-testing-40lk</link>
      <guid>https://dev.to/rushabh5000/what-is-sast-a-developers-guide-to-static-application-security-testing-40lk</guid>
      <description>&lt;p&gt;Static Application Security Testing (SAST) is the practice of analyzing source code — or bytecode, or compiled binaries — for security vulnerabilities without executing the program. A SAST tool reads your code the way a security-aware compiler would: following execution paths, tracking data flow, and flagging patterns that match known vulnerability classes. The key word is &lt;em&gt;static&lt;/em&gt;: analysis happens offline, on source files, with no running server or test environment required.&lt;/p&gt;

&lt;p&gt;That property makes SAST the right tool for catching security bugs when they're cheapest to fix: at the moment a developer writes the code, on every commit, in the CI pipeline before a PR is merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  How SAST works: three technical approaches
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Pattern matching (regex-based)
&lt;/h3&gt;

&lt;p&gt;The simplest approach is pattern matching: define a regular expression for a dangerous construct (&lt;code&gt;eval(&lt;/code&gt;, &lt;code&gt;System.exec(&lt;/code&gt;, &lt;code&gt;password =&lt;/code&gt;) and flag every match. This is fast, easy to write rules for, and works across any language without a parser. The downside is false positives: &lt;code&gt;eval(&lt;/code&gt; appears in safe code too, and a regex can't distinguish whether the argument to &lt;code&gt;eval&lt;/code&gt; is attacker-controlled or a hardcoded constant.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Abstract Syntax Tree (AST) analysis
&lt;/h3&gt;

&lt;p&gt;More sophisticated tools parse source code into an AST — a structured tree representing the code's syntax — and analyze patterns within that structure. An AST rule can express "find all method calls to &lt;code&gt;query()&lt;/code&gt; where the first argument is a concatenation involving a non-constant value," which is far more precise than a regex. AST analysis understands code shape rather than raw text.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Taint analysis / data flow
&lt;/h3&gt;

&lt;p&gt;The most powerful approach is taint analysis: the tool tracks data from untrusted &lt;strong&gt;sources&lt;/strong&gt; (HTTP request parameters, form inputs, user-uploaded content) through the code until it reaches a dangerous &lt;strong&gt;sink&lt;/strong&gt; (database queries, shell commands, HTML output). A taint finding fires only when there is a confirmed path from source to sink with no sanitization in between.&lt;/p&gt;

&lt;p&gt;Taint analysis is how serious SQL injection, XSS, and command injection detections work. It requires building a data flow graph across the codebase, which is more expensive than pattern matching but dramatically reduces false positives. DepWarden's SAST engine combines all three: pattern rules for broad coverage, tree-sitter AST rules for structural precision on supported languages, and an intra-file taint pass that only fires injection-class rules when user-controlled data actually reaches the sink.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vulnerability categories SAST covers
&lt;/h2&gt;

&lt;p&gt;SAST tools primarily find &lt;strong&gt;code-level vulnerabilities&lt;/strong&gt; — flaws in the application's own logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Injection&lt;/strong&gt; (SQL, NoSQL, LDAP, OS command) — user input reaches a dangerous API call without sanitization. CWE-89, CWE-78.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Site Scripting (XSS)&lt;/strong&gt; — user input is reflected into HTML output without escaping, enabling script execution in the victim's browser. CWE-79.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insecure deserialization&lt;/strong&gt; — attacker-controlled data is passed to a deserializer that can instantiate arbitrary objects. CWE-502.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded credentials&lt;/strong&gt; — API keys, passwords, tokens, private keys committed directly in source. CWE-798.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak cryptography&lt;/strong&gt; — MD5 or SHA-1 for password hashing, ECB mode encryption, 1024-bit RSA keys, hardcoded initialization vectors, DES/RC4 usage. CWE-327, CWE-326.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insecure randomness&lt;/strong&gt; — &lt;code&gt;Math.random()&lt;/code&gt; or &lt;code&gt;rand()&lt;/code&gt; for security-sensitive values like tokens or session IDs. CWE-338.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path traversal&lt;/strong&gt; — user input reaches a file system call without sanitization, allowing arbitrary file access. CWE-22.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF&lt;/strong&gt; — user-controlled URLs are fetched server-side, enabling access to internal services and cloud metadata endpoints. CWE-918.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insecure configuration&lt;/strong&gt; — &lt;code&gt;DEBUG = True&lt;/code&gt; in Django settings, CSRF protection disabled, security headers absent, CORS wildcard with credentials. CWE-16.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SAST, DAST, and SCA: the three-scanner model
&lt;/h2&gt;

&lt;p&gt;These scanner types are complementary, not interchangeable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scanner&lt;/th&gt;
&lt;th&gt;What it finds&lt;/th&gt;
&lt;th&gt;When it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SAST&lt;/td&gt;
&lt;td&gt;Bugs in your own source code&lt;/td&gt;
&lt;td&gt;CI, pre-commit, every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DAST&lt;/td&gt;
&lt;td&gt;Bugs exposed at runtime in a deployed app&lt;/td&gt;
&lt;td&gt;Staging environment, release gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCA&lt;/td&gt;
&lt;td&gt;Vulnerabilities in open-source dependencies&lt;/td&gt;
&lt;td&gt;CI, pre-commit, every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SAST catches bugs in the code you write. SCA catches bugs in the code you import. DAST catches bugs that only manifest when the application is running — misconfigured servers, race conditions, issues SAST can't reach statically. A mature security pipeline uses all three; SAST and SCA run on every commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating SAST into CI/CD
&lt;/h2&gt;

&lt;p&gt;The most valuable property of SAST is that it runs without a deployed application. Upload a source zip in the browser, or connect a GitHub, GitLab, Bitbucket or Azure DevOps repo branch and schedule recurring scans so new findings surface without you doing anything.&lt;/p&gt;

&lt;p&gt;The key configuration choice is &lt;code&gt;--fail-on&lt;/code&gt;. Setting it to &lt;code&gt;critical&lt;/code&gt; only blocks the most severe findings; &lt;code&gt;high&lt;/code&gt; blocks high and critical. Starting with &lt;code&gt;critical&lt;/code&gt; and moving to &lt;code&gt;high&lt;/code&gt; once you've baselined your codebase is the practical approach. Failing on &lt;code&gt;medium&lt;/code&gt; is usually too noisy until you've tuned suppressions for your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing false positives
&lt;/h2&gt;

&lt;p&gt;The main challenge with SAST is false positives, which create alert fatigue that causes developers to ignore the scanner entirely. Good tooling reduces false positives through:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taint gating:&lt;/strong&gt; Only fire injection-class rules when the data flow is confirmed. A rule that matches &lt;code&gt;eval(&lt;/code&gt; pattern-only fires on every eval call; a taint-gated rule fires only when the argument comes from user input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence levels:&lt;/strong&gt; Separate HIGH-confidence (taint-confirmed, specific pattern, no plausible safe use) from MEDIUM and LOW (pattern-only, more context needed). Treat them differently in CI — block on HIGH, review MEDIUM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suppression markers:&lt;/strong&gt; When a specific finding is safe by design, document it in code:&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="c1"&gt;// depwarden-ignore: eval-user-input — content is from a curated config, not user input&lt;/span&gt;
&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configExpression&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not-patterns:&lt;/strong&gt; Rules can include patterns that cancel the match. A "hardcoded password" rule can exclude lines that reference &lt;code&gt;process.env&lt;/code&gt; or &lt;code&gt;os.environ&lt;/code&gt;, since those are reading from environment variables, not hardcoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DepWarden's SAST covers
&lt;/h2&gt;

&lt;p&gt;DepWarden's static analysis covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;14 languages:&lt;/strong&gt; JavaScript, TypeScript, Python, Java, Go, PHP, Ruby, C, C++, C#, Rust, Kotlin, Swift, Shell/Bash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IaC:&lt;/strong&gt; Terraform/HCL, YAML (GitHub Actions, Kubernetes), Dockerfile, config/secret files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;300+ rules&lt;/strong&gt; organized into packs:

&lt;ul&gt;
&lt;li&gt;Core injection, XSS, secrets, weak crypto (applies to all languages)&lt;/li&gt;
&lt;li&gt;Framework-specific: Django, Spring Boot, Express, Rails, Laravel, ASP.NET Core&lt;/li&gt;
&lt;li&gt;Advanced cryptography: ECB mode, DES, RC4, hardcoded IV, bcrypt cost, TLS version&lt;/li&gt;
&lt;li&gt;Shell script security: eval injection, wget/curl pipe exec, world-writable files&lt;/li&gt;
&lt;li&gt;Mobile: Android WebView, iOS keychain, SSL pinning bypass&lt;/li&gt;
&lt;li&gt;API &amp;amp; web: JWT algorithm confusion, SSRF, open redirect, GraphQL introspection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every finding includes CWE and OWASP Top 10 mapping, severity (CRITICAL/HIGH/MEDIUM/LOW), confidence level, remediation guidance, and a redacted code snippet that never exposes actual credentials.&lt;/p&gt;

&lt;p&gt;Findings are separated from SCA dependency results and filterable by severity, confidence, category, language, and status. The engine runs in under 10 seconds for most projects.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href="https://depwarden.in/blog/sast-vs-sca" rel="noopener noreferrer"&gt;SAST vs SCA — why you need both&lt;/a&gt;, &lt;a href="https://depwarden.in/blog/detect-typosquats-in-ci" rel="noopener noreferrer"&gt;catching typosquats in CI&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;/p&gt;

</description>
      <category>sast</category>
      <category>security</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>CVSS, EPSS and KEV: how to actually prioritize dependency vulnerabilities</title>
      <dc:creator>Rushabh Shah</dc:creator>
      <pubDate>Sun, 27 Sep 2026 04:42:38 +0000</pubDate>
      <link>https://dev.to/rushabh5000/cvss-epss-and-kev-how-to-actually-prioritize-dependency-vulnerabilities-31e4</link>
      <guid>https://dev.to/rushabh5000/cvss-epss-and-kev-how-to-actually-prioritize-dependency-vulnerabilities-31e4</guid>
      <description>&lt;p&gt;Every week a dependency scanner finds dozens of vulnerabilities. Every week most developers ignore them, because there's no time to fix 200 issues and no obvious way to know which 3 actually matter. The answer lives in three numbers: CVSS, EPSS and KEV. Understanding what each one measures — and what it doesn't — is the difference between a security backlog that never shrinks and one you can actually work through.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVSS: severity, not risk
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Common Vulnerability Scoring System (CVSS)&lt;/strong&gt; gives every published CVE a score from 0 to 10 based on how bad the vulnerability &lt;em&gt;could&lt;/em&gt; be in a worst-case scenario. CVSS 9.8 sounds terrifying. The problem is that CVSS scores are entirely theoretical — they describe the vulnerability in the abstract, with no reference to how often it's actually exploited or whether attackers even care about it.&lt;/p&gt;

&lt;p&gt;In practice, fewer than 5% of published CVEs are ever exploited in the wild. A CVSS 9.8 in a library feature you don't use, in an attack path that requires local access you don't expose, is a lower priority than a CVSS 6.0 that's actively being used in ransomware campaigns. Sorting by CVSS alone means you're sorting by worst-case theory, not real-world risk.&lt;/p&gt;

&lt;p&gt;That's why CVSS is the &lt;em&gt;starting point&lt;/em&gt;, not the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  EPSS: exploitation probability
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Exploit Prediction Scoring System (EPSS)&lt;/strong&gt;, maintained by FIRST, takes a very different approach. It's a machine-learning model trained on actual exploitation evidence — threat intelligence feeds, honeypot data, proof-of-concept exploit activity, CISA data and more. It produces a daily probability score (0–1) for the likelihood that a given CVE will be exploited in the next 30 days.&lt;/p&gt;

&lt;p&gt;The numbers are striking: the top 1% of CVEs by EPSS account for roughly 75% of observed exploitation attempts. The bottom 80% have near-zero probability of being exploited. This means if you filter to CVEs with EPSS &amp;gt; 0.10 (10% probability), your fix list shrinks dramatically while still covering the overwhelming majority of real attacks.&lt;/p&gt;

&lt;p&gt;EPSS is updated every day as new exploitation evidence arrives, so a CVE that was low-priority last month can jump to high-priority overnight if attackers start weaponizing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  KEV: confirmed exploitation in the wild
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;CISA Known Exploited Vulnerabilities (KEV) catalog&lt;/strong&gt; is the most reliable signal of all. CISA — the U.S. Cybersecurity and Infrastructure Security Agency — maintains a manually curated list of vulnerabilities that have been confirmed exploited in real-world attacks. These aren't predictions; they're facts. If a CVE is in the KEV catalog, attackers have working exploits and are actively using them.&lt;/p&gt;

&lt;p&gt;The KEV list is legally binding for U.S. federal agencies (CISA mandates remediation within 2–3 weeks) but is freely published for everyone. KEV entries often have EPSS scores near 1.0, but not always — a CVE targeting industrial control systems might be KEV-listed with a lower EPSS simply because the affected population is small.&lt;/p&gt;

&lt;p&gt;KEV findings are always your highest priority, regardless of CVSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Suppose a scan of a mid-sized Node.js service returns five open findings. Here's what a naive CVSS-only sort produces versus what the KEV/EPSS-aware ranking produces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;CVSS&lt;/th&gt;
&lt;th&gt;EPSS (illustrative)&lt;/th&gt;
&lt;th&gt;KEV&lt;/th&gt;
&lt;th&gt;Fix available&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A — RCE in a logging library, reachable from a public HTTP handler&lt;/td&gt;
&lt;td&gt;9.8&lt;/td&gt;
&lt;td&gt;0.94&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B — Deserialization bug in an admin-only internal tool&lt;/td&gt;
&lt;td&gt;9.1&lt;/td&gt;
&lt;td&gt;0.02&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C — Prototype pollution in a deep transitive utility package&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D — SSRF in a webhook-delivery library, actively targeted this quarter&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;td&gt;0.61&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E — Denial-of-service in a rarely-used parsing edge case&lt;/td&gt;
&lt;td&gt;5.3&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sorted by CVSS alone&lt;/strong&gt;, the order is A, B, C, D, E — B (9.1) outranks D (6.5) despite D being far more likely to actually be exploited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sorted by KEV, then EPSS, then CVSS&lt;/strong&gt;, the order becomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt; — KEV-listed &lt;em&gt;and&lt;/em&gt; the highest EPSS in the set. Fix this week, full stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D&lt;/strong&gt; — not KEV-listed, but EPSS of 0.61 puts it in the top slice of real-world exploitation activity. Fix in the current sprint, ahead of B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B&lt;/strong&gt; — high CVSS, but an EPSS of 0.02 says almost nobody is actually exploiting it (consistent with it requiring admin access an external attacker doesn't have). Schedule normally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C&lt;/strong&gt; — same story as B: real bug, low real-world pressure. A fix exists, so bundle it into the next routine dependency bump rather than an emergency patch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E&lt;/strong&gt; — lowest EPSS, no fix available yet, and the weakest CVSS in the set. Accept and monitor; revisit if a fix ships or its EPSS moves.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CVSS-only ranking would have burned a week fixing B before D — the finding attackers were actually using. This is the entire value of layering EPSS and KEV on top of CVSS: it's not that CVSS is wrong, it's that CVSS alone answers "how bad could this be," not "should I fix this today."&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting them together: a practical priority order
&lt;/h2&gt;

&lt;p&gt;A useful mental model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;KEV-listed + fix available&lt;/strong&gt; → fix immediately, this week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EPSS &amp;gt; 0.20 + fix available&lt;/strong&gt; → fix in the current sprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVSS Critical + EPSS &amp;lt; 0.05&lt;/strong&gt; → schedule, deprioritise over items 1–2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVSS Medium with no fix&lt;/strong&gt; → accept/document, monitor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else&lt;/strong&gt; → triage periodically, don't block releases.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two more signals matter beyond these scores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct vs transitive&lt;/strong&gt; — a vulnerability in a package you import directly is a higher priority than the same CVE in a package five levels deep that you can't easily upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix availability&lt;/strong&gt; — upgrading to a safe version is a different problem than waiting for the upstream to patch. If there's no fix, your option is mitigation or accepting the risk, not just "upgrade".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your organisation needs a documented SLA (for a customer contract, an audit, or an internal policy), it's reasonable to turn this into fixed remediation windows: KEV-listed findings within 7 days (matching CISA's own federal mandate as a sane external benchmark), EPSS above 0.20 within 30 days, everything else on your normal release cadence. Write the policy in terms of KEV/EPSS thresholds rather than a flat CVSS cutoff — a CVSS-only SLA ("fix all Criticals within 30 days") is exactly the policy that produces the wasted-effort scenario in the worked example above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes teams make with these three signals
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treating CVSS as a risk score.&lt;/strong&gt; CVSS measures theoretical severity, not likelihood. A team that gates CI purely on "no CVSS &amp;gt;= 7" will spend real engineering time on findings nobody is exploiting while a lower-CVSS, high-EPSS finding sits unpatched in the same backlog. Use CVSS to understand &lt;em&gt;impact&lt;/em&gt;, and EPSS/KEV to understand &lt;em&gt;urgency&lt;/em&gt; — they answer different questions and neither replaces the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring EPSS because it "isn't a real number."&lt;/strong&gt; EPSS is a probability, not a certainty, and it does move — a score of 0.02 today can become 0.80 within days of a public proof-of-concept exploit landing. Teams that check EPSS once and never again lose exactly the signal that makes it useful. It's designed to be re-checked, not memorised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting that KEV is a floor, not a ceiling.&lt;/strong&gt; A vulnerability leaving the KEV catalog (CISA does periodically review and remove stale entries) doesn't mean it's safe — it means active campaigns have moved on. A vulnerability &lt;em&gt;entering&lt;/em&gt; KEV, on the other hand, should immediately jump every other prioritisation signal you're using, including a low EPSS score computed before the exploitation started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sorting the whole backlog instead of triaging the top slice.&lt;/strong&gt; You don't need a perfect ranking of 200 findings. You need a reliable way to identify the 5-10 that matter this week. Spending analyst time perfectly ordering findings 50 through 200 is wasted effort next to just clearing the KEV-listed and high-EPSS findings first and re-triaging weekly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get EPSS and KEV data yourself
&lt;/h2&gt;

&lt;p&gt;Both are free and public, with no API key required for reasonable use. &lt;strong&gt;EPSS&lt;/strong&gt; scores are published daily by FIRST at &lt;a href="https://api.first.org/data/v1/epss" rel="noopener noreferrer"&gt;api.first.org/data/v1/epss&lt;/a&gt; — query by CVE ID and get back the current probability plus a percentile rank. &lt;strong&gt;CISA KEV&lt;/strong&gt; is a single JSON file at &lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog" rel="noopener noreferrer"&gt;cisa.gov's known-exploited-vulnerabilities feed&lt;/a&gt;, updated as CISA confirms new active exploitation; there's no per-CVE lookup API, so most tooling downloads and caches the full catalog. Combined with CVSS from NVD or the CVE's own CNA record, this is enough to build the priority order above by hand — or a scanner can fetch and merge all three automatically, which is what removes the manual step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DepWarden does with this
&lt;/h2&gt;

&lt;p&gt;DepWarden fetches EPSS scores and KEV status daily from their respective sources alongside the OSV vulnerability mirror. In the scan results, KEV findings are called out first with a red flag, EPSS scores are shown alongside CVSS so you can compare them at a glance, and the "Fix these first" ranking uses a composite of these signals — not a raw CVSS sort — to produce a list ordered by real-world risk. The goal is to put the three actionable fixes at the top, not the eighty theoretical ones.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devsecops</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <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>
