<?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: Samuel Rojas</title>
    <description>The latest articles on DEV Community by Samuel Rojas (@samuelrojasdev).</description>
    <link>https://dev.to/samuelrojasdev</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%2F4241464%2F06afdc5d-cdb2-4c92-bdb6-284f67fe2dd3.jpg</url>
      <title>DEV Community: Samuel Rojas</title>
      <link>https://dev.to/samuelrojasdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samuelrojasdev"/>
    <language>en</language>
    <item>
      <title>Fixing one bug in my security tool would have hidden every alert in my test project</title>
      <dc:creator>Samuel Rojas</dc:creator>
      <pubDate>Thu, 12 Nov 2026 23:53:40 +0000</pubDate>
      <link>https://dev.to/samuelrojasdev/fixing-one-bug-in-my-security-tool-would-have-hidden-every-alert-in-my-test-project-l5c</link>
      <guid>https://dev.to/samuelrojasdev/fixing-one-bug-in-my-security-tool-would-have-hidden-every-alert-in-my-test-project-l5c</guid>
      <description>&lt;p&gt;I maintain &lt;a href="https://github.com/samuelrojas-dev/depquiet" rel="noopener noreferrer"&gt;depquiet&lt;/a&gt;. It sits on top of pip-audit, uv audit and npm audit and tries to cut down the noise. The one rule I care about is that it should never hide something real.&lt;/p&gt;

&lt;p&gt;This week I found out how close I came to breaking it with what looked like a simple bug fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;depquiet had an EPSS filter. EPSS is a score from FIRST.org that estimates how likely a CVE is to get exploited in the next 30 days. My defaults were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;under 10%: filter it out&lt;/li&gt;
&lt;li&gt;under 30%: move it to "review"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I picked those numbers because they felt sensible. Most CVEs never get exploited, so why bug people about them?&lt;/p&gt;

&lt;p&gt;Then I noticed the filter basically never ran on Python projects. EPSS only works with CVE ids, and pip-audit gives you ids like &lt;code&gt;PYSEC-2023-74&lt;/code&gt;. uv audit gives you &lt;code&gt;GHSA-...&lt;/code&gt;. The CVE is there, but as an alias, and depquiet wasn't looking at aliases. No CVE, no score, no filtering.&lt;/p&gt;

&lt;p&gt;Easy fix, right? Look up the alias.&lt;/p&gt;

&lt;p&gt;But that would turn the filter on for every Python user at once, so I stopped and thought about what it would actually throw away. I didn't like the answer, so I shipped the alias fix and also stopped EPSS from filtering by default. More on why below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I ran it on real data
&lt;/h2&gt;

&lt;p&gt;After the release I made a tiny project with three old pins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests==2.25.0
urllib3==1.26.4
jinja2==2.11.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pip-audit found 22 vulnerabilities. With the alias fix, all 22 got an EPSS score. The highest was 4%. The lowest was 0.2%.&lt;/p&gt;

&lt;p&gt;So if I had only fixed the alias and kept my 10% default, the filter would have thrown away all 22. Every single one. That list includes requests leaking your &lt;code&gt;.netrc&lt;/code&gt; credentials and urllib3 forwarding &lt;code&gt;Proxy-Authorization&lt;/code&gt; headers. The 30% rule would have pushed all of them down to "review" too.&lt;/p&gt;

&lt;p&gt;I knew it would filter a lot. I didn't expect it to filter everything. The tool would have told me the project was fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong
&lt;/h2&gt;

&lt;p&gt;I was treating EPSS like it says something about my app. It doesn't. It tells you how likely a CVE is to be exploited somewhere on the internet. A 0.5% score means nobody is spraying that bug at scale right now. It doesn't tell you if your code passes credentials through a proxy you don't control.&lt;/p&gt;

&lt;p&gt;Using that number to hide alerts means a global statistic gets to decide what you see about your own project. That's not what I want this tool to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;In v0.42:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EPSS doesn't filter anything by default anymore. It just sorts. Known exploited (CISA KEV) go first, then highest EPSS, then severity.&lt;/li&gt;
&lt;li&gt;If you still want the filter, &lt;code&gt;--epss-threshold 0.05&lt;/code&gt; works. You just have to turn it on yourself.&lt;/li&gt;
&lt;li&gt;Lookups use the CVE alias now, so Python and npm findings actually get scores.&lt;/li&gt;
&lt;li&gt;Tiny scores show as &lt;code&gt;0.4%&lt;/code&gt; instead of &lt;code&gt;0%&lt;/code&gt;. Seeing "0%" next to a credential leak felt wrong.
Going through the whole release afterwards also turned up two other things I'm not happy about. &lt;code&gt;depquiet init&lt;/code&gt; was still writing the old 10% threshold into new config files. And if you passed a &lt;code&gt;--kev-file&lt;/code&gt; that didn't exist, depquiet crashed with exit code 1, which is the same code it uses for "found vulnerabilities". So a CI job that tolerates findings would just pass, without ever checking KEV. Both are fixed in 0.42.1.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you build tools like this
&lt;/h2&gt;

