<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Surfer: /api/zip serves the whole site anonymously even when "Public folder listing" is off]]></title><description><![CDATA[<p dir="auto">Hello <a class="plugin-mentions-user plugin-mentions-a" href="/user/nebulon" aria-label="Profile: nebulon">@<bdi>nebulon</bdi></a>  -  I'm using Surfer as a simple "link-only" static host. Access is <strong>Public</strong>, <strong>Public folder listing is off</strong>, and each set of pages lives under an unguessable folder name, so only people who get the link can read it. That model holds for normal browsing: with listing off, unknown folders return 404. But <code>/api/zip</code> gets around it.</p>
<p dir="auto"><strong>Environment</strong></p>
<ul>
<li>Cloudron 10.0.5</li>
<li>Surfer package 7.2.6 (upstream at <code>c5329dc</code>, 2026-10-06)</li>
<li>Settings: access = Public, Public folder listing = off</li>
</ul>
<p dir="auto"><strong>Steps to reproduce</strong></p>
<ol>
<li>Set Surfer access to <strong>Public</strong> and turn <strong>Public folder listing off</strong>.</li>
<li>Upload some content into a folder, for example <code>/some-unguessable-name/index.html</code>.</li>
<li>From a logged-out browser or curl:<pre><code>curl -o all.zip 'https://surfer.example.com/api/zip?paths=%5B%22%2F%22%5D'
unzip -l all.zip
</code></pre>
</li>
</ol>
<p dir="auto"><strong>Expected:</strong> the request is refused (401/404), because folder listing is off, so anonymous visitors shouldn't be able to enumerate or bulk-download content they don't have a URL for.</p>
<p dir="auto"><strong>Actual:</strong> <code>200 application/zip</code> containing the <strong>entire</strong> site root, including every folder name, so anyone who can reach the host can discover and download everything.</p>
<p dir="auto"><strong>Why it happens</strong></p>
<ul>
<li><code>src/routes/index.js:79</code>: <code>router.get('/api/zip', access.handleProtection, publicSite.zipDownload);</code>. On a Public site, <code>handleProtection</code> lets everyone through, and <code>folderListingEnabled</code> is never consulted.</li>
<li><code>src/routes/public.js</code> <code>zipDownload()</code> accepts any <code>paths</code> inside the site root, and <code>"/"</code> is allowed.</li>
<li>Related: the <code>deployment</code> query parameter (<code>sites.rootForQuery()</code>) lets an anonymous caller zip <strong>another named site's</strong> root (<code>public-&lt;name&gt;</code>) through the same endpoint, from any of the app's domains.</li>
</ul>
<p dir="auto">The only callers I could find are the public listing UI (<code>frontend/utils.js:55</code>, used by <code>Public.vue</code>) and the admin file view (<code>views/FilesView.vue</code>). So when listing is off, the anonymous use case for this endpoint doesn't exist.</p>
<p dir="auto"><strong>Suggested fix: a "Public zip downloads" on/off setting</strong></p>
<p dir="auto">Add a setting next to "Public folder listing", stored the same way as <code>folderListingEnabled</code>:</p>
<blockquote>
<p dir="auto"><strong>Public zip downloads</strong>: when enabled, anonymous visitors can download folders as a .zip. When disabled, only signed-in users can (the admin file view keeps working).</p>
</blockquote>
<ul>
<li><strong>Default:</strong> existing installs inherit their current <code>folderListingEnabled</code> value, so nobody's behavior changes on update. Listing-on sites keep their download button, and listing-off sites stop leaking.</li>
<li><strong>Gate:</strong> when the switch is off, <code>/api/zip</code> requires auth. A <code>deployment=</code> request always requires auth, because anonymous visitors shouldn't be able to zip other sites.</li>
<li><strong>Public UI:</strong> hide the folder/multi-select "Download" action when the switch is off. Single-file downloads use <code>?download</code> and keep working.</li>
</ul>
<p dir="auto">Rough sketch (untested):</p>
<pre><code class="language-js">// src/settings.js
const DEFAULTS = { folderListingEnabled: false, publicZipEnabled: null, /* ... */ };
// normalize(): null/undefined -&gt; follow folderListingEnabled (backwards compatible)
publicZipEnabled: values.publicZipEnabled == null
    ? (values.folderListingEnabled === true || values.folderListingEnabled === 'true')
    : (values.publicZipEnabled === true || values.publicZipEnabled === 'true'),
// storedValue(): add 'publicZipEnabled' to the boolean keys

// src/routes/settings.js
// get(): publicZipEnabled: !!config.publicZipEnabled,
// put(): optional, so older clients that don't send it keep working
if ('publicZipEnabled' in req.body &amp;&amp; typeof req.body.publicZipEnabled !== 'boolean') return next(new HttpError(400, 'publicZipEnabled must be a boolean'));
if ('publicZipEnabled' in req.body) config.publicZipEnabled = req.body.publicZipEnabled;

// src/routes/index.js
function zipAccess(req, res, next) {
    if (config.publicZipEnabled &amp;&amp; !req.query.deployment) return access.handleProtection(req, res, next);
    return auth.requireAuth(req, res, next);   // admin sessions / app passwords still work
}
router.get('/api/zip', zipAccess, publicSite.zipDownload);

// frontend/views/SettingsView.vue: one more &lt;Switch v-model="settings.publicZipEnabled"&gt;, like folder listing
// frontend/Public.vue: hide the zip-download action when !settings.publicZipEnabled
</code></pre>
<p dir="auto">If a setting is more than you'd like to add, the minimal version is simply to tie the gate to <code>config.folderListingEnabled</code> instead of a new key.</p>
]]></description><link>https://forum.cloudron.io/topic/16107/surfer-api-zip-serves-the-whole-site-anonymously-even-when-public-folder-listing-is-off</link><generator>RSS for Node</generator><lastBuildDate>Wed, 14 Oct 2026 07:22:36 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/16107.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 10 Oct 2026 21:02:35 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Surfer: /api/zip serves the whole site anonymously even when "Public folder listing" is off on Mon, 12 Oct 2026 10:36:27 GMT]]></title><description><![CDATA[<p dir="auto">Instead of making a new setting, I think it's better to simply disable zip downloads when public listing is disabled. I have made this fix. Will release shortly.</p>
]]></description><link>https://forum.cloudron.io/post/130868</link><guid isPermaLink="true">https://forum.cloudron.io/post/130868</guid><dc:creator><![CDATA[girish]]></dc:creator><pubDate>Mon, 12 Oct 2026 10:36:27 GMT</pubDate></item></channel></rss>