<?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: Gap Hunter Labs</title>
    <description>The latest articles on DEV Community by Gap Hunter Labs (@gap_hunterlabs).</description>
    <link>https://dev.to/gap_hunterlabs</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%2F4155167%2Fb577decb-cffd-42be-b794-98eaaa00de78.png</url>
      <title>DEV Community: Gap Hunter Labs</title>
      <link>https://dev.to/gap_hunterlabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gap_hunterlabs"/>
    <language>en</language>
    <item>
      <title>Does Dependency Security Really Need the Cloud?</title>
      <dc:creator>Gap Hunter Labs</dc:creator>
      <pubDate>Fri, 02 Oct 2026 07:53:00 +0000</pubDate>
      <link>https://dev.to/gap_hunterlabs/does-dependency-security-really-need-the-cloud-3pki</link>
      <guid>https://dev.to/gap_hunterlabs/does-dependency-security-really-need-the-cloud-3pki</guid>
      <description>&lt;p&gt;&lt;strong&gt;The question&lt;/strong&gt;&lt;br&gt;
I kept asking myself a simple question:&lt;br&gt;
Why does checking a dependency for a known vulnerability need to send anything to the cloud?&lt;br&gt;
This isn't an argument against &lt;strong&gt;cloud-based security tooling&lt;/strong&gt;.&lt;br&gt;
There are good reasons to use centralized services, &lt;em&gt;especially&lt;/em&gt; when you need continuously updated data, organization-wide policies, reporting, or large-scale analysis.&lt;/p&gt;

&lt;p&gt;I was interested in a narrower engineering problem:&lt;br&gt;
&lt;strong&gt;What would a dependency vulnerability analyzer look like if the core analysis had to work entirely locally?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That constraint led me to build &lt;strong&gt;Dependency Vulnerability Companio&lt;/strong&gt;n, an IntelliJ-family plugin that analyzes dependency declarations against a vulnerability database bundled with the plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraints were deliberately strict:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account&lt;/li&gt;
&lt;li&gt;No token&lt;/li&gt;
&lt;li&gt;No API key&lt;/li&gt;
&lt;li&gt;No network call during analysis&lt;/li&gt;
&lt;li&gt;No runtime vulnerability database download
The interesting part wasn't making an IDE inspection.
The interesting part was seeing what those constraints did to the architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smallest useful pipeline&lt;br&gt;
A dependency manifest already contains much of the information needed for a basic vulnerability check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For example:&lt;/strong&gt;&lt;br&gt;
&lt;br&gt;
com.fasterxml.jackson.core&lt;br&gt;
jackson-databind&lt;br&gt;
...&lt;br&gt;
&lt;br&gt;
If I can extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ecosystem&lt;/li&gt;
&lt;li&gt;package&lt;/li&gt;
&lt;li&gt;version&lt;/li&gt;
&lt;li&gt;source location
then I can compare that information against a local vulnerability dataset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The initial pipeline therefore became:&lt;/strong&gt;&lt;br&gt;
Project file -&amp;gt; Dependency parser -&amp;gt; Normalized dependency -&amp;gt; Version matching -&amp;gt; Local vulnerability database -&amp;gt; IntelliJ inspection&lt;br&gt;
There is no remote lookup in that path.&lt;/p&gt;

&lt;p&gt;That sounds simple, but every box introduces its own set of problems.&lt;/p&gt;

&lt;p&gt;Keeping the vulnerability database local&lt;br&gt;
The most important architectural decision was to treat vulnerability data as part of the plugin rather than as a runtime service dependency.&lt;br&gt;
The plugin contains a versioned vulnerability database inside its JAR.&lt;/p&gt;

&lt;p&gt;The current dataset is derived from OSV bulk data and contains a selected snapshot of high- and critical-severity advisories from the GitHub Advisory Database.&lt;/p&gt;

