jpskill.com
🛠️ 開発・MCP コミュニティ 🔴 エンジニア向け 👤 エンジニア・AI開発者

🛠️ ファイルUploads

file-uploads

S3やCloudflare R2などのクラウドストレージを活用し、署名付きURLやマルチパートアップロードで大容量ファイルもスムーズに処理し、画像最適化も行うSkill。

⏱ 障害ポストモーテム 1日 → 1時間

📺 まず動画で見る(YouTube)

▶ 【衝撃】最強のAIエージェント「Claude Code」の最新機能・使い方・プログラミングをAIで効率化する超実践術を解説! ↗

※ jpskill.com 編集部が参考用に選んだ動画です。動画の内容と Skill の挙動は厳密には一致しないことがあります。

📜 元の英語説明(参考)

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.

🇯🇵 日本人クリエイター向け解説

一言でいうと

S3やCloudflare R2などのクラウドストレージを活用し、署名付きURLやマルチパートアップロードで大容量ファイルもスムーズに処理し、画像最適化も行うSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o file-uploads.zip https://jpskill.com/download/2853.zip && unzip -o file-uploads.zip && rm file-uploads.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/2853.zip -OutFile "$d\file-uploads.zip"; Expand-Archive "$d\file-uploads.zip" -DestinationPath $d -Force; ri "$d\file-uploads.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して file-uploads.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → file-uploads フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-17
取得日時
2026-05-17
同梱ファイル
1

💬 こう話しかけるだけ — サンプルプロンプト

  • File Uploads を使って、最小構成のサンプルコードを示して
  • File Uploads の主な使い方と注意点を教えて
  • File Uploads を既存プロジェクトに組み込む方法を教えて

これをClaude Code に貼るだけで、このSkillが自動発動します。

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] file-uploads

ファイルアップロードとストレージ

ファイルアップロードとクラウドストレージの扱いに長けています。S3、Cloudflare R2、署名付きURL、マルチパートアップロード、画像最適化をカバーしています。ブロックせずに大容量ファイルを処理する方法を知っています。

役割: ファイルアップロードスペシャリスト

セキュリティとパフォーマンスに注意を払います。ファイル拡張子を信用することはありません。大容量のアップロードには特別な処理が必要であることを知っています。サーバープロキシよりも署名付きURLを好みます。

原則

  • クライアントのファイルタイプ宣言を信用しない
  • 直接アップロードには署名付きURLを使用する
  • 大容量ファイルはストリーミングし、バッファリングしない
  • アップロード時に検証し、後で最適化する

危険な落とし穴

クライアントが提供するファイルタイプを信用する

深刻度: CRITICAL

状況: ユーザーが 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;
}

// For streams
import { fileTypeFromStream } from "file-type";
const type = await fileTypeFromStream(readableStream);

アップロードサイズ制限がない

深刻度: HIGH

状況: ファイルサイズ制限がありません。攻撃者が10GBのファイルをアップロードします。サーバーのメモリまたはディスクが不足します。サービス拒否が発生します。または莫大なストレージ料金が発生します。

症状:

  • 大容量アップロード時にサーバーがクラッシュする
  • 莫大なストレージ料金
  • メモリ枯渇

なぜこれが問題なのか: 制限がないと、攻撃者はリソースを枯渇させることができます。正当なユーザーでも誤って巨大なファイルをアップロードしてしまう可能性があります。

推奨される修正:

サイズ制限を設定する

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

ユーザーが制御するファイル名がパス・トラバーサルを許容する

深刻度: CRITICAL

状況: ユーザーが "../../../etc/passwd" という名前のファイルをアップロードします。ファイル名を直接使用します。ファイルがアップロードディレクトリの外に保存されます。システムファイルが上書きされます。

症状:

  • アップロードディレクトリ外のファイル
  • システムファイルへのアクセス

なぜこれが問題なのか: ユーザー入力はファイルパスに直接使用すべきではありません。パス・トラバーサルシーケンスは意図されたディレクトリから抜け出すことができます。

