dani-garcia/vaultwarden · error

Error updating attachment

Error message

Error updating attachment

What it means

Completion of a direct attachment upload (POST /ciphers/{cipher_uuid}/attachments/{attachment_id}): when the streamed size differs from the declared size but stays within the ±1MiB leeway, the server rewrites attachment.file_size with .expect("Error updating attachment") — a DB failure here panics after the file bytes were already stored, leaving metadata stale or the row missing.

Source

Thrown at src/api/core/ciphers.rs:1298

    if let Some(attachment) = &mut attachment {
        // v2 API

        // Check the actual size against the size initially provided by
        // the client. Upstream allows +/- 1 MiB deviation from this
        // size, but it's not clear when or why this is needed.
        const LEEWAY: i64 = 1024 * 1024; // 1 MiB
        let Some(max_size) = attachment.file_size.checked_add(LEEWAY) else {
            err!("Invalid attachment size max")
        };
        let Some(min_size) = attachment.file_size.checked_sub(LEEWAY) else {
            err!("Invalid attachment size min")
        };

        if min_size <= size && size <= max_size {
            if size != attachment.file_size {
                // Update the attachment with the actual file size.
                attachment.file_size = size;
                attachment.save(&conn).await.expect("Error updating attachment");
            }
        } else {
            attachment.delete(&conn).await.ok();

            err!(format!("Attachment size mismatch (expected within [{min_size}, {max_size}], got {size})"));
        }
    } else {
        // Legacy API

        // SAFETY: This value is only stored in the database and is not used to access the file system.
        // As a result, the conditions specified by Rocket [0] are met and this is safe to use.
        // [0]: https://docs.rs/rocket/latest/rocket/fs/struct.FileName.html#-danger-
        let encrypted_filename = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string());

        if encrypted_filename.is_none() {
            err!("No filename provided")
        }
        if data.key.is_none() {

View on GitHub (pinned to 0cefa4cca7)

Solutions

  1. Retry the upload from the start; remove the orphaned attachment record on the cipher if needed
  2. Restore DB availability; confirm the attachments row still exists before retrying
  3. Code fix: return a proper error instead of expect

Example fix

// before
attachment.save(&conn).await.expect("Error updating attachment");
// after
attachment.save(&conn).await.map_err(|e| Error::new("Failed to update attachment size", e.to_string()))?;
Defensive patterns

Strategy: try-catch

Validate before calling

-- Verify the pending attachment row still exists before finishing the upload
SELECT id, file_size, akey FROM attachments WHERE id = '<attachment_id>';

Try / catch

match attachment.save(&conn).await {
    Ok(_) => {}
    Err(e) => {
        error!("Failed to update attachment size for {}: {e}", attachment.id);
        return Err(Error::new("Failed to update attachment size", e.to_string()));
    }
}

Prevention

When it happens

Trigger: Finishing an upload where actual size != declared size (within 1MiB) and the update fails: locked DB, the row concurrently deleted, or connection loss at completion time.

Common situations: Flaky networks ending uploads with slightly different sizes; two clients finishing the same attachment id; DB maintenance exactly when uploads complete.

Related errors


AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16). Data as JSON: /api/errors/aa17b9c438abb38f. Report an issue: GitHub.