Surfer: /api/zip serves the whole site anonymously even when "Public folder listing" is off
-
Hello @nebulon - I'm using Surfer as a simple "link-only" static host. Access is Public, Public folder listing is off, 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
/api/zipgets around it.Environment
- Cloudron 10.0.5
- Surfer package 7.2.6 (upstream at
c5329dc, 2026-10-06) - Settings: access = Public, Public folder listing = off
Steps to reproduce
- Set Surfer access to Public and turn Public folder listing off.
- Upload some content into a folder, for example
/some-unguessable-name/index.html. - From a logged-out browser or curl:
curl -o all.zip 'https://surfer.example.com/api/zip?paths=%5B%22%2F%22%5D' unzip -l all.zip
Expected: 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.
Actual:
200 application/zipcontaining the entire site root, including every folder name, so anyone who can reach the host can discover and download everything.Why it happens
src/routes/index.js:79:router.get('/api/zip', access.handleProtection, publicSite.zipDownload);. On a Public site,handleProtectionlets everyone through, andfolderListingEnabledis never consulted.src/routes/public.jszipDownload()accepts anypathsinside the site root, and"/"is allowed.- Related: the
deploymentquery parameter (sites.rootForQuery()) lets an anonymous caller zip another named site's root (public-<name>) through the same endpoint, from any of the app's domains.
The only callers I could find are the public listing UI (
frontend/utils.js:55, used byPublic.vue) and the admin file view (views/FilesView.vue). So when listing is off, the anonymous use case for this endpoint doesn't exist.Suggested fix: a "Public zip downloads" on/off setting
Add a setting next to "Public folder listing", stored the same way as
folderListingEnabled:Public zip downloads: when enabled, anonymous visitors can download folders as a .zip. When disabled, only signed-in users can (the admin file view keeps working).
- Default: existing installs inherit their current
folderListingEnabledvalue, so nobody's behavior changes on update. Listing-on sites keep their download button, and listing-off sites stop leaking. - Gate: when the switch is off,
/api/ziprequires auth. Adeployment=request always requires auth, because anonymous visitors shouldn't be able to zip other sites. - Public UI: hide the folder/multi-select "Download" action when the switch is off. Single-file downloads use
?downloadand keep working.
Rough sketch (untested):
// src/settings.js const DEFAULTS = { folderListingEnabled: false, publicZipEnabled: null, /* ... */ }; // normalize(): null/undefined -> 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 && 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 && !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 <Switch v-model="settings.publicZipEnabled">, like folder listing // frontend/Public.vue: hide the zip-download action when !settings.publicZipEnabledIf a setting is more than you'd like to add, the minimal version is simply to tie the gate to
config.folderListingEnabledinstead of a new key. -
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login