&lt;p&gt;At runtime, the plugin reads that data locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This changes the architecture from:&lt;/strong&gt;&lt;br&gt;
IDE -&amp;gt; Plugin -&amp;gt; Remote vulnerability service -&amp;gt; Response&lt;br&gt;
to:&lt;br&gt;
IDE -&amp;gt; Plugin -&amp;gt; Local vulnerability database &amp;amp; Dependency analysis&lt;br&gt;
That gives the analyzer a useful property: the core vulnerability check doesn't depend on network availability.&lt;/p&gt;

&lt;p&gt;It also makes the trade-off explicit. A bundled database is not a real-time feed.&lt;/p&gt;

&lt;p&gt;If a new advisory is published after a plugin release, that advisory isn't magically available to an existing installation.&lt;/p&gt;

&lt;p&gt;The dataset therefore becomes part of the release lifecycle.&lt;br&gt;
I consider that a much better engineering trade-off than pretending that offline and real-time can be achieved simultaneously without additional infrastructure.&lt;/p&gt;

&lt;p&gt;Dependency extraction&lt;br&gt;
The plugin currently works with dependency declarations in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pom.xml&lt;/li&gt;
&lt;li&gt;build.gradle&lt;/li&gt;
&lt;li&gt;build.gradle.kts&lt;/li&gt;
&lt;li&gt;package.json&lt;/li&gt;
&lt;li&gt;requirements.txt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One design decision I made was to keep the initial parsers relatively lightweight. I didn't need to build a complete compiler for each ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The analyzer primarily needs to answer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What dependency is declared?&lt;/li&gt;
&lt;li&gt;What version is associated with it?&lt;/li&gt;
&lt;li&gt;Where is that declaration in the source file?
The output can then be normalized into a common representation:
Dependency&lt;/li&gt;
&lt;li&gt;ecosystem&lt;/li&gt;
&lt;li&gt;package&lt;/li&gt;
&lt;li&gt;version&lt;/li&gt;
&lt;li&gt;source file&lt;/li&gt;
&lt;li&gt;source range&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that representation exists, the vulnerability matching layer doesn't need to care whether the original declaration came from Maven XML, Gradle Kotlin DSL, npm, or pip.&lt;br&gt;
pom.xml, build.gradle, build.gradle.kts, package.json, requirements.txt -&amp;gt; Dependency model -&amp;gt; Vulnerability matcher&lt;br&gt;
The parsers are ecosystem-specific. The matching layer doesn't have to be.&lt;/p&gt;

&lt;p&gt;Why source offsets matter&lt;br&gt;
Finding a vulnerable package is only half of the problem. The result needs to become useful inside the IDE.&lt;/p&gt;

&lt;p&gt;For example, if the parser discovers com.example:library:1.2.3, I also need to know where that dependency occurs in the original document.&lt;br&gt;
That means the extracted representation needs source information, not just semantic information:&lt;br&gt;
dependency declaration -&amp;gt; extract dependency &amp;amp; retain source range -&amp;gt; lookup vulnerability -&amp;gt; create IntelliJ inspection &amp;amp; highlight original declaration&lt;br&gt;
The final result isn't just "This package is vulnerable."&lt;br&gt;
It is: "This declaration in the file you're currently editing matches a known vulnerable version."&lt;br&gt;
That distinction makes the analysis much more useful.&lt;br&gt;
Version comparison is where the problem gets interesting&lt;br&gt;
The vulnerability lookup itself isn't particularly complicated. Version matching is.&lt;/p&gt;

&lt;p&gt;A vulnerability record can describe affected versions using ranges, boundaries, or ecosystem-specific semantics.&lt;br&gt;
So the analyzer has to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Is 1.2.3 affected?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is 1.2.4 affected?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does this version satisfy the affected range?&lt;/strong&gt;
And those questions don't necessarily have identical semantics across ecosystems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A dependency declared as == 1.2.3 is fundamentally different from ^1.2.3 or &amp;gt;= 1.2.&lt;/p&gt;

&lt;p&gt;This is where I think security tooling needs to be conservative. If the analyzer cannot reliably interpret a dependency expression, it shouldn't silently turn an approximation into a security finding.&lt;/p&gt;

