- What: 9Drive, a self-hosted storage gateway that unifies multiple Google Drive and S3 accounts (862 stars, 232 forks, a live hosted demo), sets every uploaded file to
role: 'writer', type: 'anyone'in the user's real Google Drive. - Impact: Every stored file becomes readable and editable by anyone with the link, no sign-in required. Separately, the app has no authorization model, so any registered account can download or replace the entire database and overwrite the global OAuth configuration.
- Fix: Default to private. If sharing is needed, use
readerand make it an explicit user action. Add a role check so privileged endpoints are not reachable by every logged-in user. - Who's at risk: Anyone running 9Drive or one of its forks, and every person whose files pass through it.
Every so often a project crosses your feed that is genuinely clever. 9Drive is one of them. It is a self-hosted storage gateway: connect several Google Drive accounts, and maybe an S3 bucket or two, and it presents them as one virtual drive. It tracks quota across all of them and routes each upload to whichever account has room, using policies like most-available or round-robin. Clean React dashboard, resumable uploads, an external upload API with hashed keys. In under two months it collected more than 860 stars and 230 forks, spun up a public demo, and picked up a trail of rebranded forks from people standing up their own instances.
It is a nice piece of engineering. It also, by default, publishes your files to the open internet. Not "readable if someone has the link." Writable. By anyone.
What the code actually does
When you upload a file, 9Drive stores it in your real Google Drive and then makes one more API call. Here is that call, which appears in three separate places across the upload and file-serving paths:
await drive.permissions.create({ fileId: providerFileId, requestBody: { role: 'writer', type: 'anyone' } })
type: 'anyone' means no sign-in required. role: 'writer' means edit access. The comment sitting directly above it in the source is not coy about it: "Make the file public (anyone with link can edit/download)."
This fires on the normal upload path, on the resumable (chunked) upload path, and again the moment the app fetches a "view URL," which the interface does just to preview a file or copy a link. So the exposure is not a corner case you have to opt into. Uploading is the trigger. Previewing is a trigger. The file lands in your personal Drive and is immediately handed to the entire internet with write permission.
The distinction between reader and writer is the whole story here. A public read link is a confidentiality problem: strangers can see your file. A public write link is also an integrity problem: a stranger with the link can overwrite the contents, replace a document with something else, or empty it. On a tool people use to consolidate personal and work storage, that is about as blunt as it gets.
The second door: any login is an admin login
The permission default is the headline, but the authorization model underneath it is the deeper lesson. 9Drive authenticates users, and then treats every authenticated user as fully trusted. There is no role, no admin flag, nothing separating a random person who signed up thirty seconds ago from the operator of the instance. The user record simply has no concept of privilege.
That matters because the app ships a set of system endpoints guarded only by "are you logged in." On the common SQLite deployment, one of them streams the entire database to any account that asks: every user's password hash, every stored Google refresh token, every S3 secret key, all of it. Another accepts an uploaded database file, replaces the live one, and restarts the server, which is data destruction and account takeover in a single request. A third lets any user overwrite the global Google OAuth client configuration, which means an attacker can quietly swap in their own OAuth application and begin collecting other users' Drive authorizations as they sign in.
Authentication answers "who are you." Authorization answers "what are you allowed to do." 9Drive answers the first question well (argon2 password hashing, encrypted tokens, hashed refresh tokens) and never asks the second. A login page becomes an admin console the moment privileged actions sit behind nothing more than a valid session.
This is not theoretical
We did not touch anyone's files to write this, and we are not naming the people running instances. We did not need to. The behavior is fixed in the source, so every deployment that has not modified it inherits it. And the deployments exist: a live demo, hundreds of forks, several of them recently updated and renamed in the way people do when they are actually shipping something. Anyone who wants to confirm the mechanism can do it in a few minutes on their own account: stand up the app, connect a throwaway Drive, upload a file, then read that file's permissions back through the Drive API. The anyone grant will be sitting right there.
Watching for the blast radius in your own logs
You cannot stop someone else's app from flipping a file to public. What you can do, if any of this touches an account inside your own Google Workspace tenant, is watch for the footprint. Drive audit logs record every visibility and access-level change. A burst of files being shared to "anyone" with edit rights from a single actor is a strong signal, whether it comes from a tool like this, a misconfiguration, or a real attacker staging exfiltration.
The value is not in one query. It is in keeping enough Drive and cloud audit history, cheaply enough, that a "wait, when did that get shared out?" question is answerable months later instead of aged out. That retention economics problem is exactly what we built Caver around.
The lesson for builders
The interesting part of 9Drive is not that it has bugs. Everything has bugs. It is that both problems come from the same instinct: make it work smoothly by removing friction, and let security be a thing you add later.
Making files public by default removes the friction of sharing. It also removes the wall between your data and everyone else's. Treating every logged-in user as trusted removes the friction of building roles. It also removes the wall between a guest and the operator. Convenience and least privilege are pulling in opposite directions, and when convenience wins by default, the failure mode is not a warning. It is silence, until someone notices.
Three habits would have prevented all of it. Default to private, and make sharing an explicit choice with reader before writer. Ask for the narrowest OAuth scope that does the job, not full Drive access, so a single mistake cannot reach the user's entire account. And remember that a login answers "who are you," while authorization answers "what are you allowed to do." Skipping the second question is how a sign-up form becomes an admin panel.
We notified the 9Drive maintainer privately before publishing, and we have kept exploitation detail out of this writeup. Our findings are drawn from the public source and public repository data only. We did not access any third-party data. 9Drive is worth watching, and worth learning from, in both directions.
Could you tell if your files went public last month?
Caver is an enterprise SIEM on an OCSF lakehouse. It speaks SPL, runs on your own storage, and makes long-horizon retention affordable, so cloud sharing and identity changes stay queryable long after they happen, not just for the last few days.
Explore Caver →Bottom line
A clever tool made every file it stored world-writable and handed admin to anyone who registered, both by default, both in a few lines of code. The engineering is good. The defaults are the vulnerability. Secure defaults are not a finishing touch you add before launch. They are the product deciding, on your behalf, who can read and change your data. Decide it on purpose.
Deploying third-party tools that touch your data?
RedEye Security reviews the software and integrations you rely on, from OAuth scopes to authorization models, and tells you where the real exposure is.
Talk to us