&lt;p&gt;Run your filter on real data and look at what it throws away, not just what it keeps. My numbers looked fine on paper. On an actual project they would have hidden everything.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;pip install depquiet&lt;/code&gt;, or use the GitHub Action &lt;code&gt;samuelrojas-dev/depquiet@v0&lt;/code&gt;. If you think I'm wrong about EPSS, I'd like to hear why: &lt;a href="https://github.com/samuelrojas-dev/depquiet" rel="noopener noreferrer"&gt;github.com/samuelrojas-dev/depquiet&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>I audited my own security tool for every place it said "you're safe" when you weren't. I found 10.</title>
      <dc:creator>Samuel Rojas</dc:creator>
      <pubDate>Thu, 12 Nov 2026 16:31:35 +0000</pubDate>
      <link>https://dev.to/samuelrojasdev/i-audited-my-own-security-tool-for-every-place-it-said-youre-safe-when-you-werent-i-found-10-4081</link>
      <guid>https://dev.to/samuelrojasdev/i-audited-my-own-security-tool-for-every-place-it-said-youre-safe-when-you-werent-i-found-10-4081</guid>
      <description>&lt;p&gt;I maintain &lt;a href="https://github.com/samuelrojas-dev/depquiet" rel="noopener noreferrer"&gt;depquiet&lt;/a&gt;, an open-source (MIT) triage layer for &lt;code&gt;pip-audit&lt;/code&gt;, &lt;code&gt;uv audit&lt;/code&gt; and &lt;code&gt;npm audit&lt;/code&gt;. It drops the alerts that only reach your dev tooling and tells you which of the rest you can actually fix.&lt;/p&gt;

&lt;p&gt;A triage tool has one job it must never get wrong: it must not hide a real alert. So this week I went through every feature with one question: &lt;strong&gt;where can depquiet say "you're protected" or "this doesn't matter" when that isn't true?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I found 10 places. All are fixed in v0.40.4. Here they are, because if you build security tooling, you probably have some of the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;--fail-on high&lt;/code&gt; let every Python alert through
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pip-audit&lt;/code&gt; and &lt;code&gt;uv audit&lt;/code&gt; report no severity. depquiet filled the gap with a placeholder, &lt;code&gt;moderate&lt;/code&gt;, and then compared that placeholder against &lt;code&gt;--fail-on high&lt;/code&gt; as if a scanner had measured it. On a real project (pallets/flask), two ACTION alerts: exit 1 without the flag, &lt;strong&gt;exit 0 with &lt;code&gt;--fail-on high&lt;/code&gt;&lt;/strong&gt;. And the README recommended &lt;code&gt;--fail-on high&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now an alert with no severity counts as meeting any threshold, with a note on stderr.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The same placeholder was shown everywhere
&lt;/h2&gt;

&lt;p&gt;Even after the gate fix, "moderate" still appeared in the terminal, the Job Summary, the HTML report, the JSON, GitHub annotations, SARIF and the exported SBOM's CycloneDX ratings. SARIF marked those alerts as &lt;code&gt;warning&lt;/code&gt;, so code scanning merge protection set to block on errors let them through.&lt;/p&gt;

&lt;p&gt;Now it says &lt;code&gt;unknown&lt;/code&gt; everywhere, and an ACTION alert with no severity is a SARIF &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;--fix&lt;/code&gt; "fixed" vulnerabilities by installing into the wrong environment
&lt;/h2&gt;

&lt;p&gt;In a uv project scanned with pip-audit, &lt;code&gt;--fix&lt;/code&gt; ran &lt;code&gt;pip install pkg==fix&lt;/code&gt; &lt;strong&gt;with depquiet's own interpreter&lt;/strong&gt;. The project's &lt;code&gt;uv.lock&lt;/code&gt; did not change, and the report said ✅ Fixed.&lt;/p&gt;

&lt;p&gt;Now it runs &lt;code&gt;uv lock --upgrade-package&lt;/code&gt; and says what blocks the fix when uv refuses. Python projects without a lockfile are never pip-installed; you get the requirement to raise instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;--license&lt;/code&gt; checked depquiet's environment, not your project
&lt;/h2&gt;

