Directory Listing on the Document Store
Directory listing was left on over a document store, serving thousands of tenant identity documents unauthenticated. How it chained, and how to close it.
The setup
The target was a furnished-housing and coliving platform: a company that lets people book fully furnished homes and rooms, handling the lease, the identity checks, and the payments end to end. A product like that accumulates a heavy set of documents per tenant - government identity papers, signed leases, proof-of-payment records, signatures - because it has to satisfy both a landlord’s due diligence and a payment processor’s.
All of that lives in a document store somewhere. How that store is served, and who is allowed to read from it, is the question that decides whether a furnished-housing platform is holding its tenants’ most sensitive paperwork safely or leaving it on a shelf in the lobby. That is where we looked.
Recon
Working black-box, from the open internet with no account, we mapped the platform’s API host and the paths it served. Most of the application behaved normally. One path stood out: an uploads directory, served directly by the web server, that returned something other than an error when requested as a directory rather than as a specific file.
That is the signature of automatic directory indexing. When a server has autoindex enabled and a directory has no index file, a request for the directory returns a generated listing of everything inside it. For a static asset folder that is harmless. For a document store, it is a table of contents to data that was never meant to be browsable.
The hypothesis
The hypothesis was direct: if directory listing was on over the uploads path, and that path was where tenant documents were stored, then the store was a mass-PII exposure open to anyone, with no authentication and no need to guess a single filename. The server would simply hand over the index.
There was a second, quieter possibility worth holding in mind. Upload systems frequently name files after an internal object identifier - a booking ID, an application ID, a user ID - because it is the convenient way to tie a file back to its record. If that were true here, the filenames in the listing would not just be files. They would be a list of live identifiers, and identifiers are keys that other parts of a system tend to trust.
What we tried
We requested the uploads path as a directory. The server returned a listing: page after page of tenant documents, thousands of them, each fetchable directly and none of them behind authentication. Identity documents, signatures, signed leases, and payment proofs were all in the index.
We confirmed the exposure the careful way. We read the listing and performed header checks on a small number of objects to establish that they served real document content unauthenticated. We did not download document contents. Proving that thousands of identity documents are anonymously fetchable does not require pulling a single tenant’s passport onto a laptop, and it should not.
Then we tested the second hypothesis. The filenames in the listing carried object identifiers. We took identifiers straight from the filenames and tried them against the platform’s user-data API. That API was keyed only by the identifier - hand it a valid ID and it returned the associated record - and it carried a wildcard CORS policy, meaning any origin could read its responses. Supplying the identifiers the listing had just handed us returned booking and application PII. The two weaknesses were not separate; the first one fed the second.
What we found
The finding was a chain, and the chain is where the severity lives.
The first link was the directory listing. Autoindex was enabled over the document store, so an unauthenticated request for the uploads directory returned a browsable index of many thousands of tenant documents - identity papers, signatures, signed leases, and payment proofs - each directly retrievable.
-> GET /<uploads-path>/ (requested as a directory)
<- 200 OK (autoindex listing)
Index of /<uploads-path>/
<application-id>-id.<ext>
<booking-id>-lease.<ext>
<application-id>-payment.<ext>
... thousands of entries, each fetchable, no auth ...
The second link was the identifiers embedded in those filenames. A separate user-data API was keyed only by that identifier and wrapped in a wildcard CORS policy:
-> GET /<user-data-endpoint>?id=<application-id-from-filename>
Origin: https://anything
<- 200 OK
Access-Control-Allow-Origin: *
{ "booking": { ... }, "applicant": { name, contact, ... } }
So the listing did double duty. It exposed the documents outright, and it published the exact identifiers that the user-data API would trade for booking and application PII. One misconfiguration leaked the files; it also leaked the keys to a second store. That is why a directory-listing issue, which sounds like a low-severity hygiene problem, was in fact a critical, whole-estate PII exposure.
Why it happens
Nobody set out to publish their tenants’ identity documents. This is the ordinary result of a few convenient defaults left in place.
Directory listing gets left on because it is often the default, or because it was useful during development to eyeball what had uploaded, and disabling it is not the kind of thing that shows up as a broken feature. The application still works perfectly with autoindex on; nothing fails a test. Naming files after object identifiers is likewise the obvious, tidy choice - it makes the file self-describing and easy to reconcile with its record. And a wildcard CORS policy is the fastest way to make a front end stop complaining during development, so it frequently survives into production untouched.
Each of these is individually forgivable and individually low-drama. The critical exposure comes from their combination: a browsable store, filenames that are really identifiers, and an API that trusts the identifier and shares its answers with any origin. No single mistake looks severe. The chain is.
For developers
If you serve uploaded documents, these controls close this:
- Never serve a document store with directory listing on. Disable autoindex and serve-index on any path that holds user files. There is no legitimate reason for a tenant document folder to be browsable.
- Require authorization on every document fetch. A document should be returned only to a request that proves it is allowed to see that specific document. Serve files through an authenticated application route that checks ownership, not straight off a static path.
- Do not put guessable identifiers in filenames. Name stored objects with unguessable random tokens, and keep the booking or application ID in the database mapping, not in the filename. A filename should reveal nothing about the record behind it.
- Lock CORS. Replace the wildcard with an explicit allowlist of the origins that legitimately need to read the API, so a leaked identifier cannot be cashed in from an arbitrary origin.
And one detection step: request your own upload paths as directories from an unauthenticated client and confirm you get an error, not an index. If a listing comes back, the store is public right now.
The takeaway
Directory listing sounds like a hygiene issue until the directory is a document store and the filenames are live identifiers. Then it is a public index of your most sensitive paperwork, and a set of keys to whatever else trusts those identifiers. Turn listing off, authorize every fetch, keep identifiers out of filenames, and lock CORS - the fix is boring, and that is the point. This teardown is one instance of a pattern we see repeatedly, written up in full in The Cloud Database With the Door Left Open.
Frequently asked
What is a directory-listing vulnerability?
Why is directory listing on a document store dangerous?
How do you fix an exposed document store?
This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.
How it works →

