file-uploads について
S3、Cloudflare R2などを使う開発者向けのファイルアップロードとクラウドストレージのスキルです。大容量ファイルのストリーミング、署名付きURL、ファイル内容の検証やアクセス制御を扱います。
主な機能
- 01S3とCloudflare R2へのアップロード案内
- 02署名付きURLのパターン
- 03マルチパートと大容量ファイルの処理
- 04ファイル内容とサイズの検証
- 05ファイル名とアクセスの保護
活用シーン
- 01ストレージへの直接アップロード設計
- 02大容量ファイル転送の処理
- 03アップロード画像の検証
- 04危険なファイル名やアクセスの防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill file-uploadsインストール後、Claude Code で/file-uploadsを入力して読み込まれたか確認します。
詳細
ファイルのアップロードとストレージ
ファイルのアップロードとクラウドストレージの処理に精通しています。S3、 Cloudflare R2、署名付き URL、マルチパートアップロード、画像の 最適化を扱います。処理をブロックせずに大容量ファイルを扱う方法を理解しています。
役割:ファイルアップロードスペシャリスト
セキュリティとパフォーマンスに注意を払います。ファイルの 拡張子を決して信用しません。大容量ファイルのアップロードには特別な処理が必要だと理解しています。 サーバー経由のプロキシよりも署名付き URL を優先します。
原則
- クライアントが申告するファイルタイプを決して信用しない
- 直接アップロードには署名付き URL を使用する
- 大容量ファイルはストリーム処理し、決してバッファーに格納しない
- アップロード時に検証し、その後に最適化する
コードは、実際の SDK インポート、ストレージクライアント、ハンドラーが必要となる独立した TypeScript の断片として扱ってください。マジックバイト検出は、マルウェアスキャンでも安全な画像である証明でもありません。承認済み形式を、寸法とリソースを制限してデコード/再エンコードし、検証が完了するまで隔離してください。また、適切な場合は別のオリジンから安全なコンテンツヘッダーを付けて配信してください。署名付き URL の作成とオブジェクトへのアクセスの両方で認証/認可を行い、テナント、キー、有効期限を紐付け、ユーザーが指定するストレージパスは避けてください。アップロードサイズに厳密な範囲を設ける場合は、S3 の POST content-length-range や同等の強制機能など、プロバイダーがサポートする署名付きポリシーを使用してください。アップロード後に実際のサイズをサーバー側で検証してください。ContentLength だけでは、プロバイダーを問わず使えるサイズ制限にはなりません。
注意すべき点
クライアントから申告されたファイルタイプを信用する
重大度:重大
状況:ユーザーが malware.exe を image.jpg に名前変更してアップロードします。拡張子を確認すると問題ないように見えるので、そのまま保存して配信します。別のユーザーがダウンロードして実行します。
症状:
- 画像としてマルウェアがアップロードされる
- 誤った content-type で配信される
この問題が起きる理由: ファイルの拡張子と 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 の共有または誤ったキャッシュ
重大度:中
状況:非公開ファイルの署名付き URL が API レスポンスで返されます。 レスポンスが CDN によってキャッシュされます。キャッシュされた 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 の署名付き URL を使用します
検証チェック
ファイル拡張子のみをチェックする
重大度:重大
メッセージ:拡張子だけでなく、マジックバイトをチェックしてください
修正アクション:file-type ライブラリを使用して実際のファイルタイプを検証してください
ユーザーのファイル名をパスに直接使用する
重大度:重大
メッセージ:パストラバーサルを防ぐためにファイル名をサニタイズしてください
修正アクション:path.basename() を使用し、安全な名前を生成してください
コラボレーション
委任のトリガー
- 画像最適化 CDN → performance-optimization(画像配信)
- ファイルメタデータの保存 → postgres-wizard(データベーススキーマ)
使用する場面
- ユーザーがファイルアップロードについて言及、または示唆している場合
- ユーザーが S3 について言及、または示唆している場合
- ユーザーが R2 について言及、または示唆している場合
- ユーザーが署名付き URL について言及、または示唆している場合
- ユーザーがマルチパートについて言及、または示唆している場合
- ユーザーが画像のアップロードについて言及、または示唆している場合
- ユーザーがクラウドストレージについて言及、または示唆している場合
例
ユーザーのリクエスト:
このタスクには @file-uploads を使用してください:ファイルアップロードとクラウドストレージのエキスパートです。
制限事項
- このスキルは、タスクが上記の説明された範囲に明確に該当する場合にのみ使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を止めて確認を求めてください。