Summary
upload_init in src/main.rs creates a zero-byte file on disk before validating the request's recipient field. When the recipient is unparseable, the handler returns 400 Bad Request but the empty file in data_dir is never cleaned up. Each rejected request leaves a UUID-named empty file behind that the in-memory purge_task will not remove (it only walks Store::expirations, and no FileState was inserted for this UUID).
Code path
src/main.rs:91-139:
File::create(Path::new(config.data_dir()).join(&uuid)) — file created on disk (line 101)
bytes_to_hex(&rand::rng().random::<[u8; 32]>()) — token generated (line 109)
request.recipient.parse() — recipient validated last (line 111)
- On
Err, returns 400 — but the file from step 1 stays on disk
Store::create is never called on this path either, so the cleanup task has no record to drive a removal.
Impact
- Disk fills with empty zero-byte files when clients (or attackers) repeatedly POST
/fileupload/init with malformed recipients
- No quota or rate limit on
/fileupload/init — easily exploitable
Suggested fix
Reorder: parse the recipient first; only on success generate UUID, create the on-disk file, and call store.create. This matches the flow already used in upload_finalize for cleanup-on-error (tokio::fs::remove_file + store.remove).
let recipient = request.recipient.parse()
.map_err(|e| Error::BadRequest(Some(format!("Could not parse e-mail address: {}", e))))?;
let uuid = uuid::Uuid::new_v4().hyphenated().to_string();
File::create(...).await.map_err(...)?;
// ...
Tests
Add a unit test that POSTs /fileupload/init with a malformed recipient and asserts the data_dir is unchanged afterwards.
Summary
upload_initinsrc/main.rscreates a zero-byte file on disk before validating the request'srecipientfield. When the recipient is unparseable, the handler returns400 Bad Requestbut the empty file indata_diris never cleaned up. Each rejected request leaves a UUID-named empty file behind that the in-memorypurge_taskwill not remove (it only walksStore::expirations, and noFileStatewas inserted for this UUID).Code path
src/main.rs:91-139:File::create(Path::new(config.data_dir()).join(&uuid))— file created on disk (line 101)bytes_to_hex(&rand::rng().random::<[u8; 32]>())— token generated (line 109)request.recipient.parse()— recipient validated last (line 111)Err, returns 400 — but the file from step 1 stays on diskStore::createis never called on this path either, so the cleanup task has no record to drive a removal.Impact
/fileupload/initwith malformed recipients/fileupload/init— easily exploitableSuggested fix
Reorder: parse the recipient first; only on success generate UUID, create the on-disk file, and call
store.create. This matches the flow already used inupload_finalizefor cleanup-on-error (tokio::fs::remove_file+store.remove).Tests
Add a unit test that POSTs
/fileupload/initwith a malformed recipient and asserts thedata_diris unchanged afterwards.