
File Uploads
Wire secure, non-blocking uploads to S3 or Cloudflare R2 with presigned URLs, multipart handling, and post-upload image optimization.
Overview
File Uploads is an agent skill for the Build phase that guides secure S3 and Cloudflare R2 uploads with presigned URLs, streaming, multipart handling, and magic-byte validation.
Install
npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill file-uploadsWhat is this skill?
- Presigned URLs for direct client-to-bucket uploads without proxying large bodies through your API
- Multipart and streaming patterns so large files never fully buffer in app memory
- Magic-byte validation via file-type—do not trust extensions or client Content-Type
- Image optimization workflow after validated upload
- Four explicit principles: never trust client types, presign, stream, validate-then-optimize
- 4 upload/storage principles
- CRITICAL sharp edge on trusting client file type
Adoption & trust: 628 installs on skills.sh; 40.1k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need users to upload files without blocking your server, exposing malware, or trusting fake image extensions.
Who is it for?
Indie SaaS adding avatars, attachments, or media with AWS S3 or R2 and a Node/TypeScript stack.
Skip if: Static sites with no upload surface or teams that only need local dev file mocks without cloud storage.
When should I use this skill?
Implementing user file uploads, S3/R2 integration, or fixing upload performance and security issues.
What do I get? / Deliverables
Your agent implements direct-to-storage uploads with validated content types and scalable large-file handling.
- Presigned upload flow
- Server-side validation step
- Large-file streaming or multipart configuration
Recommended Skills
Journey fit
How it compares
Use for end-to-end upload security and storage patterns instead of copying a minimal multer example without validation.
Common Questions / FAQ
Who is file-uploads for?
Solo and indie builders shipping SaaS or APIs that store user files on S3 or Cloudflare R2.
When should I use file-uploads?
During Build when implementing upload endpoints, presigned URL flows, multipart uploads, or image pipelines before launch hardening.
Is file-uploads safe to install?
Review the Security Audits panel on this Prism page and treat cloud credentials and upload endpoints as sensitive in your own deployment.
SKILL.md
READMESKILL.md - File Uploads
# File Uploads & Storage Expert at handling file uploads and cloud storage. Covers S3, Cloudflare R2, presigned URLs, multipart uploads, and image optimization. Knows how to handle large files without blocking. **Role**: File Upload Specialist Careful about security and performance. Never trusts file extensions. Knows that large uploads need special handling. Prefers presigned URLs over server proxying. ### Principles - Never trust client file type claims - Use presigned URLs for direct uploads - Stream large files, never buffer - Validate on upload, optimize after ## Sharp Edges ### Trusting client-provided file type Severity: CRITICAL Situation: User uploads malware.exe renamed to image.jpg. You check extension, looks fine. Store it. Serve it. Another user downloads and executes it. Symptoms: - Malware uploaded as images - Wrong content-type served Why this breaks: File extensions and Content-Type headers can be faked. Attackers rename executables to bypass filters. Recommended fix: # CHECK MAGIC BYTES import { fileTypeFromBuffer } from "file-type"; async function validateImage(buffer: Buffer) { const type = await fileTypeFromBuffer(buffer); const allowedTypes = ["image/jpeg", "image/png", "image/webp"]; if (!type || !allowedTypes.includes(type.mime)) { throw new Error("Invalid file type"); } return type; } // For streams import { fileTypeFromStream } from "file-type"; const type = await fileTypeFromStream(readableStream); ### No upload size restrictions Severity: HIGH Situation: No file size limit. Attacker uploads 10GB file. Server runs out of memory or disk. Denial of service. Or massive storage bill. Symptoms: - Server crashes on large uploads - Massive storage bills - Memory exhaustion Why this breaks: Without limits, attackers can exhaust resources. Even legitimate users might accidentally upload huge files. Recommended fix: # SET SIZE LIMITS // Formidable const form = formidable({ maxFileSize: 10 * 1024 * 1024, // 10MB }); // Multer const upload = multer({ limits: { fileSize: 10 * 1024 * 1024 }, }); // Client-side early check if (file.size > 10 * 1024 * 1024) { alert("File too large (max 10MB)"); return; } // Presigned URL with size limit const command = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentLength: expectedSize, // Enforce size }); ### User-controlled filename allows path traversal Severity: CRITICAL Situation: User uploads file named "../../../etc/passwd". You use filename directly. File saved outside upload directory. System files overwritten. Symptoms: - Files outside upload directory - System file access Why this breaks: User input should never be used directly in file paths. Path traversal sequences can escape intended directories. Recommended fix: # SANITIZE FILENAMES import path from "path"; import crypto from "crypto"; function safeFilename(userFilename: string): string { // Extract just the base name const base = path.basename(userFilename); // Remove any remaining path chars const sanitized = base.replace(/[^a-zA-Z0-9.-]/g, "_"); // Or better: generate new name entirely const ext = path.extname(userFilename).toLowerCase(); const allowed = [".jpg", ".png", ".pdf"]; if (!allowed.includes(ext)) { throw new Error("Invalid extension"); } return crypto.randomUUID() + ext; } // Never do this const path = "uploads/" + req.body.filename; // DANGER! // Do this const path = "uploads/" + safeFilename(req.body.filename); ### Presigned URL shared or cached incorrectly Severity: MEDIUM Situation: Presigned URL for private file returned in API response. Response cached by CDN. Anyone with