The application source was untracked, so this commit brings it under
version control together with fixes for the issues found while auditing
it. Notable fixes:
Authorization
- Require a session and scope to senderEmail on /api/transfers/[id]
(GET, DELETE) and .../resend. These were unauthenticated over an
autoincrement id, so the ids could be walked to soft-delete any
transfer, read sender/recipient metadata, or make the app mail
arbitrary recipients.
- Require a session on the legacy /api/send and /api/chunk-upload
endpoints, and take the sender from the session rather than a request
field so transfers cannot be posted as another user.
Encryption
- Replace the chunk encryption scheme. Every chunk was encrypted under
one shared session IV with its auth tag discarded, which reuses the
AES-GCM keystream (XORing two ciphertexts recovers plaintext without
the key) and left the stored file undecryptable, surfacing to users as
a wrong-password error. Chunks are now self-contained frames carrying
their own random IV and auth tag, behind a magic+salt header.
- Files written by the previous format now report UNSUPPORTED_FORMAT
instead of a misleading password error.
Download
- Verify the password against the stored bcrypt hash before serving a
file, and enforce expiresAt and DELETED/EXPIRED status.
- Move the password from the query string into a POST body so it stays
out of access logs and Referer headers.
- Record a download only after successful authentication.
- Decrypt frame by frame through a stream instead of buffering the whole
file, and encode the Content-Disposition filename per RFC 5987.
Data exposure
- /api/download ran before the password prompt and returned the full
transfer row, including absolute server file paths. It now returns
only what the pre-password screen renders; filenames, message and
recipient are withheld until /api/verify succeeds.
Correctness
- Fix BigInt handling that made /api/transfers and /api/transfers/[id]
fail unconditionally (JSON.stringify cannot serialize BigInt, and
seeding a BigInt reduce with 0 throws).
- Fail loudly on a missing chunk during reassembly rather than silently
writing a corrupt file.
- Meter plan usage in plaintext bytes rather than on-disk encrypted size.
Ignore /uploads: it holds runtime transfer payloads, not source.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>