&lt;p&gt;For Python, the license scan listed what &lt;code&gt;pip list&lt;/code&gt; saw where depquiet runs. On flask it reported &lt;strong&gt;187 packages&lt;/strong&gt;, including playwright, pytest and depquiet itself. A GPL dependency of your project that was not installed next to depquiet was never read, and the check could PASS.&lt;/p&gt;

&lt;p&gt;Now it reads your lockfile (78 packages on flask, matching its &lt;code&gt;uv.lock&lt;/code&gt;) and each license from your own virtualenv or PyPI for that exact version. An unread license is UNKNOWN and fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;--kev&lt;/code&gt; never matched uv audit findings
&lt;/h2&gt;

&lt;p&gt;The CISA KEV catalog lists CVE ids. uv audit names advisories by GHSA id and pip-audit by PYSEC or GHSA, with the CVE as an alias, and depquiet dropped the aliases. So an actively exploited vulnerability in a dev-only package was never escalated.&lt;/p&gt;

&lt;p&gt;Now aliases are kept and KEV matches any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A comment turned a policy rule into "filter everything"
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filter&lt;/span&gt;
&lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# noise only
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trailing comment broke the list, the severity condition was dropped, and the rule filtered a &lt;strong&gt;critical alert with EPSS 0.9&lt;/strong&gt;. The same happened with a typo (&lt;code&gt;severty:&lt;/code&gt;), an unreadable &lt;code&gt;epss_below&lt;/code&gt;, and &lt;code&gt;severity: medium&lt;/code&gt;, the value my own README documented (internally it is &lt;code&gt;moderate&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Now comments are stripped, &lt;code&gt;medium&lt;/code&gt; means &lt;code&gt;moderate&lt;/code&gt;, and a rule with anything unreadable is skipped with a warning instead of widening.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Temporary suppressions became permanent
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;.depquiet-ignore&lt;/code&gt;, &lt;code&gt;expires: 2025-06-01  # temp&lt;/code&gt;, &lt;code&gt;2025/06/01&lt;/code&gt; or &lt;code&gt;2025-13-01&lt;/code&gt; was read as "no expiry". Now an unreadable date counts as expired.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. &lt;code&gt;.depquiet.yaml&lt;/code&gt; slips weakened the scan
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kev: true  # exploited&lt;/code&gt; turned KEV off. &lt;code&gt;always_flag: [lodash]  # core&lt;/code&gt; left lodash unprotected. &lt;code&gt;epss_threshold: 5&lt;/code&gt; (meant as 5%) was accepted, which filters every alert with an EPSS score. Now comments are stripped, EPSS must be between 0 and 1, and unknown keys are reported.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Fix versions from the wrong release line
&lt;/h2&gt;

&lt;p&gt;Scanners list every release line that has the fix (&lt;code&gt;0.3.81, 1.2.22&lt;/code&gt;). depquiet took the first one listed, which could be older than what you have installed. Now it takes the nearest fix above your version.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. The PR comment cleanup deleted human comments
&lt;/h2&gt;

&lt;p&gt;The GitHub Action removed any PR comment starting with "## 🔍 depquiet", whoever wrote it. Now it only removes its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  And one number I had to correct
&lt;/h2&gt;

&lt;p&gt;Two days ago I published that 54% of the fixes scanners suggest can't be installed. That was an upper bound: my first run checked only one fix per package. The exact per-alert figure, on 18 public uv projects, is &lt;strong&gt;502 of 991 (51%)&lt;/strong&gt;. The more useful finding came with it: in &lt;strong&gt;58 of the 72 blocked upgrades (81%)&lt;/strong&gt;, the blocker is a pin in the project's own &lt;code&gt;pyproject.toml&lt;/code&gt;. "Update to X" usually really means "widen your own pin". &lt;a href="https://github.com/samuelrojas-dev/depquiet/tree/main/benchmarks/fixcheck_study" rel="noopener noreferrer"&gt;Data, script and every resolver message&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Almost every bug had the same shape: &lt;strong&gt;a missing or unreadable value was replaced by a default that sounded safe&lt;/strong&gt;. "No severity" became "moderate". "Unreadable date" became "no expiry". "Unknown key" became "no condition". "Not installed here" became "not a dependency".&lt;/p&gt;

&lt;p&gt;The fix was the same each time: when the tool doesn't know, it has to say so, and treat "unknown" as the risky case, never the safe one.&lt;/p&gt;

&lt;p&gt;If you build security tooling, it's worth grepping for your defaults.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;pip install depquiet&lt;/code&gt;, or the GitHub Action &lt;code&gt;samuelrojas-dev/depquiet@v0&lt;/code&gt;. Issues and disagreements welcome: &lt;a href="https://github.com/samuelrojas-dev/depquiet" rel="noopener noreferrer"&gt;github.com/samuelrojas-dev/depquiet&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
