<?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: Zein Saleh</title>
    <description>The latest articles on DEV Community by Zein Saleh (@zein_saleh_866d073614f017).</description>
    <link>https://dev.to/zein_saleh_866d073614f017</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%2F4010254%2Fb50f6242-cfd6-4ae6-b7f8-ce1cd31ec0d7.jpg</url>
      <title>DEV Community: Zein Saleh</title>
      <link>https://dev.to/zein_saleh_866d073614f017</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zein_saleh_866d073614f017"/>
    <language>en</language>
    <item>
      <title>I found 10 bugs in my own security scanner. Here's what they taught me about false positives.</title>
      <dc:creator>Zein Saleh</dc:creator>
      <pubDate>Wed, 01 Jul 2026 02:54:46 +0000</pubDate>
      <link>https://dev.to/zein_saleh_866d073614f017/i-found-10-bugs-in-my-own-security-scanner-heres-what-they-taught-me-about-false-positives-4o8m</link>
      <guid>https://dev.to/zein_saleh_866d073614f017/i-found-10-bugs-in-my-own-security-scanner-heres-what-they-taught-me-about-false-positives-4o8m</guid>
      <description>&lt;h2&gt;
  
  
  I found 10 bugs in my own security scanner. Here's what they taught me about false positives.
&lt;/h2&gt;

&lt;p&gt;I built a VS Code extension that scans code for leaked secrets, PII, and security vulnerabilities before you commit. A few weeks in, I sat down and did something most tool builders put off: I went looking for everywhere my own scanner was wrong.&lt;/p&gt;

&lt;p&gt;Not "wrong" as in crashing. Wrong as in flagging things that weren't actually problems, and — worse — missing things that were.&lt;/p&gt;

&lt;p&gt;For a security tool, false positives aren't a minor annoyance. They're the thing that gets a tool uninstalled. The first time a scanner cries wolf on &lt;code&gt;loginToken&lt;/code&gt; because it contains the substring "log," a developer stops trusting every other finding it makes, including the real ones. So I spent a focused pass hunting for exactly this kind of bug. Found 10. Here they are, with the actual root cause for each — because "we fixed some bugs" isn't useful to anyone, but the specific ways pattern-matching security tools go wrong might be.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Substring matching instead of call-site matching
&lt;/h3&gt;

&lt;p&gt;A rule meant to catch sensitive data in log statements was matching any variable name containing "log" — including &lt;code&gt;loginToken&lt;/code&gt;, which has nothing to do with logging. The regex was checking for the substring, not for an actual logging function call. Fixed by requiring the pattern to match a real log-call shape (&lt;code&gt;logger.info(...)&lt;/code&gt;, &lt;code&gt;console.log(...)&lt;/code&gt;, etc.), not just nearby text.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A dependency-pinning rule that fired on every file
&lt;/h3&gt;

&lt;p&gt;An "unpinned dependency" check was broad enough that it lit up on files that had nothing to do with dependencies at all. It needed to be scoped specifically to requirements/manifest-style files, not applied repo-wide.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Outdated key format for GCP credentials
&lt;/h3&gt;

&lt;p&gt;The private key detector only recognized the old &lt;code&gt;BEGIN RSA PRIVATE KEY&lt;/code&gt; PEM header. Real-world GCP service account keys use PKCS#8 format (&lt;code&gt;BEGIN PRIVATE KEY&lt;/code&gt;), which the pattern didn't cover. This wasn't a false positive — it was a false negative, arguably worse, since it means real leaked keys were silently passing through.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Credit card numbers with no checksum
&lt;/h3&gt;

&lt;p&gt;Any 16-digit number was getting flagged as a potential credit card. Any 16-digit number. Order IDs, invoice numbers, random test fixtures — all of it. Added Luhn checksum validation, the same algorithm real payment systems use to validate card numbers, so only mathematically plausible card numbers get flagged.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Passport numbers matching SKUs and license plates
&lt;/h3&gt;

&lt;p&gt;An unanchored regex for passport number formats was broad enough to match product SKUs and vehicle plates. Now it requires a nearby label ("passport," "passport no.," etc.) before it fires.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. IPv6 shorthand wasn't recognized
&lt;/h3&gt;

&lt;p&gt;The IPv6 detector only matched the full 8-group form. Real-world IPv6 addresses are almost always written in compressed shorthand using &lt;code&gt;::&lt;/code&gt;, which the original pattern missed entirely. Another false negative.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Flagging every &lt;code&gt;import random&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A rule meant to catch insecure random number generation (using &lt;code&gt;random&lt;/code&gt; instead of a cryptographically secure source for things like tokens or session IDs) was flagging the import itself, regardless of what it was actually used for. Scoped it down to the specific insecure call sites, not the import statement.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Path traversal rule flagged normal file reads
&lt;/h3&gt;

&lt;p&gt;Any call to &lt;code&gt;open(data)&lt;/code&gt; was getting flagged as a path traversal risk, even when &lt;code&gt;data&lt;/code&gt; had nothing to do with user input. Scoped to cases where the path actually derives from request data.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. SSH public keys flagged as critical secrets
&lt;/h3&gt;

&lt;p&gt;This one's almost funny in hindsight: SSH &lt;em&gt;public&lt;/em&gt; keys were being flagged as critical leaked secrets. They're called public keys because they're meant to be shared. Pulled them out of the secrets detector entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. The same finding reported twice
&lt;/h3&gt;

&lt;p&gt;A JS &lt;code&gt;eval()&lt;/code&gt; call was tripping both the Python-oriented rule and the JS-oriented rule for unsafe eval usage, so it showed up as two separate findings for one line of code. Language-scoped the rules so each finding only fires once.&lt;/p&gt;




&lt;h3&gt;
  
  
  The bigger lesson
&lt;/h3&gt;

&lt;p&gt;Almost every bug on this list comes down to the same root cause: &lt;strong&gt;a pattern that matched text instead of matching meaning.&lt;/strong&gt; "Contains the word log," "is 16 digits," "is inside a &lt;code&gt;BEGIN...END&lt;/code&gt; block" — these are all &lt;em&gt;proxies&lt;/em&gt; for the thing you actually care about, and proxies break in both directions. They flag things that aren't real problems (noise), and they miss things that are (the actually dangerous outcome for a security tool).&lt;/p&gt;

&lt;p&gt;The fix, in almost every case, wasn't a smarter model or more data. It was going back and asking "what's the actual signal here?" — a checksum instead of a digit count, a call-site instead of a substring, a label instead of a bare pattern.&lt;/p&gt;

&lt;p&gt;I also added a zero-dependency test suite so these specific regressions can't silently come back, and fixed a handful of consistency bugs where the editor and the commit-hook CLI were enforcing different rules on the same code — which is its own quiet source of false trust ("it didn't complain in my editor" ≠ "it won't block my commit").&lt;/p&gt;

&lt;p&gt;None of this makes the scanner perfect. But I'd rather ship "we found 10 ways this was wrong and fixed them" than pretend it was right from day one. If you're evaluating any static analysis or secret-scanning tool, "have they published their false positive fixes" is a better trust signal than any accuracy percentage on a landing page.&lt;/p&gt;

&lt;p&gt;Veilo is a free VS Code extension — detection runs 100% locally, nothing leaves your machine. If you want to see if it catches anything in your own repo: &lt;a href="https://marketplace.visualstudio.com/items?itemName=veilo.veilo" rel="noopener noreferrer"&gt;marketplace.visualstudio.com/items?itemName=veilo.veilo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>security</category>
      <category>testing</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
