<?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: Dark Master</title>
    <description>The latest articles on DEV Community by Dark Master (@darkmaster0345).</description>
    <link>https://dev.to/darkmaster0345</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%2F3882200%2F17cfc069-222c-4bb0-941b-6ed202e24a91.jpeg</url>
      <title>DEV Community: Dark Master</title>
      <link>https://dev.to/darkmaster0345</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/darkmaster0345"/>
    <language>en</language>
    <item>
      <title>I scanned GitLab's source code and found 2,449 undocumented API routes</title>
      <dc:creator>Dark Master</dc:creator>
      <pubDate>Sat, 18 Jul 2026 12:29:50 +0000</pubDate>
      <link>https://dev.to/darkmaster0345/i-scanned-gitlabs-source-code-and-found-2449-undocumented-api-routes-3l0c</link>
      <guid>https://dev.to/darkmaster0345/i-scanned-gitlabs-source-code-and-found-2449-undocumented-api-routes-3l0c</guid>
      <description>&lt;p&gt;Last week I pointed a static analysis tool I built at GitLab's public source code. It found 2,449 routes in the actual Ruby code that don't exist anywhere in the official API documentation.&lt;/p&gt;

&lt;p&gt;Then I ran it on Mastodon. 601 undocumented routes.&lt;/p&gt;

&lt;p&gt;These aren't bugs — they're shadow APIs. Routes that exist, respond to requests, but were never documented. No auth requirements listed. No rate limits specified. No deprecation warnings. Just... there.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a shadow API?
&lt;/h2&gt;

&lt;p&gt;A shadow API is any route your server handles that isn't in your OpenAPI/Swagger spec. They appear for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A developer added an internal endpoint and forgot to document it&lt;/li&gt;
&lt;li&gt;A route was deprecated but never removed from code&lt;/li&gt;
&lt;li&gt;A feature was half-built — routes exist but docs were never written&lt;/li&gt;
&lt;li&gt;A framework auto-generates routes (Rails resources, NestJS decorators) and nobody audited what got generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem: your security team can't protect what they don't know exists. Your API gateway can't rate-limit it. Your consumers can't predict when it'll break. And attackers can find it with a directory scan.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I found 2,449 of them in GitLab
&lt;/h2&gt;

