<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://darkmaster0345.github.io/shadow-Audit/blog</id>
    <title>shadowaudit — Shadow API Scanner Blog</title>
    <updated>2025-07-01T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://darkmaster0345.github.io/shadow-Audit/blog"/>
    <subtitle>shadowaudit — Shadow API Scanner Blog</subtitle>
    <icon>https://darkmaster0345.github.io/shadow-Audit/img/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[How shadowaudit found 269 shadow routes in Ghost CMS]]></title>
        <id>https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan</id>
        <link href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan"/>
        <updated>2025-07-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Real-world scan results: 269 routes detected in Ghost CMS, 88% false positive reduction after v0.3.0's AST auth detection.]]></summary>
        <content type="html"><![CDATA[<p>We ran shadowaudit against <a href="https://github.com/TryGhost/Ghost" target="_blank" rel="noopener noreferrer" class="">Ghost CMS</a> — a popular open-source Node.js blogging platform — to see how it performs on a production-grade Express codebase.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-ghost-cms">Why Ghost CMS?<a href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan#why-ghost-cms" class="hash-link" aria-label="Direct link to Why Ghost CMS?" title="Direct link to Why Ghost CMS?" translate="no">​</a></h2>
<p>Ghost is a real production app, not a toy boilerplate. It powers publications like 404Media, Platformer, and The Browser. It has:</p>
<ul>
<li class="">A large Express.js API surface (admin API, content API, members API, webhooks)</li>
<li class="">Custom auth middleware (<code>mw.authAdminApi</code>)</li>
<li class="">Complex route mounting patterns</li>
<li class="">A <a href="https://docs.ghost.org/security" target="_blank" rel="noopener noreferrer" class="">security disclosure policy</a></li>
</ul>
<p>If shadowaudit can handle Ghost, it can handle most Express codebases.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setup">Setup<a href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan#setup" class="hash-link" aria-label="Direct link to Setup" title="Direct link to Setup" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">git clone https://github.com/TryGhost/Ghost.git --depth=1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">cd Ghost</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"># Scan the web layer (where Express routes live)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">shadowaudit --dir ghost/core/core/server/web --framework express</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="raw-results--v021">Raw results — v0.2.1<a href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan#raw-results--v021" class="hash-link" aria-label="Direct link to Raw results — v0.2.1" title="Direct link to Raw results — v0.2.1" translate="no">​</a></h2>
<p>The first scan with shadowaudit v0.2.1 found <strong>313 routes</strong>, but the results had two problems:</p>
<table><thead><tr><th>Metric</th><th>Count</th><th>Issue</th></tr></thead><tbody><tr><td>Total routes detected</td><td>313</td><td>—</td></tr><tr><td>Real HTTP routes</td><td>302</td><td>✅ Accurate</td></tr><tr><td><code>config.get()</code> false positives</td><td>11</td><td>❌ <code>config.get('paths')</code> matched as <code>GET paths</code></td></tr><tr><td>Auth detected</td><td>20</td><td>❌ Should be 235 — Ghost uses <code>mw.authAdminApi</code> which wasn't in the hardcoded pattern list</td></tr><tr><td>Routes reported as "no auth"</td><td>293</td><td>❌ 213 of these actually HAD auth (<code>mw.authAdminApi</code>)</td></tr></tbody></table>
<p><strong>Root causes:</strong></p>
<ol>
<li class=""><strong>No object-name filtering</strong> — shadowaudit matched <code>.get()</code> on ANY object (<code>config.get()</code>, <code>settings.get()</code>), not just Express objects (<code>app.get()</code>, <code>router.get()</code>)</li>
<li class=""><strong>String-based auth detection</strong> — shadowaudit couldn't recognize <code>authAdminApi</code> as auth because it wasn't in the hardcoded pattern list</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="after-v030--ast-based-auth-detection">After v0.3.0 — AST-based auth detection<a href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan#after-v030--ast-based-auth-detection" class="hash-link" aria-label="Direct link to After v0.3.0 — AST-based auth detection" title="Direct link to After v0.3.0 — AST-based auth detection" translate="no">​</a></h2>
<p>v0.3.0 shipped two improvements that directly addressed these issues:</p>
<ol>
<li class=""><strong>Object-name filtering</strong> — only match route calls on <code>app</code>, <code>router</code>, <code>api</code>, <code>server</code> objects</li>
<li class=""><strong>AST-based auth detection</strong> — walk the route's CallExpression arguments and check if any middleware name contains <code>auth</code>, <code>login</code>, <code>session</code>, <code>token</code>, <code>jwt</code>, <code>passport</code>, <code>permission</code>, <code>require</code>, <code>protect</code>, <code>secure</code>, or <code>verify</code></li>
</ol>
<p>Same scan with v0.3.0:</p>
<table><thead><tr><th>Metric</th><th>v0.2.1</th><th>v0.3.0</th><th>Improvement</th></tr></thead><tbody><tr><td><code>config.get()</code> false positives</td><td>11</td><td><strong>0</strong></td><td>✅ Eliminated</td></tr><tr><td>Auth detected</td><td>20</td><td><strong>235</strong></td><td>✅ 12x improvement</td></tr><tr><td>False negatives (no auth)</td><td>293</td><td><strong>34</strong></td><td>✅ 88% reduction</td></tr><tr><td>Total routes</td><td>313</td><td><strong>269</strong></td><td>More accurate</td></tr></tbody></table>
<p>The 34 remaining "no auth" routes are <strong>legitimately public</strong> — session endpoints (<code>POST /session</code>), authentication endpoints (<code>POST /authentication/password_reset</code>), setup endpoints (<code>POST /authentication/setup</code>), and webhooks (<code>POST /webhooks/stripe</code>). These are expected to be public.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-caught">What it caught<a href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan#what-it-caught" class="hash-link" aria-label="Direct link to What it caught" title="Direct link to What it caught" translate="no">​</a></h2>
<p>shadowaudit correctly identified the security-sensitive routes in Ghost's admin API:</p>
<ul>
<li class=""><code>/db</code> — database export/import (CRITICAL if undocumented, but Ghost has <code>mw.authAdminApi</code> on it)</li>
<li class=""><code>/db/backup</code> — database backup</li>
<li class=""><code>/themes/upload</code> — theme file upload</li>
<li class=""><code>/images/upload</code> — image upload</li>
<li class=""><code>/users/:id/token</code> — user token generation</li>
<li class=""><code>/integrations/:id/api_key/:keyid/refresh</code> — API key refresh</li>
</ul>
<p>All of these had <code>mw.authAdminApi</code> applied — and after v0.3.0, shadowaudit correctly detected that auth.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://darkmaster0345.github.io/shadow-Audit/blog/ghost-cms-scan#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>This validates that shadowaudit works on real, production-grade codebases — not just toy examples. The Ghost CMS scan directly shaped the v0.3.0 roadmap:</p>
<ul>
<li class=""><strong>Object-name filtering</strong> (roadmap item #1) — eliminated <code>config.get()</code> false positives</li>
<li class=""><strong>AST-based auth detection</strong> (roadmap item #2) — caught custom <code>authAdminApi</code> middleware without needing config patterns</li>
</ul>
<p>The remaining 34 "no auth" routes were verified as legitimately public endpoints. <strong>Zero real vulnerabilities were found</strong> — Ghost's admin API is properly secured.</p>
<hr>
<p><em>Want to test shadowaudit on your codebase? <a class="" href="https://darkmaster0345.github.io/shadow-Audit/docs/intro">Get started →</a></em></p>]]></content>
        <author>
            <name>Ubaid ur Rehman</name>
            <uri>https://github.com/darkmaster0345</uri>
        </author>
        <category label="case-study" term="case-study"/>
        <category label="express" term="express"/>
        <category label="ghost-cms" term="ghost-cms"/>
        <category label="shadow-api" term="shadow-api"/>
    </entry>
</feed>