Please do not open a public issue for a security problem.
Report it privately through GitHub: Report a vulnerability (Security → Advisories → Report a vulnerability on this repository).
Include what you can of: the affected version or commit, whether it is the
Worker or the dropbin npm package, a minimal reproduction, and what an
attacker gets out of it. You will get an acknowledgement within a few days, and
a fix or an explanation of why it is out of scope. Please give a reasonable
window before disclosing publicly.
| What | Supported |
|---|---|
main of this repository, redeployed |
✅ |
The latest dropbin release on npm |
✅ |
| Anything older, or a fork you have changed | ❌ |
There are no long-lived release branches. A fix lands on main, and the Worker
side of it is live for you only once you redeploy your own instance.
dropbin is a single-tenant drop box. One password gates everything. There are no accounts, no roles and no per-file permissions, and there is no attempt to keep one user's uploads from another's — everyone who has the password is the same person as far as the tool is concerned.
What the design does defend:
- Nothing is listable. There is no browse, search or index path, and no endpoint returns more than one pin. A pin is 4 digits by default, so treat a pin as a handle, not a secret — the password is what protects the contents.
- The password is never stored in the clear anywhere. The
dpcookie holdssha256("dropbin:" + password), and comparison is length-checked constant time (eq()insrc/util.js).dbwrites nothing at all: no config file, no token cache. - Uploaded bytes cannot act on the origin. Every file-serving path sends
Content-Security-Policy: sandboxandX-Content-Type-Options: nosniff, so an uploaded.htmlor.svgrenders inert. Preserve both headers on any new file-serving path. - Share links are narrow.
?k=<expiry>.<hmac>is an HMAC-SHA256 of"dropbin/" + pin + "/" + exp, keyed on the password. A holder reachesdownload()for that one pin and nothing else — no/up, noDELETE, no other pin, no home page — and only withGET/HEAD. Nothing is stored;shared()recomputes it. - Nothing is indexed. Every response carries
X-Robots-Tag: noindex, nofollowand/robots.txtdisallows everything.
What it does not defend, by design:
- Anyone with the password can read, replace and delete every pin.
- A pin is guessable. 9000 four-digit pins exist; someone with the password can walk them. The password is the only boundary.
- A share link is a bearer URL. Whoever holds it can read that pin until it expires, and an individual link cannot be revoked — only changing the password invalidates outstanding links (and every browser cookie with them).
- There is no scanning of uploaded content, and no abuse or malware handling. You are hosting arbitrary bytes on your own domain.
-
Set a real password before the URL is anywhere. With
ACCESS_PASSWORDunset or empty the Worker falls back tochangemein code, and a stranger can upload to and delete from your bucket. Verify what is actually live rather than what you think the environment holds (changememust get a 401):curl -o /dev/null -w '%{http_code}\n' -H "X-Pass: YOUR_PASSWORD" https://host/?info=1
-
Give the S3 key one bucket and nothing else. dropbin needs read, write and list on its own bucket. A key that can see other buckets turns a leaked password into a leaked store.
-
Prefer the header or the cookie over
?p=. The password is accepted four ways:?p=,X-Pass:, HTTP basic auth, and thedpcookie. A query string ends up in browser history, inRefereron any outbound link, and in request logs — yours and those of any proxy in front. Use?p=for throwaway curl against a test instance, not in anything you paste around. -
Put a rate limit in front of it. The Worker does not throttle password attempts. A rate limit on the hostname in whatever sits in front — a reverse proxy, or a CDN / WAF rule (say, 20 requests per minute per IP with a 401 response) — turns a single shared password from brute-forceable into merely guessable. Choose a long password.
-
Deploy on a subdomain, not the apex. You are serving files people upload. A dedicated
files.example.comkeeps thedpcookie and any sandbox escape away from the rest of your site. -
Rotate by changing the password. That invalidates every
dpcookie (one year,HttpOnly,SameSite=Lax,Secureover HTTPS) and every outstandingdb open/db qrlink at once, since both are derived from it. -
Remember there is no expiry. Files live until you
DELETEthem, andQUOTA_GBis a display figure, not a cap — set a quota on the bucket itself if the disk or the bill has a limit.
In scope: auth bypass; reaching a pin without the password or a valid ?k=; a
share link that reads more than its one pin or outlives SHARE_HOURS; path
traversal out of a pin prefix; stored XSS or a sandbox escape from an uploaded
file; anything that leaks the password out of the Worker or the clients; a
supply-chain problem in the dropbin npm package.
Out of scope: anything that requires the password already; brute-forcing a weak
password you chose; guessing a pin with the password; missing rate limiting on
a deployment with nothing in front (documented above); denial of service by
uploading until the disk or the bill hurts; a deployment still running changeme.