&lt;p&gt;A false sense of precision is worse than an explicitly unsupported case.&lt;/p&gt;

&lt;p&gt;What the analyzer doesn't currently resolve&lt;br&gt;
This is also why the limitations are part of the technical design.&lt;/p&gt;

&lt;p&gt;For example, a Maven declaration such as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&amp;gt; ${jackson.version}&lt;/strong&gt;&lt;br&gt;
isn't an ordinary literal version. The analyzer would need to resolve the property before it could reliably perform version matching. Gradle introduces similar problems through mechanisms such as version catalogs.&lt;br&gt;
The current implementation intentionally doesn't try to become a complete replacement for Maven, Gradle, npm, or pip dependency resolution.&lt;br&gt;
Instead, it focuses on declarations that can be analyzed reliably within the information available to the plugin.&lt;br&gt;
npm ranges expose the same problem&lt;br&gt;
Consider:&lt;br&gt;
{&lt;br&gt;
"dependencies": {&lt;br&gt;
"some-package": "^1.4.0"&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
The declared range doesn't necessarily tell me the exact version installed on the machine. The lockfile may.&lt;br&gt;
That creates an important distinction between declared dependency and resolved dependency.&lt;br&gt;
A more complete npm analysis should be able to follow:&lt;br&gt;
package.json -&amp;gt; declared range -&amp;gt; package-lock.json -&amp;gt; resolved version -&amp;gt; vulnerability matching&lt;br&gt;
The dependency graph is a separate consumer of the same model&lt;br&gt;
The plugin also includes a Dependency Graph view. Architecturally, it made sense to treat it as another consumer of the dependency information already extracted:&lt;br&gt;
Project -&amp;gt; dependency model -&amp;gt; vulnerability analysis / dependency graph&lt;br&gt;
The graph UI uses a bundled copy of Cytoscape.js and runs locally through IntelliJ's embedded browser support.&lt;br&gt;
For Maven, it can inspect .m2/repository. For pip, it can inspect the local installed environment.&lt;/p&gt;

&lt;p&gt;The important part was keeping the dependency information and the consumers of that information separate.&lt;br&gt;
Offline doesn't mean complete&lt;br&gt;
An offline architecture provides useful properties:&lt;br&gt;
** * &lt;strong&gt;No network dependency during analysis&lt;/strong&gt;&lt;br&gt;
** * &lt;strong&gt;No account or API token required&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable local execution
But it doesn't provide:&lt;/li&gt;
&lt;li&gt;Real-time advisory updates&lt;/li&gt;
&lt;li&gt;Complete vulnerability coverage&lt;/li&gt;
&lt;li&gt;Automatic knowledge of non-visible dependencies
A tool that says "no vulnerability found" is only stating: "No matching vulnerability was found in the data and dependency information available to this analyzer."
Why IntelliJ?
The reason for putting the analysis inside IntelliJ is about context.
If I'm looking at build.gradle.kts and a dependency has a known vulnerability, the source declaration is the most useful place to surface that information.
Some dependency questions can be answered at the exact place where the dependency is being edited.
The resulting architecture
Vulnerability data -&amp;gt; Local database -&amp;gt; Vulnerability matching -&amp;gt; IntelliJ inspection
Project files -&amp;gt; Dependency extraction -&amp;gt; Dependency model -&amp;gt; Vulnerability matching / Dependency graph
What I would work on next&lt;/li&gt;
&lt;li&gt;Lockfile-aware dependency analysis: Resolving actual installed versions from package-lock.json.&lt;/li&gt;
&lt;li&gt;Better version resolution: Handling Maven properties, Gradle version catalogs, and indirection.&lt;/li&gt;
&lt;li&gt;More complete dependency graphs: Representing deeper resolved dependency trees.&lt;/li&gt;
&lt;li&gt;Better ecosystem-specific semantics: Refining version comparison rules per package manager.
What this experiment actually showed me
The interesting result wasn't simply that vulnerability detection can work offline.
The interesting part was discovering how many assumptions appear once the network is removed:&lt;/li&gt;
&lt;li&gt;The vulnerability dataset becomes a build artifact.&lt;/li&gt;
&lt;li&gt;Version resolution becomes an explicit problem.&lt;/li&gt;
&lt;li&gt;Source locations become part of the analysis model.&lt;/li&gt;
&lt;li&gt;Unsupported cases must remain explicit instead of guessed.
Vulnerability inspection does not inherently require a network request. Once that became a hard constraint, the rest of the implementation became a series of engineering trade-offs.
The project is open source:
&lt;a href="https://github.com/GapHunterLabs/dependency-vulnerability-companion" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;View Dependency Vulnerability Companion on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>security</category>
      <category>gradle</category>
      <category>opensource</category>
    </item>
    <item>
      <title>JetBrains Said No Static Tool Catches This Freeze Bug. We Built One — And Found a Real Instance in Their Own Code.</title>
      <dc:creator>Gap Hunter Labs</dc:creator>
      <pubDate>Thu, 01 Oct 2026 15:31:42 +0000</pubDate>
      <link>https://dev.to/gap_hunterlabs/jetbrains-said-no-static-tool-catches-this-freeze-bug-we-built-one-and-found-a-real-instance-in-18c8</link>
      <guid>https://dev.to/gap_hunterlabs/jetbrains-said-no-static-tool-catches-this-freeze-bug-we-built-one-and-found-a-real-instance-in-18c8</guid>
      <description>&lt;p&gt;In September 2025 and again in March 2026, JetBrains' own Platform engineering team published two posts about a specific, recurring cause of IntelliJ IDE freezes: a background thread calling a blocking, non-cancellable read-lock API (ReadAction.compute(), ReadAction.run(), Application#runReadAction()) while a write action is pending. The March post said it plainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"many reports actually show problems in plugins that contain that single erroneous pattern"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And just as plainly: there's no static tool that catches this today. Detection is manual — reading a thread dump after the freeze already happened in someone's real IDE.&lt;/p&gt;

&lt;p&gt;That's a strange gap for a static-analysis catalog to walk past. So i didn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What i built&lt;/strong&gt; — Background ReadAction Freeze Companion, a free, open-source IntelliJ Platform plugin that statically traces exactly this pattern. It's interprocedural — it walks the real call graph from four recognized background-thread entry points to a blocking read-lock call, however many method calls deep. It distinguishes the formally deprecated read-lock APIs from the one that isn't but is mechanically identical and explicitly discouraged in its own Javadoc. A hit where the surrounding code already checks for cancellation gets downgraded, not suppressed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/GapHunterLabs/background-readaction-freeze-companion" rel="noopener noreferrer"&gt;https://github.com/GapHunterLabs/background-readaction-freeze-companion&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validating it against the real thing&lt;/strong&gt; — i checked it out against intellij-community itself. First pass caught something for the right reason to not flag it: a real ReadAction.run() call in the Mercurial VCS integration that traces back to the EDT, not background — correctly held back. The same pass surfaced a real gap in our own coverage (a background entry point we hadn't covered yet); i added it the same day. Then, pointed at the Java test-execution module, it surfaced a clean, direct hit in SearchForTestsTask: nine lines apart, in the same method, one branch uses the cancellable pattern, the sibling branch — reachable through an ordinary "run tests before indexing finishes" interaction — uses the blocking one the Platform team's own post named.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I filed it&lt;/strong&gt;: &lt;a href="https://youtrack.jetbrains.com/issue/IJPL-254654" rel="noopener noreferrer"&gt;IJPL-254654&lt;/a&gt;, with the exact source reference and a suggested fix mirroring the pattern already used nine lines above it.&lt;/p&gt;

&lt;p&gt;Why this, and why now — I read what a platform team said was a real, unsolved problem for them, built the tool that problem was asking for, and used it exactly the way it was designed — against real code, every finding hand-verified before it went anywhere.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>python</category>
    </item>
  </channel>
</rss>
