Skip to main content

File Validation and Deletion

Enforce file policy, prevent orphans, and delete storage with product metadata safely.

A secure file workflow covers the storage object and the product document that references it.

Validation stages

StagePurpose
Browser size/type checkFast UX feedback
Authorized upload-URL mutationDecide who may begin an upload
Server/content validationVerify actual bytes and product policy
Metadata mutationAssociate storage ID with an authorized resource

Never trust file extension or browser MIME type as the security decision.

Prevent orphans

An upload can succeed before metadata creation fails. Choose an explicit strategy:

  • delete the new storage object in compensation;
  • create a pending upload record before bytes are sent;
  • run a bounded cleanup job for unattached uploads.

The module does not own this product lifecycle.

Authorized deletion

convex/documents.ts
export const removeFile = mutation({
  args: { documentId: v.id('documents') },
  handler: async (ctx, args) => {
    const identity = await requireIdentity(ctx)
    const document = await ctx.db.get(args.documentId)

    if (!document || document.ownerId !== identity.subject) {
      throw new ConvexError({ code: 'DOCUMENT_NOT_FOUND' })
    }

    if (document.storageId) {
      await ctx.storage.delete(document.storageId)
    }
    await ctx.db.patch(document._id, { storageId: undefined })
  },
})

Authorize by the product document, not by accepting an arbitrary storage ID from the browser.

Processing untrusted files

Use a controlled action or external scanning service for formats that require inspection. Record processing status in Convex so the UI can subscribe to pending, accepted, or rejected state.

Do not publish the storage URL until validation finishes when the product's threat model requires quarantine.