署名付きURL、厳格なタイプ・サイズチェック、非同期マルウェアスキャン、権限ルールでトラフィック増加時にも高速を保つ大規模向けの安全なファイルアップロード設計。

ファイルアップロードは一見簡単ですが、実際のユーザーが増えると問題が出てきます。まずはプロフィール写真を一人がアップロードします。次に同時に1万人がPDFや動画、スプレッドシートをアップすると、アプリが遅く感じられ、ストレージコストが急増し、サポートに問い合わせが殺到します。
よくある失敗パターンは予測可能です。サーバーがファイルの全バイトを扱おうとしてアップロードページがハングしたりタイムアウトしたりする。権限設定がずれて誰かがファイルURLを推測して見てはいけないものを見てしまう。"無害"に見えるファイルにマルウェアが潜んでいたり、下流のツールをクラッシュさせる巧妙な形式が混じっている。ログが不十分で、誰がいつ何をアップロードしたかすら追えない。
望ましいのは地味で確実なものです:高速なアップロード、明確なルール(許可するタイプとサイズ)、インシデント調査がしやすい監査トレイル。
最も難しいトレードオフは速度と安全性のバランスです。すべてのチェックをユーザーが終わる前に行うと待ち時間が増えてリトライが発生し負荷が増える。チェックを延ばしすぎると、不正や未承認のファイルが検出前に広まる可能性があります。実用的な方法はアップロードと検査を分離し、それぞれを素早く測定可能に保つことです。
また「スケール」を具体的に定義してください。1日あたりのファイル数、ピーク時の分あたりアップロード数、最大ファイルサイズ、ユーザーの所在地などを数値で書き出すと地域ごとのレイテンシやプライバシー要件の設計に役立ちます。
Koder.ai のようなプラットフォーム上でアプリを作る場合、これらの制限を早めに決めると権限設計、ストレージ、バックグラウンドのスキャンワークフローの方針が定まりやすくなります。
ツールを選ぶ前に、何が問題になるかを明確にしてください。脅威モデルは大きな文書である必要はありません。防止すべきこと、後で検出可能なこと、受け入れるトレードオフを簡潔に共有するだけで十分です。
攻撃者は通常、いくつかの予測可能なポイントから侵入しようとします:クライアント(メタデータ改ざんやMIMEタイプの偽装)、ネットワークエッジ(リプレイやレートリミット乱用)、ストレージ(オブジェクト名の推測、上書き)、ダウンロード/プレビュー(危険なレンダリングを誘発したり共有アクセスでファイルを盗む)などです。
そこから脅威を単純な制御に結び付けます:
過大なファイルは最も簡単な悪用です。コストをかさ上げし、本物のユーザーを遅くします。早期にバイト上限で拒否してください。
偽のファイルタイプも多い問題です。invoice.pdf という名前でも別物かもしれません。拡張子やUIのチェックを信用せず、アップロード後に実際のバイトで検証してください。
マルウェアは別問題です。アップロード完了前に全てをスキャンするとユーザー体験が悪化するため、非同期に検出し疑わしいものを隔離して、スキャン完了までアクセスをブロックするパターンが現実的です。
未承認アクセスは最も被害が大きくなりがちです。アップロードとダウンロードの都度、権限判定を行ってください。ユーザーは自分が所有する(または書き込みが許可された)場所にのみアップロードでき、見てよいファイルのみダウンロードできるべきです。
多くのアプリで有効な v1 ポリシー例:
最速の処理方法はアプリサーバーを“バイトの処理”から切り離すことです。すべてのファイルをバックエンド経由で送る代わりに、クライアントが短時間有効な署名付きURLを使って直接オブジェクトストレージにアップロードするようにします。バックエンドは判断と記録に集中し、ギガバイトの転送は扱いません。
分担は簡単です:バックエンドは「誰が何をどこにアップロードできるか」に答え、ストレージがファイルデータを受け取ります。これにより、アプリサーバーが認可判定とファイルのプロキシを二重にこなしてボトルネック化するのを防げます。
データベース(例:PostgreSQL)に小さなアップロードレコードを保持して、各ファイルに明確な所有者とライフサイクルを持たせてください。アップロード開始前にこのレコードを作り、イベントに応じて更新します。
有用なフィールド例:オーナーとテナント/ワークスペース識別子、ストレージオブジェクトキー、ステータス、主張されたサイズとMIMEタイプ、検証可能なチェックサム。
アップロードを状態機械として扱えば、リトライ時にも権限チェックが正しく保てます。
実用的な状態セット例:
バックエンドが requested レコードを作ってからクライアントに署名付きURLを渡してください。ストレージがアップロードを確認したら uploaded に移し、バックグラウンドでマルウェアスキャンを開始し、approved になるまで公開しない運用にします。
ユーザーがアップロードをクリックすると始まります。アプリはファイル名、ファイルサイズ、用途(アバター、請求書、添付)などの基本情報でバックエンドに開始をリクエストします。バックエンドは特定のターゲットに対する権限をチェックし、アップロードレコードを作成して短時間有効な署名付きURLを返します。
署名付きURLは限定的にしてください。理想的には一つのオブジェクトキーへの単一アップロードのみを許可し、短い有効期限と明確な条件(サイズ制限、許可コンテンツタイプ、任意のチェックサム)を付けます。
ブラウザはそのURLを使って直接ストレージにアップロードします。完了したらブラウザはバックエンドに最終化を通知します。最終化時に再度権限を確認(アクセスが失われている可能性があるため)し、ストレージに実際に何があるかを検証してください:サイズ、検出されたコンテンツタイプ、使うならチェックサム。最終化は冪等にして、リトライで重複が生じないようにします。
その後レコードを uploaded にして、バックグラウンド(キュー/ジョブ)でスキャンをトリガーします。UIはスキャン中に「処理中」と表示できます。
拡張子に頼ると invoice.pdf.exe のようなファイルがバケットに入ってしまいます。検証は繰り返し実行できるチェックのセットとして複数箇所で行ってください。
まずはサイズ制限です。最大サイズを署名付きURLのポリシー(またはプレサインドPOST条件)に入れると、ストレージ側で早期に拒否できます。クライアントがUIをバイパスしようとする可能性があるので、バックエンドでも同じ上限を最終化時に再度強制してください。
タイプチェックはファイルの内容に基づくべきです。ファイルの先頭バイト(マジックバイト)を検査して期待する形式と一致するか確認します。本物のPDFは先頭が %PDF、PNGは固定のシグネチャを持ちます。内容が許可リストに合わなければ、拡張子が正しく見えても拒否してください。
許可リストは機能ごとに具体的にしてください。アバターなら JPEG と PNG のみ、ドキュメント機能なら PDF と DOCX を許可する、といった具合です。ルールが狭いほどリスクは減り、説明も簡単になります。
元のファイル名をそのままストレージキーに使わないでください。表示用には正規化(特殊文字除去、長さ調整)を行い、実際のオブジェクトキーは自分で決めます(UUID+タイプに応じた拡張子など)。
データベースにチェックサム(例:SHA-256)を保存し、後で処理やスキャン時に比較できるようにしてください。これにより破損や部分的なアップロード、リトライ時の改ざんを検出できます。
マルウェアスキャンは重要ですが、クリティカルパスに入れてユーザーを待たせるべきではありません。アップロードを素早く受け入れ、ファイルをブロック状態にしてからスキャンを実行してください。
pending_scan のようなステータスでアップロードレコードを作ります。UIではファイルを見せても、そのまま使わせないようにします。
スキャンは通常、オブジェクト作成時のストレージイベントでトリガーするか、アップロード完了後にジョブをキューに投げるか、あるいは両方(バックアップとしてのストレージイベント)で起動します。
スキャンワーカーはオブジェクトをダウンロードまたはストリームで取得し、スキャナを実行して結果をデータベースに書き戻します。必須の情報はスキャン状態、スキャナのバージョン、タイムスタンプ、誰がアップロードしたかなどです。これがあれば「なぜファイルがブロックされたのか?」という問い合わせに答えやすくなります。
失敗したファイルをクリーンなものと混ぜないでください。隔離してアクセスを取り除くか、調査が不要なら削除するか、どちらかのポリシーを一貫して適用します。
ユーザーへのメッセージは冷静で具体的に。再アップロードを促すかサポートに連絡するよう案内し、短時間に多数の失敗が発生したらチームにアラートを出してください。
最も重要なのはダウンロードとプレビューに関する厳格なルールです:approved とマークされたファイルだけを配信し、それ以外は「ファイルはまだ確認中です」などの安全な応答を返します。
高速なアップロードは良いですが、誤った人が誤ったワークスペースにファイルを添付できると問題は深刻です。最も単純で強力なルールは:各ファイルレコードは厳密に一つのテナント(ワークスペース/組織/プロジェクト)に属し、明確なオーナーまたは作成者を持つことです。
権限チェックは二度行ってください:署名付きアップロードURLを発行するときと、誰かがファイルをダウンロードまたは表示しようとするときです。最初のチェックで不正なアップロードを止め、二回目のチェックでアクセスが取り消されたりURLが漏洩したりした場合に守ります。
最小権限の原則はセキュリティとパフォーマンスの両方を予測可能にします。単一の広範な「files」権限ではなく、「アップロード可」「閲覧可」「管理(削除/共有)可」のように役割を分けると、多くのリクエストはユーザー・テナント・アクションの簡単なルックアップで済むようになります。
ID推測を防ぐため、URLやAPIで連番のファイルIDを使わないでください。不透明な識別子を使い、ストレージキーは推測不能にします。署名付きURLは輸送手段であり、権限システムそのものではないことを忘れないでください。
共有はシステムが遅く混乱しがちな部分です。共有は暗黙のアクセスではなく明示的なデータとして扱ってください。単純な方法は、ユーザーやグループにあるファイルへのアクセスを付与する別テーブル(共有レコード)を作り、オプションで有効期限を持たせることです。
セキュアなアップロードのスケール化では、しばしばセキュリティチェックに注目して基本を忘れがちです:バイトを移動すること自体が遅いのです。目標は大きなファイルのトラフィックをアプリサーバーから遠ざけ、リトライを抑え、安全チェックが無制限のキューにならないようにすることです。
大きなファイルは multipart やチャンクアップロードを使い、不安定な接続で最初からやり直す必要がないようにします。チャンクは合計サイズ、チャンク最大サイズ、アップロード時間の上限などを明確に enforcement するのにも役立ちます。
クライアントのタイムアウトとリトライは意図的に設定してください。少数のリトライは実ユーザーを救いますが、無制限のリトライは特にモバイル回線でコストを膨らませます。チャンクごとの短いタイムアウト、小さなリトライ上限、全体のハードデッドラインを目標にしてください。
署名付きURLは重いデータパスを高速にしますが、それを作るリクエスト自体がホットスポットになり得ます。応答性を保つために以下を実施してください:
レイテンシは地理的配置によっても左右されます。可能ならアプリ、ストレージ、スキャンワーカーを同一リージョンに置いてください。国別のホスティングが必要な場合は、アップロードが大陸を横断しないよう早めにルーティングを計画します。AWS 上でグローバルに動くプラットフォーム(Koder.ai のような)では、データ居住性が重要な場合にユーザーに近い場所にワークロードを置けます。
最後にダウンロード計画も立ててください。署名付きダウンロードURLで配信し、ファイルタイプやプライバシーレベルに応じたキャッシュルールを設定します。公開アセットは長めにキャッシュ、プライベートな領収書などは短期間かつ権限チェックを行います。
従業員が請求書や領収書の写真をアップロードし、マネージャーが承認して経費精算する小規模ビジネス向けアプリを想像してください。ユーザーが多く、大きな画像が入り、金銭が絡む場面では設計が実務的になります。
良いフローは明確なステータスを使って自動化できる部分を増やします:ファイルはオブジェクトストレージに置かれ、ユーザー/ワークスペース/経費に紐づくレコードを保存。バックグラウンドジョブがファイルをスキャンし基本的なメタデータ(実際のMIMEタイプなど)を抽出し、承認されればレポートで使えるように、拒否されればブロックします。
ユーザーには速く具体的なフィードバックが必要です。ファイルが大きすぎる場合は上限と現在サイズを表示(例:「ファイルは18MBです。最大は10MBです。」)。タイプが間違っている場合は許可される形式を示す(「PDF、JPG、PNGをアップロードしてください」)。スキャンが失敗した場合は落ち着いた行動案内を(「このファイルは安全でない可能性があります。新しいコピーをアップロードしてください。」)。
サポートチームはファイルを開かずにデバッグできる情報を求めます:アップロードID、ユーザーID、ワークスペースID、created/uploaded/scan started/scan finished のタイムスタンプ、結果コード(サイズ超過、タイプ不一致、スキャン失敗、認可拒否 など)、ストレージキー、チェックサム。
再アップロードや差し替えはよくある操作です。新しいアップロードとして扱い、同じ経費に新しいバージョンとして紐づけ、履歴(誰がいつ差し替えたか)を残し、最新バージョンのみアクティブにしてください。Koder.ai 上で作るなら、uploads テーブルと expense_attachments テーブルに version フィールドを持たせるとマッピングしやすいです。
ほとんどのアップロードバグは目新しい技巧ではなく、小さな近道がトラフィック増加時にリスクになるパターンです。
より多くのチェックがアップロードを遅くする必要はありません。高速パスと重い処理を分離してください。
迅速に行うチェック(認可、サイズ、許可タイプ、レート制限)を同期的に行い、スキャンや深い検査はバックグラウンドワーカーに委ねます。ユーザーはファイルが uploaded から ready に移る間も作業を続けられます。チャットベースのビルダー(Koder.ai のような)で構築する場合も同じ考え方:アップロードエンドポイントは小さく厳密にし、スキャンや後処理はジョブに投げてください。
リリース前に「v1 として十分安全」を定義してください。チームは厳しすぎるルール(実ユーザーをブロックする)とルール不足(悪用を招く)の間で失敗しがちです。まずは小さく始め、すべてのアップロードに「受領」から「ダウンロード許可」までの明確な経路があることを確認してください。
出荷前のタイトなチェックリスト:
最小有効ポリシーが必要なら、シンプルに:サイズ上限、狭いタイプ許可リスト、署名付きURLアップロード、「スキャン通過まで隔離」。コアが安定したらプレビューや対応タイプの追加、バックグラウンド再処理などを後から加えます。
モニタリングは成長中に「高速」が「原因不明の遅さ」に変わらないようにするための鍵です。アップロード失敗率(クライアント側とサーバー/ストレージ側)、スキャン失敗率とスキャンレイテンシ、ファイルサイズバケットごとの平均アップロード時間、ダウンロード時の認可拒否、ストレージのエグレスパターンを追跡してください。
実際のファイルサイズと現実的なネットワーク(モバイル回線はオフィスのWi‑Fiと挙動が異なります)で小規模な負荷テストを行い、ローンチ前にタイムアウトとリトライを修正してください。
Koder.ai(koder.ai)でこれを実装する場合、まず Planning Mode でアップロード状態とエンドポイントをマッピングし、そのフローを中心にバックエンドとUIを生成すると実用的です。スナップショットとロールバックも、制限やスキャンルールを調整するときに役立ちます。
直接オブジェクトストレージへのアップロードを短時間有効な署名付きURLで行えば、アプリサーバーがファイルのバイトを中継する必要がなくなります。バックエンドは認可判定とアップロード状態の記録に注力し、ギガバイトの転送はストレージに任せます。
二度チェックしてください。一度目は署名付きURLを発行するとき(アップロード作成時)、二度目は最終化時とダウンロード提供時です。署名付きURLは運搬手段でしかないので、ファイルレコードとテナント/ワークスペースに紐づく認可をアプリ側で管理する必要があります。
リトライや部分失敗でセキュリティの穴ができないよう、状態機械として扱ってください。一般的なフローは requested, uploaded, scanned, approved, rejected で、ダウンロードは approved のときのみ許可します。
署名付きURLのポリシー(またはプレサインドPOST条件)にバイト単位の上限を入れて、ストレージ側で大きすぎるアップロードを早期に拒否させてください。最終化時にもストレージのメタデータで同じ上限を再確認し、クライアント側だけを信頼しないでください。
ファイル名の拡張子やブラウザが送るMIMEタイプを信用しないでください。アップロード後に実際のバイト列(マジックバイト)を検査し、期待する形式と照合してください。例:本物のPDFは先頭が %PDF で、PNGは固定のシグネチャを持ちます。
ユーザーに待たせないためにスキャンをクリティカルパスに入れないでください。アップロードを受け入れて隔離(quarantine)し、バックグラウンドでスキャンを行い、クリーンと判定されるまでダウンロード/プレビューを許可しない運用にします。
一貫したポリシーを決めて適用してください。隔離してアクセスを遮断するか、調査の必要がなければ削除するかを統一し、ユーザーには落ち着いた具体的なメッセージ(再アップロードやサポートへの連絡方法など)を返してください。サポートのために監査データは必ず残します。
ユーザー提供の名前やパスをそのままストレージキーに使わないでください。衝突や推測を避けるために UUID 等の推測不能なオブジェクトキーを生成し、元のファイル名は表示用メタデータとして正規化して保存してください。
不安定な接続では multipart/chunked アップロードを使い、接続が切れても途中から再開できるようにします。リトライは上限を設け、タイムアウトと全体のデッドラインを決めて一つのクライアントが無限にリソースを占有しないようにしてください。
デバッグのために最小限のアップロードレコードを残してください。owner, tenant/workspace, object key, status, タイムスタンプ, 検出されたタイプ, サイズ, (使うなら)チェックサム等があれば十分です。Koder.ai 上で構築する場合は、Go バックエンド、PostgreSQL の uploads テーブル、スキャン用のバックグラウンドジョブという構成が合います。