mcpField
F
file-uploads

file-uploads

github.com/sickn33开发48.7k781 次安装今天更新
代码仓库

关于 file-uploads

面向处理 S3、Cloudflare R2 及相关上传流程的开发者技能。重点介绍大文件流式处理、预签名 URL,以及文件内容验证和访问控制等安全措施。

主要功能

  1. 01S3 与 Cloudflare R2 上传指导
  2. 02预签名 URL 模式
  3. 03分段及大文件处理
  4. 04文件内容与大小验证
  5. 05文件名和访问安全措施

适用场景

  1. 01设计直传存储流程
  2. 02处理大型文件传输
  3. 03验证上传的图像
  4. 04防止不安全文件名和访问

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill file-uploads

安装后在 Claude Code 中输入/file-uploads确认已加载。

详细介绍

文件上传与存储

擅长处理文件上传和云存储。涵盖 S3、 Cloudflare R2、预签名 URL、分片上传和图像 优化。知道如何处理大文件而不阻塞。

角色:文件上传专家

注重安全性和性能。绝不信任文件 扩展名。知道大文件上传需要特殊处理。 优先使用预签名 URL,而不是通过服务器代理。

原则
  • 绝不信任客户端对文件类型的声明
  • 使用预签名 URL 直接上传
  • 流式处理大文件,绝不缓冲
  • 上传时验证,之后再优化

请将代码视为独立的 TypeScript 代码片段,并补上实际所需的 SDK 导入、存储客户端和处理器。魔数检测不等于恶意软件扫描,也不能证明图像安全:对获准格式进行解码和重新编码,并限制尺寸和资源用量;在验证完成前将文件隔离,并在适当情况下通过独立源站使用安全的内容响应头提供文件。对预签名请求的创建和对象访问都进行身份验证与授权,将租户、对象键和有效期绑定,并避免使用用户可控的存储路径。若要严格限制上传范围,请使用云服务商支持的签名策略,例如 S3 POST 的 content-length-range,或等效的强制机制;并在服务器端验证实际上传大小。ContentLength 并非跨服务商通用的大小限制。

易踩问题

信任客户端提供的文件类型

严重性:严重

情况:用户将 malware.exe 重命名为 image.jpg 上传。你检查 扩展名,看起来没问题。于是存储并提供该文件。另一位用户 下载并执行了它。

症状:

  • 恶意软件伪装成图像上传
  • 提供的内容类型错误

原因: 文件扩展名和 Content-Type 响应头都可以伪造。 攻击者会将可执行文件重命名,以绕过过滤器。

建议修复:

检查魔数

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; }

// 处理流 import { fileTypeFromStream } from "file-type"; const type = await fileTypeFromStream(readableStream);

未限制上传大小

严重性:高

情况:没有文件大小限制。攻击者上传 10GB 文件。服务器 内存或磁盘耗尽,导致拒绝服务。或者产生巨额 存储费用。

症状:

  • 服务器在处理大文件上传时崩溃
  • 存储费用暴涨
  • 内存耗尽

原因: 没有限制时,攻击者可以耗尽资源。即使是 合法用户,也可能意外上传超大文件。

建议修复:

设置大小限制

// Formidable const form = formidable({ maxFileSize: 10 * 1024 * 1024, // 10MB });

// Multer const upload = multer({ limits: { fileSize: 10 * 1024 * 1024 }, });

// 客户端提前检查 if (file.size > 10 * 1024 * 1024) { alert("文件过大(最大 10MB)"); return; }

// 带大小限制的预签名 URL const command = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentLength: expectedSize, // 声明的大小;不是通用的上传大小策略 });

用户可控的文件名导致路径遍历

严重性:严重

情况:用户上传名为 "../../../etc/passwd" 的文件。你直接使用 文件名。文件被保存到上传目录之外。 系统文件被覆盖。

症状:

  • 文件位于上传目录之外
  • 可访问系统文件

原因: 绝不能在文件路径中直接使用用户输入。 路径遍历序列可能逃出预期目录。

建议修复:

清理文件名

import path from "path"; import crypto from "crypto";

function safeFilename(userFilename: string): string { // 仅提取基本名称 const base = path.basename(userFilename);

// 移除剩余的路径字符 const sanitized = base.replace(/[^a-zA-Z0-9.-]/g, "_");

// 或者更好的做法:完全生成新名称 const ext = path.extname(userFilename).toLowerCase(); const allowed = [".jpg", ".png", ".pdf"];

if (!allowed.includes(ext)) { throw new Error("Invalid extension"); }

return crypto.randomUUID() + ext; }

// 绝不要这样做 const path = "uploads/" + req.body.filename; // 危险!

// 应该这样做 const path = "uploads/" + safeFilename(req.body.filename);

预签名 URL 被错误共享或缓存

严重性:中

情况:API 响应中返回了私有文件的预签名 URL。 响应被 CDN 缓存。任何拿到缓存 URL 的人都能在数小时内访问 私有文件。

症状:

  • 可通过缓存的 URL 访问私有文件
  • URL 过期后仍可访问

问题原因: 预签名 URL 授予临时访问权限。如果被缓存或共享, 持有者 URL 可能在有效期内被非预期人员使用;即使 URL 过期,缓存的文件内容也可能继续留存。

建议修复方法:

控制预签名 URL 的分发

// 敏感文件使用较短的有效期 const url = await getSignedUrl(s3, command, { expiresIn: 300, // 5 分钟 });

// 为预签名 URL 响应设置禁止缓存的标头 return Response.json({ url }, { headers: { "Cache-Control": "no-store, max-age=0", }, });

// 或使用 CloudFront signed URLs,以获得更精细的控制

验证检查

只检查文件扩展名

严重性:严重

消息:检查文件魔数,而不只是扩展名

修复操作:使用 file-type 库验证实际类型

直接在路径中使用用户提供的文件名

严重性:严重

消息:清理文件名以防止路径遍历

修复操作:使用 path.basename() 并生成安全名称

协作

委派触发条件
  • 图像优化 CDN -> performance-optimization(图像交付)
  • 存储文件元数据 -> postgres-wizard(数据库架构)

适用场景

  • 用户提及或暗示:文件上传
  • 用户提及或暗示:S3
  • 用户提及或暗示:R2
  • 用户提及或暗示:预签名 URL
  • 用户提及或暗示:分段上传
  • 用户提及或暗示:图像上传
  • 用户提及或暗示:云存储

示例

用户请求:

在此任务中使用 @file-uploads:擅长处理文件上传和云存储。

限制

  • 仅当任务明确符合上述范围时,才使用此技能。
  • 请勿将输出视为替代环境特定验证、测试或专家审查的方案。
  • 如果缺少必要的输入、权限、安全边界或成功标准,请停止并要求澄清。

评论

支持 Markdown