Scofti
← Board
SC-30DonePhase Two · The First Floor

A drop on the holocron, for moving files between machines

Drop a file on one machine, pick it up on another. Two columns, a private store of its own, and no polling.

Full-width, below the tiles. Left half takes what you drop on it, right half lists what is in there with a download and a delete. It replaces wanting an FTP account somewhere: no third party, no server to patch, and it is already behind the gate.

Its own store, and that is not a preference

Public and private is a property of the store, not of a file in it. Podcast audio has to be public — a podcast client fetching an <enclosure> has no cookie to send — and a file drop has to be private. They cannot be the same store. Vercel's own guidance says the same thing under store limits: use separate stores to isolate public and private content.

So the store SC-20 created is left alone, and this gets a private one. It also means the delete button here cannot reach the audio, whatever anyone clicks.

Why nothing refreshes by itself

list() is a billed Advanced Operation. Polling the listing every five seconds is 720 an hour, so a tab left open overnight would run up thousands of calls for a page nobody is looking at. The listing moves on page load, after an upload, after a delete, and when you press refresh.

The first listing is rendered by the page rather than fetched on mount, so arriving costs one operation rather than two.

The file never touches the site

Uploads go straight from the browser to the store, with /api/drop/upload handing out a one-shot token. Through a Function instead, a file would be capped at 4.5 MB — the body limit — and the transfer would be paid for twice.

Downloads have to come back through /api/drop/file, because a private blob's URL answers to nobody without a token. That route streams it with Cache-Control: private, no-store.

Ceiling of 100 MB a file, because Vercel recommends against serving anything larger through a private store, and because 1 GB of storage does not survive many mistakes.

The gate is now carrying more than it was sized for

lib/holocron.ts says of itself:

This keeps a casual wanderer out and nothing more, which is the right size for a page with nothing in it.

The page no longer has nothing in it. The attempt counter lives in a cookie, so a private window resets it and two tries an hour is really unlimited guessing. Every route here checks the session server-side next to the call it guards, and a closed session gets a 404 that says nothing — but that is the door, not the lock. Real rate limiting needs server-side state keyed to something the visitor does not control.

Until that exists, the password is the whole of the security, and it should be long enough that unlimited guessing does not matter.

Not done until

Built and QA'd, September 21

The store is scofti-drop — store_d6TyYQLokipUaXAP, private, iad1 — created separately from the audio store, which stays empty and untouched. DROP_BLOB_READ_WRITE_TOKEN is set on production, preview and development.

Michael dropped a file in through the browser and pulled it back down. That was the one path nothing else could test: the upload goes straight from the browser to the store on a one-shot token, so it needs a real browser and a real file.

Everything else was checked against production:

The thing worth knowing before touching this again

Setting it up put a test file in the wrong store. The Vercel CLI and the SDK both ignore an explicitly passed token when OIDC credentials and a store id are present in the environment — and .env.local had a stale BLOB_STORE_ID pointing at the audio store. The file went there instead, and had to be deleted.

So: an explicit token is not authoritative. When two stores exist, clear BLOB_STORE_ID and VERCEL_OIDC_TOKEN from the environment before running anything that writes, or it will quietly write somewhere else. The site itself was never wrong — it reads DROP_BLOB_READ_WRITE_TOKEN and nothing else.

Two related traps, recorded rather than fixed: vercel blob commands rewrite .env.local without being asked, which cost a local-only API key on the way through; and the default BLOB_* variables still sit in that file pointing at the audio store.

Still true, and still soft

The gate did not change. The attempt counter is a cookie, so a private window resets it, and the password is doing all the work. That was noted when this was built and it is noted again now that the page holds files rather than a clock.

Correction, September 21

This ticket and the code both said the drop does not poll because the Hobby plan includes 2,000 Advanced Operations a month, and that exceeding it locks the store for thirty days. The project is on Pro, where those operations are billed rather than capped, so the consequence was wrong in both places.

The decision does not change — polling still costs, it just costs money instead of access. The reason now says what is actually true.

Noted because it was an assumption rather than a check: the plan was read out of Vercel's Hobby documentation and never confirmed against the account.