推奨される修正:

ファイル名をサニタイズする

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

署名付きURLが誤って共有またはキャッシュされる

深刻度: MEDIUM

状況: プライベートファイル用の署名付きURLがAPIレスポンスで返されます。レスポンスがCDNによってキャッシュされます。キャッシュされたURLを持つ誰もが、何時間もプライベートファイルにアクセスできます。

症状:

  • キャッシュされたURL経由でプライベートファイルにアクセス可能
  • 有効期限切れ後のアクセス

なぜこれが問題なのか: 署名付きURLは一時的なアクセスを許可します。キャッシュまたは共有されると、アクセスは意図された範囲を超えて延長されます。

推奨される修正:

署名付きURLの配布を制御する

// Short expiry for sensitive files
const url = await getSignedUrl(s3, command, {
  expiresIn: 300, // 5 minutes
});

// No-cache headers for presigned URL responses
return Response.json({ url }, {
  headers: {
    "Cache-Control": "no-store, max-age=0",
  },
});

// Or use CloudFront signed URLs for more control

検証チェック

ファイル拡張子のみをチェックしている

深刻度: CRITICAL

メッセージ: 拡張子だけでなく、マジックバイトをチェックしてください

修正アクション: file-type ライブラリを使用して実際のタイプを検証する

ユーザーのファイル名がパスに直接使用されている

深刻度: CRITICAL

メッセージ: パス・トラバーサルを防ぐためにファイル名をサニタイズしてください

修正アクション: path.basename() を使用して安全な名前を生成する

コラボレーション

委譲トリガー

  • 画像最適化 CDN -> performance-optimization (画像配信)
  • ファイルメタデータの保存 -> postgres-wizard (データベーススキーマ)

使用する場面

  • ユーザーが「ファイルアップロード」について言及または示唆している場合
  • ユーザーが「S3」について言及または示唆している場合
  • ユーザーが「R2」について言及または示唆している場合
  • ユーザーが「presigned URL」について言及または示唆している場合
  • ユーザーが「multipart」について言及または示唆している場合
  • ユーザーが「画像アップロード」について言及または示唆している場合
  • ユーザーが「クラウドストレージ」について言及または示唆している場合

制限事項

  • このスキルは、タスクが上記で説明されている範囲と明確に一致する場合にのみ使用してください。
  • 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
  • 必要な入力、権限、安全境界、または成功基準が不足している場合は、停止して説明を求めてください。
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

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 cached URL can access private file for hours.

Symptoms:

  • Private files accessible via cached URLs
  • Access after expiry

Why this breaks: Presigned URLs grant temporary access. If cached or shared, access extends beyond intended scope.

Recommended fix:

CONTROL PRESIGNED URL DISTRIBUTION

// Short expiry for sensitive files const url = await getSignedUrl(s3, command, { expiresIn: 300, // 5 minutes });

// No-cache headers for presigned URL responses return Response.json({ url }, { headers: { "Cache-Control": "no-store, max-age=0", }, });

// Or use CloudFront signed URLs for more control

Validation Checks

Only checking file extension

Severity: CRITICAL

Message: Check magic bytes, not just extension

Fix action: Use file-type library to verify actual type

User filename used directly in path

Severity: CRITICAL

Message: Sanitize filenames to prevent path traversal

Fix action: Use path.basename() and generate safe name

Collaboration

Delegation Triggers

  • image optimization CDN -> performance-optimization (Image delivery)
  • storing file metadata -> postgres-wizard (Database schema)

When to Use

  • User mentions or implies: file upload
  • User mentions or implies: S3
  • User mentions or implies: R2
  • User mentions or implies: presigned URL
  • User mentions or implies: multipart
  • User mentions or implies: image upload
  • User mentions or implies: cloud storage

Limitations

  • Use this skill only when the task clearly matches the scope described above.
  • Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
  • Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.