&lt;p&gt;I built a CLI called &lt;strong&gt;shadowaudit&lt;/strong&gt;. It does static analysis — reads your actual framework code, extracts every route, then diffs it against your OpenAPI spec.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; ./gitlab &lt;span class="nt"&gt;--spec&lt;/span&gt; ./openapi.yaml &lt;span class="nt"&gt;--framework&lt;/span&gt; rails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For GitLab, the scanner:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parsed every &lt;code&gt;routes.rb&lt;/code&gt; and &lt;code&gt;config/routes/*.rb&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Expanded every &lt;code&gt;resources :projects&lt;/code&gt; call into its 7 REST routes&lt;/li&gt;
&lt;li&gt;Followed every &lt;code&gt;concern :reviewable&lt;/code&gt; mounted across multiple resources&lt;/li&gt;
&lt;li&gt;Added Grape API routes from &lt;code&gt;lib/api/*.rb&lt;/code&gt; — 1,017 routes from that alone&lt;/li&gt;
&lt;li&gt;Diffed all detected routes against the public GitLab API spec&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result: &lt;strong&gt;2,449 routes in code with no match in the spec.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some are intentional internals. Some are legacy. Some are genuinely undocumented public endpoints that clients are probably hitting right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool: shadowaudit
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; shadowaudit
shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; ./your-api &lt;span class="nt"&gt;--spec&lt;/span&gt; ./openapi.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7 frameworks supported:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Express (including 3-level nested router chains)&lt;/li&gt;
&lt;li&gt;Fastify&lt;/li&gt;
&lt;li&gt;NestJS (including &lt;code&gt;@Version()&lt;/code&gt; decorators)&lt;/li&gt;
&lt;li&gt;Koa&lt;/li&gt;
&lt;li&gt;Hapi&lt;/li&gt;
&lt;li&gt;Rails&lt;/li&gt;
&lt;li&gt;Grape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Three modes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default — find shadow routes (code not in spec):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRITICAL  POST   /api/v1/internal/reset      (no auth documented)
HIGH      GET    /api/v2/users/export        (no rate limit documented)
INFO      GET    /health                     (intentionally undocumented)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reverse mode — find dead spec entries (spec not in code):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml &lt;span class="nt"&gt;--reverse&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finds routes your spec documents that no longer exist in code. Dead docs that confuse your consumers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage scoring — how complete is your OpenAPI spec?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml &lt;span class="nt"&gt;--coverage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coverage Score: 67/100
  Descriptions:    142/200 endpoints  (71%)
  Parameters:      89/134 params      (66%)
  Response codes:  67/200 endpoints   (33.5%)  ← missing 4xx/5xx
  Security:        45/67 endpoints    (67%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CI integration — fails the build if shadow routes exist:&lt;/strong&gt;&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/workflows/api-audit.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Shadow API Audit&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx shadowaudit --dir . --spec openapi.yaml --format markdown &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit codes: &lt;code&gt;0&lt;/code&gt; = clean, &lt;code&gt;1&lt;/code&gt; = findings, &lt;code&gt;2&lt;/code&gt; = tool error. CI-safe by design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignore known-good routes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml &lt;span class="nt"&gt;--ignore-paths&lt;/span&gt; /health,/metrics,/ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What makes this different from existing tools
&lt;/h2&gt;

&lt;p&gt;Most shadow API detection tools work at runtime — they watch traffic and flag requests to unknown endpoints. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need production traffic to find them&lt;/li&gt;
&lt;li&gt;You find out AFTER deployment&lt;/li&gt;
&lt;li&gt;You need an API gateway or proxy in the path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;shadowaudit works at &lt;strong&gt;development time&lt;/strong&gt;, on &lt;strong&gt;static code&lt;/strong&gt;, with &lt;strong&gt;zero infrastructure&lt;/strong&gt;. Run it in CI and catch shadow routes before they ever reach production. No proxy. No traffic. No production access needed.&lt;/p&gt;

&lt;p&gt;The closest thing is manually diffing your routes against your spec — which nobody does because it's tedious and breaks every time someone adds a route.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-world results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Routes in code&lt;/th&gt;
&lt;th&gt;Routes in spec&lt;/th&gt;
&lt;th&gt;Shadow routes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;2,449&lt;/td&gt;
&lt;td&gt;~800&lt;/td&gt;
&lt;td&gt;~1,649&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mastodon&lt;/td&gt;
&lt;td&gt;766&lt;/td&gt;
&lt;td&gt;~165&lt;/td&gt;
&lt;td&gt;~601&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These numbers aren't accusations — both projects are open source and their "shadow" routes are largely intentional internals. But the point stands: even well-maintained APIs with dedicated teams have hundreds of routes that exist outside their documented surface area.&lt;/p&gt;

&lt;p&gt;For a smaller team shipping fast, the gap is usually worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scan your API against your spec&lt;/span&gt;
npx shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml

&lt;span class="c"&gt;# Add to CI (GitHub Actions)&lt;/span&gt;
npx shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml &lt;span class="nt"&gt;--format&lt;/span&gt; markdown &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$GITHUB_STEP_SUMMARY&lt;/span&gt;

&lt;span class="c"&gt;# Score your OpenAPI spec completeness&lt;/span&gt;
npx shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml &lt;span class="nt"&gt;--coverage&lt;/span&gt;

&lt;span class="c"&gt;# Find dead spec entries&lt;/span&gt;
npx shadowaudit &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; openapi.yaml &lt;span class="nt"&gt;--reverse&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;npm: &lt;code&gt;shadowaudit&lt;/code&gt; (published under darkmaster0345)&lt;br&gt;
GitHub: github.com/darkmaster0345/shadowaudit&lt;/p&gt;

&lt;p&gt;It's free. MIT licensed. 459 tests passing across 7 frameworks.&lt;/p&gt;

&lt;p&gt;If you find a shadow route you didn't know about — I'd genuinely like to hear about it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built this in 8 days as a side project. v1.0.1 shipped yesterday with hardening fixes from an adversarial review — exit code hygiene, Express deep router chain support, NestJS versioning. Feedback welcome.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;security&lt;/code&gt; &lt;code&gt;api&lt;/code&gt; &lt;code&gt;opensource&lt;/code&gt; &lt;code&gt;devops&lt;/code&gt; &lt;code&gt;webdev&lt;/code&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>opensource</category>
      <category>ruby</category>
      <category>security</category>
    </item>
    <item>
      <title>I Built a Free Shadow API Scanner and Found 269 Forgotten Routes in Ghost CMS</title>
      <dc:creator>Dark Master</dc:creator>
      <pubDate>Sun, 12 Jul 2026 18:06:35 +0000</pubDate>
      <link>https://dev.to/darkmaster0345/i-built-a-free-shadow-api-scanner-and-found-269-forgotten-routes-in-ghost-cms-11eg</link>
      <guid>https://dev.to/darkmaster0345/i-built-a-free-shadow-api-scanner-and-found-269-forgotten-routes-in-ghost-cms-11eg</guid>
      <description>&lt;p&gt;How a weekend project turned into a production tool with 875+ npm downloads in 48 hours — and why your codebase probably has shadow APIs too.&lt;/p&gt;

&lt;p&gt;There's a route in your codebase right now that nobody remembers adding.&lt;br&gt;
Maybe a developer spun up &lt;code&gt;app.get('/api/debug/reset')&lt;/code&gt; on a Friday to test something. No auth middleware. No documentation. Committed and forgotten. It's been sitting in production for months - invisible to your API scanner, invisible to your security team, visible to anyone who reads your source code.&lt;br&gt;
These are called &lt;strong&gt;shadow APIs&lt;/strong&gt;. And the scary part is: you probably have more of them than you think.&lt;br&gt;
I know because I built a tool to find them, and the results surprised me.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;shadowaudit&lt;/strong&gt; is a free, open-source CLI tool that scans your source code statically - no agents, no traffic mirrors, no $70K enterprise contracts - and finds API routes that exist in your code but aren't in your OpenAPI spec.&lt;br&gt;
It compares what your code actually does against what your documentation says it does. Any route in the code but not in the spec gets flagged. If that route also has no authentication middleware? That's a &lt;strong&gt;CRITICAL&lt;/strong&gt; finding. Your CI pipeline fails. The PR doesn't merge.&lt;br&gt;
Simple idea. Surprisingly effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ghost CMS test
&lt;/h2&gt;

&lt;p&gt;To see if shadowaudit actually works on real code (not just toy examples), I ran it against &lt;a href="https://github.com/TryGhost/Ghost" rel="noopener noreferrer"&gt;Ghost CMS&lt;/a&gt; - the publishing platform that powers 404Media, Platformer, and The Browser. It's a production-grade Express.js app with a large API surface.&lt;br&gt;
The first scan found &lt;strong&gt;269 routes&lt;/strong&gt;.&lt;br&gt;
But the initial results had problems. Ghost uses a custom auth middleware called &lt;code&gt;mw.authAdminApi&lt;/code&gt; that shadowaudit didn't recognize - so 213 routes that actually had auth were reported as "no auth detected." That's a 70% false negative rate. Embarrassing.&lt;br&gt;
So I fixed it. The v0.3.0 update added AST-based auth detection - instead of matching hardcoded pattern strings, it walks the route's arguments in the code's abstract syntax tree and checks if any middleware name contains auth-related words like "auth," "login," "token," "jwt," or "passport."&lt;br&gt;
After the fix: &lt;strong&gt;235 routes correctly authenticated&lt;/strong&gt;, &lt;strong&gt;0 false positives&lt;/strong&gt;, &lt;strong&gt;34 routes legitimately public&lt;/strong&gt; (login, password reset, setup endpoints - expected).&lt;br&gt;
Ghost's security was solid. Zero real vulnerabilities. But the scan proved shadowaudit works on real, production-grade codebases - not just examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  The features nobody asked for but I built anyway
&lt;/h2&gt;

&lt;p&gt;What started as "compare routes against a spec" grew into a full security tool:&lt;br&gt;
&lt;strong&gt;4 framework support.&lt;/strong&gt; Express.js, FastAPI, Django, and Flask. The tool auto-detects which framework your project uses - just point it at a directory and it figures out the rest.&lt;br&gt;
&lt;strong&gt;Three output formats.&lt;/strong&gt; A colored terminal table for humans. JSON for CI pipelines and &lt;code&gt;jq&lt;/code&gt; piping. SARIF 2.1.0 for GitHub's Security tab - findings appear right in your PR with file locations and remediation guidance.&lt;br&gt;
&lt;strong&gt;GitHub Action.&lt;/strong&gt; Published to the GitHub Marketplace. Add 3 lines to your workflow YAML and every pull request gets scanned automatically. A bot posts a findings table directly on the PR as a comment.&lt;br&gt;
&lt;strong&gt;Auto-spec generation.&lt;/strong&gt; Don't have an OpenAPI spec? Run &lt;code&gt;shadowaudit - generate-spec&lt;/code&gt; and it creates one from your code. Not perfect - but a starting point.&lt;br&gt;
&lt;strong&gt;Mount prefix reconciliation.&lt;/strong&gt; If you mount routers with &lt;code&gt;app.use('/api/v1', router)&lt;/code&gt;, shadowaudit figures out that &lt;code&gt;router.get('/users')&lt;/code&gt; is actually served at &lt;code&gt;/api/v1/users&lt;/code&gt;. This was the hardest feature to build - it required cross-file require tracking and variable name resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;In 48 hours since the first npm publish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;875+ downloads&lt;/strong&gt; across all versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9 releases&lt;/strong&gt; (v0.1.0-beta through v0.5.1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;113 tests&lt;/strong&gt; across 13 test files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 frameworks&lt;/strong&gt; supported&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socket.dev audit:&lt;/strong&gt; 100/100 on vulnerability, quality, and license scores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 open issues&lt;/strong&gt; (all 5 created and closed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 GitHub Marketplace&lt;/strong&gt; listing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 documentation site&lt;/strong&gt; (built with Docusaurus, deployed to GitHub Pages)
## What it looks like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; shadowaudit
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shadowaudit - dir&amp;nbsp;./src - spec&amp;nbsp;./openapi.json
&lt;span class="go"&gt;╔══════════════════════════════════════════════╗
║ shadowaudit - Scan Report ║
╚══════════════════════════════════════════════╝
┌──────────┬────────┬───────────────────┬──────┬──────┐
│ SEVERITY │ METHOD │ PATH │ AUTH │ │
├──────────┼────────┼───────────────────┼──────┼──────┤
│ CRITICAL │ GET │ /api/debug/reset │ NO │ │
│ HIGH │ POST │ /api/shadow │ YES │ │
└──────────┴────────┴───────────────────┴──────┴──────┘
⛔ Pipeline will FAIL - 1 critical shadow route(s) detected
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That CRITICAL finding? That's the forgotten &lt;code&gt;/api/debug/reset&lt;/code&gt; route with no auth. shadowaudit just stopped it from reaching production.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; shadowaudit
&lt;span class="c"&gt;# Scan your code&lt;/span&gt;
shadowaudit - dir&amp;nbsp;./src - spec&amp;nbsp;./openapi.json
&lt;span class="c"&gt;# Don't have a spec? Generate one&lt;/span&gt;
shadowaudit - dir&amp;nbsp;./src - framework express - generate-spec &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; openapi.json
&lt;span class="c"&gt;# Add to GitHub Actions (3 lines)&lt;/span&gt;
- uses: darkmaster0345/shadow-Audit@v0.5.1
&amp;nbsp;with:
&amp;nbsp;dir: &lt;span class="s1"&gt;'./src'&lt;/span&gt;
&amp;nbsp;spec: &lt;span class="s1"&gt;'./openapi.json'&lt;/span&gt;
&amp;nbsp;fail-on: &lt;span class="s1"&gt;'critical'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The honest part
&lt;/h1&gt;

&lt;p&gt;shadowaudit isn't perfect. It has limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django class-based view methods&lt;/strong&gt; aren't fully detected (all routes show as GET - fix planned for v0.6.0)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic route mounting&lt;/strong&gt; (&lt;code&gt;routes.forEach(route =&amp;gt; router.use(route.path, route.route))&lt;/code&gt;) isn't supported (too complex for static analysis)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flask &lt;code&gt;methods=&lt;/code&gt; with tuple syntax&lt;/strong&gt; had a bug in v0.5.0 that was fixed in v0.5.1 (real Flask projects use &lt;code&gt;methods=('GET',)&lt;/code&gt; not &lt;code&gt;methods=['GET']&lt;/code&gt;)
But it's honest about its limitations. Every known issue is documented in the &lt;a href="https://github.com/darkmaster0345/shadow-Audit/blob/main/ROADMAP.md" rel="noopener noreferrer"&gt;ROADMAP.md&lt;/a&gt;. And every release gets a full QA pass before shipping - the v0.5.1 release was driven entirely by bugs found in adversarial QA testing.
##Why I built it
Enterprise API security tools (Salt Security, Traceable, Checkmarx) cost $50,000+ per year. They're runtime-based - they watch your traffic and find shadow APIs &lt;em&gt;after&lt;/em&gt; they're already deployed and receiving requests.
That's reactive. shadowaudit is preventive. It finds the shadow route in the source code before the PR merges, before the endpoint ships, before anyone can send it traffic.
And it's free. MIT licensed. No telemetry. No license keys. No phone-home. The only network call is when you use the GitHub Action's PR comment bot, which talks to &lt;code&gt;api.github.com&lt;/code&gt; using your own token.
## Try it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;npx shadowaudit - dir&amp;nbsp;./src - framework express&lt;br&gt;
If it finds zero routes - great, you're clean. If it finds a CRITICAL - you just caught a security issue before it reached production.&lt;br&gt;
Either way, it takes 2 seconds and costs nothing.&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/darkmaster0345/shadow-Audit" rel="noopener noreferrer"&gt;darkmaster0345/shadow-Audit&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/shadowaudit" rel="noopener noreferrer"&gt;shadowaudit&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://darkmaster0345.github.io/shadow-Audit/" rel="noopener noreferrer"&gt;darkmaster0345.github.io/shadow-Audit&lt;/a&gt;&lt;br&gt;
 - -&lt;br&gt;
&lt;em&gt;If this helped you find a shadow route, consider &lt;a href="https://github.com/sponsors/darkmaster0345" rel="noopener noreferrer"&gt;sponsoring the project&lt;/a&gt;. Free tools stay free when people support them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>devops</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>I built a CLI that catches shadow APIs in Express.js repos before they merge to production</title>
      <dc:creator>Dark Master</dc:creator>
      <pubDate>Fri, 10 Jul 2026 11:47:46 +0000</pubDate>
      <link>https://dev.to/darkmaster0345/i-built-a-cli-that-catches-shadow-apis-in-expressjs-repos-before-they-merge-to-production-e0k</link>
      <guid>https://dev.to/darkmaster0345/i-built-a-cli-that-catches-shadow-apis-in-expressjs-repos-before-they-merge-to-production-e0k</guid>
      <description>&lt;p&gt;Shadow audit&lt;/p&gt;

&lt;p&gt;Static API security scanner — finds undocumented and unauthenticated routes before they reach production&lt;/p&gt;

&lt;p&gt;Installation&lt;br&gt;
Global CLI (recommended)&lt;br&gt;
npm install -g shadowaudit&lt;br&gt;
npx (no install needed)&lt;br&gt;
npx shadowaudit --dir ./src --spec ./openapi.json&lt;br&gt;
GitHub Action&lt;br&gt;
Add to .github/workflows/security.yml:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uses: darkmaster0345/&lt;a href="mailto:shadow-Audit@v0.1.0-beta-2"&gt;shadow-Audit@v0.1.0-beta-2&lt;/a&gt;
with:
dir: './src'
spec: './openapi.json'
fail-on: 'critical'
The Problem
Developers spin up quick test endpoints and forget to remove them. These shadow APIs bypass documentation, skip auth middleware, and get deployed to production silently — invisible to network scanners, invisible to your security team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shadowaudit solves this by statically analyzing your codebase, comparing every route definition against your OpenAPI/Swagger spec, and failing your CI pipeline before the PR merges if it finds undocumented or unauthenticated routes.&lt;/p&gt;

&lt;p&gt;What Shadowaudit does&lt;br&gt;
Scans your codebase for all actual route definitions (Express.js supported now; FastAPI &amp;amp; Django coming soon)&lt;br&gt;
Compares them against your OpenAPI 3.x / Swagger 2.0 spec&lt;br&gt;
Flags any route missing from the documentation&lt;br&gt;
Detects whether auth middleware is applied to each route&lt;br&gt;
Fails your CI/CD pipeline before the PR merges — with exit codes that respect configurable severity thresholds&lt;br&gt;
Severity levels&lt;br&gt;
Severity    Condition   CI behavior (--fail-on critical)&lt;br&gt;
CRITICAL    Undocumented route + no auth middleware Pipeline fails&lt;br&gt;
HIGH    Undocumented route + auth middleware present    Pipeline passes (review recommended)&lt;br&gt;
INFO    Informational finding   Pipeline passes&lt;br&gt;
Supported Frameworks&lt;br&gt;
Express.js ✅ (full support — AST-based route extraction via Babel)&lt;br&gt;
FastAPI 🚧 (coming soon)&lt;br&gt;
Django 🚧 (coming soon)&lt;br&gt;
Installation&lt;br&gt;
npm install -g shadowaudit&lt;br&gt;
Or use it locally in your project:&lt;/p&gt;

&lt;p&gt;npm install --save-dev shadowaudit&lt;br&gt;
Quick Start&lt;/p&gt;

&lt;h1&gt;
  
  
  Scan your codebase against your OpenAPI spec
&lt;/h1&gt;

&lt;p&gt;shadowaudit --dir ./src --spec ./openapi.json&lt;/p&gt;

&lt;h1&gt;
  
  
  Force a specific framework
&lt;/h1&gt;

&lt;p&gt;shadowaudit --dir ./src --spec ./openapi.json --framework express&lt;/p&gt;

&lt;h1&gt;
  
  
  Output as JSON (pipe to jq for CI integrations)
&lt;/h1&gt;

&lt;p&gt;shadowaudit --dir ./src --spec ./openapi.json --format json | jq '.findings | length'&lt;/p&gt;

&lt;h1&gt;
  
  
  Output as SARIF 2.1.0 (for GitHub Code Scanning)
&lt;/h1&gt;

&lt;p&gt;shadowaudit --dir ./src --spec ./openapi.json --format sarif &amp;gt; results.sarif&lt;br&gt;
CLI Options&lt;br&gt;
Usage: shadowaudit [options]&lt;/p&gt;

&lt;p&gt;Static API security scanner for undocumented routes&lt;/p&gt;

&lt;p&gt;Options:&lt;br&gt;
  -V, --version       output the version number&lt;br&gt;
  --dir         Directory to scan (default: current directory)&lt;br&gt;
  --spec        Path to OpenAPI/Swagger spec file&lt;br&gt;
  --format      Output format: table, json, sarif (default: "table")&lt;br&gt;
  --fail-on    Fail CI pipeline on: critical, high, info (default: "critical")&lt;br&gt;
  --framework   Force framework: express, fastapi, django (default: "auto")&lt;br&gt;
  -h, --help          display help for command&lt;br&gt;
Exit codes&lt;br&gt;
Condition   Exit code&lt;br&gt;
No findings 0&lt;br&gt;
Findings below --fail-on threshold  0&lt;br&gt;
Findings at or above --fail-on threshold    1&lt;br&gt;
--fail-on value Fails when&lt;br&gt;
critical (default)  Any CRITICAL finding&lt;br&gt;
high    Any CRITICAL or HIGH finding&lt;br&gt;
info    Any finding at all&lt;br&gt;
Configuration&lt;br&gt;
shadowaudit loads config from two sources (CLI flags take priority):&lt;/p&gt;

&lt;p&gt;CLI flags (highest priority)&lt;br&gt;
.shadowaudit.yml or .shadowauditrc.yml in your project root&lt;br&gt;
Example .shadowaudit.yml:&lt;/p&gt;

&lt;p&gt;spec: ./openapi.json&lt;br&gt;
dir: ./src&lt;br&gt;
format: table&lt;br&gt;
failOn: critical&lt;br&gt;
authPatterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;myCustomAuth&lt;/li&gt;
&lt;li&gt;requireLogin
ignore:&lt;/li&gt;
&lt;li&gt;node_modules&lt;/li&gt;
&lt;li&gt;dist&lt;/li&gt;
&lt;li&gt;build
Default auth patterns (always detected)
Even without custom config, shadowaudit detects these auth middleware patterns:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.authenticate(    .authorize(       requireAuth        isAuthenticated&lt;br&gt;
verifyToken       checkAuth         ensureLoggedIn     passport.authenticate&lt;br&gt;
jwt.verify        bearerAuth        apiKeyAuth         basicAuth&lt;br&gt;
Output Formats&lt;br&gt;
Table (default)&lt;br&gt;
Human-readable colored table with severity, method, path, file, line, and auth status. Includes a summary box and pipeline recommendation.&lt;/p&gt;

&lt;p&gt;JSON&lt;br&gt;
Structured JSON for CI dashboards and log aggregators:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "scanMeta": {&lt;br&gt;
    "tool": "shadowaudit",&lt;br&gt;
    "version": "0.1.0",&lt;br&gt;
    "timestamp": "2026-01-15T12:00:00.000Z",&lt;br&gt;
    "stats": { "total": 7, "documented": 5, "undocumented": 2, "critical": 2, "high": 0, "info": 0 }&lt;br&gt;
  },&lt;br&gt;
  "findings": [&lt;br&gt;
    {&lt;br&gt;
      "severity": "CRITICAL",&lt;br&gt;
      "method": "GET",&lt;br&gt;
      "path": "/api/debug/reset",&lt;br&gt;
      "file": "routes.js",&lt;br&gt;
      "line": 21,&lt;br&gt;
      "hasAuth": false,&lt;br&gt;
      "reason": "Undocumented route with no authentication middleware detected — publicly accessible shadow API"&lt;br&gt;
    }&lt;br&gt;
  ],&lt;br&gt;
  "documentedRoutes": [...]&lt;br&gt;
}&lt;br&gt;
SARIF 2.1.0&lt;br&gt;
Industry-standard format for GitHub Code Scanning, Azure DevOps, and other security dashboards. Maps findings to two rules:&lt;/p&gt;

&lt;p&gt;Rule ID Severity    Level&lt;br&gt;
SHADOW001   CRITICAL    error&lt;br&gt;
SHADOW002   HIGH    warning&lt;br&gt;
SHADOW002   INFO    note&lt;br&gt;
Demo&lt;br&gt;
Below is the actual terminal output from an end-to-end scan. The test project has 7 Express routes — 5 documented in the OpenAPI spec, 2 undocumented shadow routes with no auth middleware:&lt;/p&gt;

&lt;p&gt;[INFO] No config file found, using defaults&lt;br&gt;
╔════════════════════════════════╗&lt;br&gt;
║     shadowaudit v0.1.0         ║&lt;br&gt;
║     API Shadow Route Scanner   ║&lt;br&gt;
╚════════════════════════════════╝&lt;br&gt;
[INFO] Directory : /tmp/auth-test&lt;br&gt;
[INFO] Spec file : /tmp/auth-test/openapi.json&lt;br&gt;
[INFO] Format    : table&lt;br&gt;
[INFO] Fail on   : critical&lt;br&gt;
[INFO] Framework : express&lt;br&gt;
[INFO] Running Express.js route scanner...&lt;br&gt;
[✓] Found 7 routes in /tmp/auth-test&lt;br&gt;
[INFO]   GET /public/health → routes.js:6 [auth]&lt;br&gt;
[INFO]   GET /public/products → routes.js:7 [auth]&lt;br&gt;
[INFO]   GET /api/users → routes.js:9 [auth]&lt;br&gt;
[INFO]   POST /api/users → routes.js:13 [auth]&lt;br&gt;
[INFO]   GET /api/admin/dashboard → routes.js:17 [auth]&lt;br&gt;
[INFO]   GET /api/debug/reset → routes.js:21 [no auth]&lt;br&gt;
[INFO]   POST /api/test/seed → routes.js:22 [no auth]&lt;br&gt;
[INFO] Detected spec: OpenAPI 3.x&lt;br&gt;
[INFO] Spec contains 5 documented routes&lt;br&gt;
[INFO] ──────────────────────────────────────────────────&lt;br&gt;
[INFO] Total routes scanned : 7&lt;br&gt;
[INFO] Documented           : 5&lt;br&gt;
[INFO] Undocumented         : 2&lt;br&gt;
[INFO] ──────────────────────────────────────────────────&lt;br&gt;
╔══════════════════════════════════════════════╗&lt;br&gt;
║           shadowaudit — Scan Report          ║&lt;br&gt;
╚══════════════════════════════════════════════╝&lt;br&gt;
┌──────────┬────────┬───────────────────────────────────┬───────────────────────────────────┬──────┬──────┐&lt;br&gt;
│ SEVERITY │ METHOD │ PATH                              │ FILE                              │ LINE │ AUTH │&lt;br&gt;
├──────────┼────────┼───────────────────────────────────┼───────────────────────────────────┼──────┼──────┤&lt;br&gt;
│ CRITICAL │ GET    │ /api/debug/reset                  │ routes.js                         │ 21   │ NO   │&lt;br&gt;
├──────────┼────────┼───────────────────────────────────┼───────────────────────────────────┼──────┼──────┤&lt;br&gt;
│ CRITICAL │ POST   │ /api/test/seed                    │ routes.js                         │ 22   │ NO   │&lt;br&gt;
└──────────┴────────┴───────────────────────────────────┴───────────────────────────────────┴──────┴──────┘&lt;br&gt;
┌─────────────────────────────────────────────┐&lt;br&gt;
│  SCAN SUMMARY                               │&lt;br&gt;
│  Total routes scanned : 7                   │&lt;br&gt;
│  Documented           : 5                   │&lt;br&gt;
│  Undocumented         : 2                   │&lt;br&gt;
│  ─────────────────────────────────────────  │&lt;br&gt;
│  🔴 CRITICAL          : 2                   │&lt;br&gt;
│  🟡 HIGH              : 0                   │&lt;br&gt;
│  🔵 INFO              : 0                   │&lt;br&gt;
└─────────────────────────────────────────────┘&lt;br&gt;
⛔ Pipeline will FAIL — 2 critical shadow route(s) detected&lt;br&gt;
Exit code: 1&lt;br&gt;
In the terminal, severity cells are color-coded:&lt;/p&gt;

&lt;p&gt;CRITICAL → red bold&lt;br&gt;
HIGH → yellow bold&lt;br&gt;
INFO → blue&lt;br&gt;
METHOD → cyan&lt;br&gt;
AUTH YES → green / NO → red&lt;br&gt;
How It Works&lt;br&gt;
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐&lt;br&gt;
│  Express Scanner │     │  Spec Parser     │     │  Delta Comparator│     │  Formatter       │&lt;br&gt;
│  (Babel AST)     │     │  (OpenAPI/Swagger)│     │  (normalize +   )│     │  (table/json/    )│&lt;br&gt;
│                  │     │                  │     │  ( match routes )│     │  (sarif)         )│&lt;br&gt;
│  src/&lt;strong&gt;/*.js     │────▶│  openapi.json    │────▶│  Route[] vs      │────▶│  Findings[]      │&lt;br&gt;
│  src/&lt;/strong&gt;/*.ts     │     │  swagger.yaml    │     │  DocumentedRoute[]│     │  + stats         │&lt;br&gt;
│                  │     │                  │     │                  │     │                  │&lt;br&gt;
│  + auth detector │     │  + basePath      │     │  + severity      │     │  + exit code     │&lt;br&gt;
│  (window scan)   │     │  + serverPrefix  │     │  scoring         │     │  logic           │&lt;br&gt;
└──────────────────┘     └──────────────────┘     └──────────────────┘     └──────────────────┘&lt;br&gt;
Express scanner uses &lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/parser + &lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/traverse to walk every .js/.ts file's AST and extract route definitions (app.get(), router.post(), router.route().get().post() chaining, etc.)&lt;br&gt;
Auth detector scans a ±15-line window around each route for auth middleware patterns (inline or in surrounding scope), with sibling-route lines stripped to avoid false positives&lt;br&gt;
Spec parser reads OpenAPI 3.x / Swagger 2.0 files (JSON or YAML), extracts documented routes, and normalizes path syntax ({id} → :id)&lt;br&gt;
Delta comparator cross-references scanned routes against documented routes, reconciling Swagger basePath and OpenAPI servers[0].url prefixes, then scores each undocumented route as CRITICAL (no auth) or HIGH (has auth)&lt;br&gt;
Formatter renders the report as a colored table, JSON, or SARIF 2.1.0&lt;br&gt;
CI/CD Integration&lt;br&gt;
GitHub Actions&lt;br&gt;
name: Security Scan&lt;br&gt;
on: [pull_request]&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  shadowaudit:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: actions/setup-node@v4&lt;br&gt;
        with:&lt;br&gt;
          node-version: '20'&lt;br&gt;
      - run: npm ci&lt;br&gt;
      - run: npx shadowaudit --dir ./src --spec ./openapi.json --format sarif &amp;gt; shadowaudit.sarif&lt;br&gt;
        continue-on-error: true&lt;br&gt;
      - uses: github/codeql-action/upload-sarif@v3&lt;br&gt;
        with:&lt;br&gt;
          sarif_file: shadowaudit.sarif&lt;br&gt;
      - name: Fail on critical&lt;br&gt;
        run: npx shadowaudit --dir ./src --spec ./openapi.json --fail-on critical&lt;br&gt;
Pre-commit hook&lt;/p&gt;

&lt;h1&gt;
  
  
  !/bin/bash
&lt;/h1&gt;

&lt;p&gt;shadowaudit --dir ./src --spec ./openapi.json --fail-on critical&lt;br&gt;
Project Structure&lt;br&gt;
shadowaudit/&lt;br&gt;
├── src/&lt;br&gt;
│   ├── index.ts              # CLI entry point&lt;br&gt;
│   ├── config.ts             # cosmiconfig loader (CLI flags + .shadowaudit.yml)&lt;br&gt;
│   ├── comparator.ts         # Delta comparator (route diffing + severity scoring)&lt;br&gt;
│   ├── types.ts              # Shared TypeScript interfaces&lt;br&gt;
│   ├── scanners/&lt;br&gt;
│   │   ├── express.ts        # Express.js route extractor (Babel AST)&lt;br&gt;
│   │   └── auth.ts           # Auth middleware detector&lt;br&gt;
│   ├── parsers/&lt;br&gt;
│   │   └── spec.ts           # OpenAPI 3.x / Swagger 2.0 parser&lt;br&gt;
│   ├── formatters/&lt;br&gt;
│   │   ├── table.ts          # Colored terminal table&lt;br&gt;
│   │   ├── json.ts           # JSON output&lt;br&gt;
│   │   ├── sarif.ts          # SARIF 2.1.0 output&lt;br&gt;
│   │   └── index.ts          # Formatter dispatcher&lt;br&gt;
│   └── utils/&lt;br&gt;
│       └── logger.ts         # Colored console output&lt;br&gt;
├── tests/&lt;br&gt;
│   ├── config.test.ts&lt;br&gt;
│   ├── comparator.test.ts&lt;br&gt;
│   ├── scanners/&lt;br&gt;
│   │   ├── express.test.ts&lt;br&gt;
│   │   └── auth.test.ts&lt;br&gt;
│   ├── parsers/&lt;br&gt;
│   │   └── spec.test.ts&lt;br&gt;
│   └── formatters/&lt;br&gt;
│       ├── table.test.ts&lt;br&gt;
│       └── sarif.test.ts&lt;br&gt;
├── .github/workflows/ci.yml&lt;br&gt;
├── package.json&lt;br&gt;
├── tsconfig.json&lt;br&gt;
├── LICENSE&lt;br&gt;
└── README.md&lt;br&gt;
Development&lt;/p&gt;

&lt;h1&gt;
  
  
  Install dependencies
&lt;/h1&gt;

&lt;p&gt;npm install&lt;/p&gt;

&lt;h1&gt;
  
  
  Build
&lt;/h1&gt;

&lt;p&gt;npm run build&lt;/p&gt;

&lt;h1&gt;
  
  
  Run tests (45 tests across 8 test files)
&lt;/h1&gt;

&lt;p&gt;npm test&lt;/p&gt;

&lt;h1&gt;
  
  
  Run in dev mode
&lt;/h1&gt;

&lt;p&gt;npm run dev -- --dir ./src --spec ./openapi.json&lt;/p&gt;

&lt;h1&gt;
  
  
  Lint
&lt;/h1&gt;

&lt;p&gt;npm run lint&lt;br&gt;
Tech Stack&lt;br&gt;
TypeScript — strict mode, ES2020 target, CommonJS modules&lt;br&gt;
Babel (&lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/parser + &lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/traverse) — AST-based route extraction with error recovery&lt;br&gt;
Commander.js — CLI argument parsing&lt;br&gt;
chalk — terminal colors&lt;br&gt;
cli-table3 — terminal table rendering&lt;br&gt;
js-yaml — Swagger/OpenAPI YAML parsing&lt;br&gt;
cosmiconfig — config file discovery&lt;br&gt;
glob — recursive file scanning&lt;br&gt;
Vitest — test runner&lt;br&gt;
License&lt;br&gt;
MIT © Ubaid ur Rehman 2026&lt;/p&gt;

</description>
      <category>security</category>
      <category>node</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Is Telegram Really Anonymous? Let’s Be Honest.</title>
      <dc:creator>Dark Master</dc:creator>
      <pubDate>Tue, 28 Apr 2026 12:51:16 +0000</pubDate>
      <link>https://dev.to/darkmaster0345/is-telegram-really-anonymous-lets-be-honest-44jb</link>
      <guid>https://dev.to/darkmaster0345/is-telegram-really-anonymous-lets-be-honest-44jb</guid>
      <description>&lt;p&gt;There’s this idea floating around — in tech circles, activist groups, even among regular people who just “heard it somewhere” — that Telegram is the anonymous, private messaging app. The one that governments can’t touch, hackers can’t crack, and cops can’t trace.&lt;/p&gt;

&lt;p&gt;I want to gently push back on that. Not because Telegram is bad. It’s not. But because anonymous is a very specific word, and Telegram earns it only in very specific situations.&lt;/p&gt;

&lt;p&gt;First, what does “anonymous” even mean?&lt;br&gt;
There’s a difference between private and anonymous. Private means your content is hidden. Anonymous means your identity is unknown. You can have one without the other. Telegram promises a version of privacy. It mostly does not promise anonymity — and that matters.&lt;/p&gt;

&lt;p&gt;The phone number problem&lt;br&gt;
To use Telegram, you need a phone number. That’s it — that’s the conversation starter right there. Your phone number is your identity. It’s linked to a SIM card, which is linked to your name in most countries (especially in Pakistan, where NADRA ties every SIM to your CNIC).&lt;/p&gt;

&lt;p&gt;Yes, you can hide your number from other users. But Telegram still knows it. And if someone subpoenas them, or a government agency sends a valid legal request to the country Telegram operates from at that moment — they have your number. And with your number, they have you.&lt;/p&gt;

&lt;p&gt;The registration phone number is never truly hidden. It’s a persistent link between your real identity and your account — regardless of what name or photo you use.&lt;/p&gt;

&lt;p&gt;What about “Secret Chats”?&lt;br&gt;
Here’s where Telegram actually does something right. Secret Chats use end-to-end encryption (E2EE) via the MTProto 2.0 protocol. That means only you and the recipient can read those messages. Not Telegram, not their servers, not anyone intercepting traffic.&lt;/p&gt;

&lt;p&gt;But here’s the catch most people miss: regular chats are NOT end-to-end encrypted. Your normal conversations — the ones in groups, channels, and standard DMs — are encrypted in transit and at rest, but Telegram holds the keys. That means they can technically read them. Or hand them over.&lt;/p&gt;

&lt;p&gt;Secret chats&lt;/p&gt;

&lt;p&gt;End-to-end encrypted&lt;br&gt;
No cloud backup&lt;br&gt;
Self-destruct timers&lt;br&gt;
Device-to-device only&lt;br&gt;
Regular chats&lt;/p&gt;

&lt;p&gt;Cloud-stored by Telegram&lt;br&gt;
Telegram holds the keys&lt;br&gt;
Can be legally requested&lt;br&gt;
No E2EE by default&lt;br&gt;
Most people never touch Secret Chats. They use the default chat mode, sync across devices, and enjoy the convenience. That’s fine — but they shouldn’t call it anonymous.&lt;/p&gt;

&lt;p&gt;The metadata issue&lt;br&gt;
Even if your messages were perfectly encrypted, metadata is a whole other beast. Who you talk to, when, how often, from what IP address — this is metadata. Telegram collects some of it. And in intelligence and law enforcement, metadata is often more useful than content. It builds a map of your relationships, habits, and patterns.&lt;/p&gt;

&lt;p&gt;Has Telegram actually handed over data?&lt;br&gt;
Yes. After years of claiming they’d never comply, Telegram updated their privacy policy in late 2024 and acknowledged they can — and do — share user data with law enforcement under valid legal requests. This followed the arrest of Telegram’s CEO Pavel Durov in France in August 2024, which put significant pressure on the platform’s policies.&lt;/p&gt;

&lt;p&gt;This isn’t a gotcha. It’s just reality. No company operating at Telegram’s scale can exist in a legal vacuum forever.&lt;/p&gt;

&lt;p&gt;Real-world case&lt;/p&gt;

&lt;p&gt;In 2024, following Durov’s arrest, Telegram disclosed that it had provided IP addresses and phone numbers of users to authorities in response to court orders — something they had previously implied would never happen.&lt;/p&gt;

&lt;p&gt;So when IS Telegram relatively safe?&lt;br&gt;
To be fair — and fairness matters here — Telegram is genuinely useful for certain threat models:&lt;/p&gt;

&lt;p&gt;If you’re worried about a random hacker intercepting your traffic on public Wi-Fi, Telegram handles that fine. If you’re avoiding casual corporate surveillance or don’t want your messages sitting in a Google or Meta server, Telegram is better than WhatsApp for that. If you’re using Secret Chats for sensitive one-on-one conversations, the E2EE is solid.&lt;/p&gt;

&lt;p&gt;Where it fails as an anonymity tool is against nation-state actors, legal subpoenas, or any adversary who can obtain your phone number and trace it back to you.&lt;/p&gt;

&lt;p&gt;What should you use instead?&lt;br&gt;
If actual anonymity is your goal — not just privacy, but real you-can’t-find-me anonymity — the honest answer involves tools like Signal (E2EE by default, minimal metadata, open source), Session (no phone number required, decentralized), or for the highest-risk situations, Briar or Cwtch over Tor.&lt;/p&gt;

&lt;p&gt;Telegram is not in that category. It’s a feature-rich, fast, convenient messaging app with optional strong encryption. That’s a genuinely useful thing. Just don’t confuse convenience with anonymity.&lt;/p&gt;

&lt;p&gt;Final verdict&lt;br&gt;
Telegram is private-ish, not anonymous. It has good security features if you deliberately use them. It’s built by people who care about privacy more than, say, Meta does. But it’s not a shield against a determined, legally-equipped adversary.&lt;/p&gt;

&lt;p&gt;The next time someone tells you “just use Telegram, they can’t track you” — you’ll know what to say.&lt;/p&gt;

&lt;p&gt;Written from the perspective of someone who runs a Tor bridge, tests apps for F-Droid, and has spent way too many late nights reading privacy architecture docs. Take it with appropriate context.&lt;/p&gt;

</description>
      <category>anonymous</category>
      <category>opensource</category>
      <category>cybersecurity</category>
      <category>privacy</category>
    </item>
    <item>
      <title>I Built a File Encryption App in Rust. Here’s What I Learned About Trust.</title>
      <dc:creator>Dark Master</dc:creator>
      <pubDate>Thu, 16 Apr 2026 10:17:55 +0000</pubDate>
      <link>https://dev.to/darkmaster0345/i-built-a-file-encryption-app-in-rust-heres-what-i-learned-about-trust-3coo</link>
      <guid>https://dev.to/darkmaster0345/i-built-a-file-encryption-app-in-rust-heres-what-i-learned-about-trust-3coo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcshaut5r6tadisjqrqvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcshaut5r6tadisjqrqvc.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m 17. I live in Karachi. I have 8GB of RAM and a laptop that throttles if you look at it wrong.&lt;/p&gt;

&lt;p&gt;And I just shipped a desktop encryption app in Rust.&lt;/p&gt;

&lt;p&gt;Not because someone asked me to. Because I needed it to exist.&lt;/p&gt;

&lt;p&gt;Why Rust&lt;/p&gt;

&lt;p&gt;I could’ve done this in Python in a weekend. But Python wouldn’t give me what I actually wanted — control. When you’re encrypting someone’s files, you don’t want a garbage collector making decisions behind your back. You don’t want mystery allocations. You want to know exactly what’s in memory and when it leaves.&lt;/p&gt;

&lt;p&gt;Rust forces that conversation. The borrow checker is annoying until it saves you from a mistake you didn’t know you were making.&lt;/p&gt;

&lt;p&gt;It took longer. It was worth it.&lt;/p&gt;

&lt;p&gt;The Stack&lt;/p&gt;

&lt;p&gt;AES-256-GCM-SIV for encryption. Argon2id for key derivation. HKDF-SHA512 to stretch the key material. egui for the UI because I didn’t want to ship an Electron app that weighs 200MB to encrypt a text file.&lt;/p&gt;

&lt;p&gt;Each of these choices was deliberate. GCM-SIV over plain GCM because nonce reuse is a real-world failure mode, not a theoretical one. Argon2id because it’s memory-hard and scrypt has a worse story on GPUs. HKDF because you should never use a password directly as a key.&lt;/p&gt;

&lt;p&gt;Security isn’t one big decision. It’s a hundred small ones.&lt;/p&gt;

&lt;p&gt;18 Bugs&lt;/p&gt;

&lt;p&gt;The first version had 18 bugs. I’m not hiding that. UTF-8 panics on non-ASCII filenames. The NSIS installer writing to the wrong path. A title bar gap being counted twice in the layout.&lt;/p&gt;

&lt;p&gt;Become a Medium member&lt;br&gt;
Most of them were embarrassing in hindsight. None of them were unfixable.&lt;/p&gt;

&lt;p&gt;I used AI tooling heavily — Roo Code, Jules — to move through them faster. The AI didn’t replace the thinking. It replaced the typing. I still had to understand every change before it merged.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;Why This Project, Really&lt;/p&gt;

&lt;p&gt;Here’s the honest answer.&lt;/p&gt;

&lt;p&gt;I live in a part of the world where privacy is not a default. Where your data going somewhere you didn’t intend is normal. Where you can’t always trust the platform, the app store, or the company behind the software you’re using.&lt;/p&gt;

&lt;p&gt;I’m also Muslim. There’s a word in Arabic — amanah — it means a trust. Something given to you that you are responsible for. I think about that a lot when I think about other people’s files, other people’s messages, other people’s data.&lt;/p&gt;

&lt;p&gt;If you handle someone’s information, that’s an amanah. Most software treats it like a liability.&lt;/p&gt;

&lt;p&gt;I wanted to build something that treats it like what it actually is.&lt;/p&gt;

&lt;p&gt;What’s Next&lt;/p&gt;

&lt;p&gt;The app is called Neuron-Encrypt. It’s on GitHub. It’s GPL-v3 because I don’t want it locked behind anyone’s business model.&lt;/p&gt;

&lt;p&gt;Version 1 works. It encrypts. It installs. It doesn’t phone home.&lt;/p&gt;

&lt;p&gt;Version 2 will do more. But I’d rather ship something honest and small than something bloated and impressive-looking.&lt;/p&gt;

&lt;p&gt;If you’re a developer who cares about this stuff — not the buzzwords, the actual problem — I’d like to hear from you.&lt;/p&gt;

&lt;p&gt;Ubaid ur Rehman is a DAE Electronics student in Karachi building FOSS privacy tools. GitHub: darkmaster0345.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>rust</category>
      <category>security</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
