fix(upload): server-side MIME/ext allowlist + atomic transactional write
C2: stored-XSS via the application/* MIME bypass is closed — the declared Content-Type and client filename are no longer trusted. New classify() derives both the canonical MIME and a safe extension purely from magic bytes against a strict allowlist (jpeg/png/webp/heic/mp4/mov/webm); anything infer can't positively identify (incl. HTML/SVG script payloads) is rejected. Unit tests cover accept + reject paths. M12: the stored extension can no longer carry '/' or arbitrary client text — it's one of the allowlist's safe constants, ending directory-pollution and the export DoS. M5/M6: quota is now reserved with a single conditional UPDATE that row-locks (no TOCTOU overshoot) and the reservation + row INSERT run in one transaction; the file is written before commit and unlinked on any rollback, so a crash mid-write can no longer leak quota bytes or orphan a row. Upload::create is now executor-generic to run inside the tx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,15 +36,21 @@ pub struct UploadDto {
|
||||
}
|
||||
|
||||
impl Upload {
|
||||
pub async fn create(
|
||||
pool: &PgPool,
|
||||
/// Generic over the executor so it can run inside the upload transaction
|
||||
/// (`&mut *tx`) alongside the quota-counter reservation, keeping the
|
||||
/// byte-counter and the row atomic (no quota leak on a mid-write crash).
|
||||
pub async fn create<'e, E>(
|
||||
executor: E,
|
||||
event_id: Uuid,
|
||||
user_id: Uuid,
|
||||
original_path: &str,
|
||||
mime_type: &str,
|
||||
original_size_bytes: i64,
|
||||
caption: Option<&str>,
|
||||
) -> Result<Self, sqlx::Error> {
|
||||
) -> Result<Self, sqlx::Error>
|
||||
where
|
||||
E: sqlx::PgExecutor<'e>,
|
||||
{
|
||||
sqlx::query_as::<_, Self>(
|
||||
"INSERT INTO upload (event_id, user_id, original_path, mime_type, original_size_bytes, caption)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
@@ -56,7 +62,7 @@ impl Upload {
|
||||
.bind(mime_type)
|
||||
.bind(original_size_bytes)
|
||||
.bind(caption)
|
||||
.fetch_one(pool)
|
||||
.fetch_one(executor)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user