2025年12月02日·1分で読めます

ファイルアップロードのウイルススキャン:アプリ向けのアーキテクチャ選択肢

ドキュメント多用アプリ向けにファイルアップロードのウイルススキャンを解説:隔離ストレージ、スキャンキュー、アクセス制御、リトライ、そして安全なリリースワークフロー。

ファイルアップロードのウイルススキャン:アプリ向けのアーキテクチャ選択肢

問題を平易に説明すると:アプリに安全でないファイルが入ってくること

ユーザーがドキュメントをアップロードできるアプリでは、あなたが作っていないファイルを受け入れています。顧客ポータル、HRシステム、保険請求アプリ、ベンダーオンボーディングのようにドキュメントが多い製品ではアップロードが頻繁に行われ、ユーザーはメールスレッドや共有ドライブ、第三者から受け取ったファイルをそのまま共有することが多いです。そうした環境は現実的な攻撃対象になりやすく、1回の成功したアップロードが多くのダウンロードに拡がる可能性があります。

リスクは単なる“ウイルス”だけではありません。WordやExcelは悪意あるマクロを含むことがあり、PDFはリーダーのバグを突くように作られることがあります。「請求書」に見せかけたフィッシング文書が偽の電話番号に誘導したり、認証情報を入力させようとする例もあります。ZIPにペイロードを隠す、二重拡張子(report.pdf.exe)を使う、リモートコンテンツを埋め込んで開くと外部と通信するようにするなど、静かに毒されるケースもあります。

単一サーバーにインストールされた簡易なアンチウイルスに頼るだけでは不十分です。アップロードは複数のアプリインスタンスに到達したり、ストレージ間で移動したり、オブジェクトストレージやCDNから配信されたりします。いずれかの経路で生のアップロードを誤って公開してしまうと、スキャン完了前にユーザーがダウンロードできてしまいます。設定ミスや一時的な管理者権限の付与、アップデートも時間とともにスキャンをバイパスする原因になります。

ファイルアップロードのウイルススキャンにおける明確な目標はシンプルです:スキャンされていないファイルが、明示的に隔離コンテンツのレビューを許可された人以外にダウンロードや閲覧されるべきではない、ということです。

「安全」の定義は感覚ではなくビジネスルールで決めてください。例えば:

  • 最新のシグネチャセットでマルウェアスキャンを通過していること
  • 許可されたファイルタイプとサイズ制限に一致していること
  • 承認された場所からのみ保管・配信されていること
  • 監査記録があること:誰が、いつアップロードし、最終状態は何か
  • 最終判断(リリースまたは拒否)が出るまでブロックされること

AppMaster のようなプラットフォームで構築する場合は、データモデルに「スキャンステータス」を第一級フィールドとして扱い、あらゆるダウンロード操作がそれをチェックするようにしてください。その一つのゲートで多くの高コストなミスを防げます。

アップロード文書の「隔離」が意味するもの

「隔離」は単なるストレージ内のフォルダではなく、システム内の状態(ステート)として考えるのが適切です。キーとなる考え方は単純です:ファイルは存在するが、アプリに明確で記録されたスキャン結果が出るまで誰も開いたりダウンロードしたりできない。これがファイルアップロードのウイルススキャンの核です。

隔離は通常、小さなライフサイクルとして明確なステータスを持ちます。状態を明示的に保持すると、プレビューや直接URL、エクスポートジョブからの誤漏洩を起こしにくくなります。

実用的なファイル状態は次のようになります:

  • received(アップロード完了、未スキャン)
  • scanning(ワーカーが処理中)
  • clean(安全でリリース可能)
  • rejected(マルウェア検出またはポリシー違反)
  • failed(スキャナエラー、タイムアウト、破損)

隔離にはアクセス制御と後の調査のための適切なメタデータも必要です。最低限、保管すべき情報は:所有者(ユーザーまたは組織)、ステータス、元のファイル名とタイプ、チェックサム(重複排除や改ざん検査用)、ストレージ位置、タイムスタンプ(アップロード、スキャン開始、スキャン終了)です。多くのチームはスキャナのバージョンやスキャン判定の詳細も保存します。

保持期間はポリシー決定ですが、意図的であるべきです。隔離されたファイルはスキャンと障害調査に必要な期間だけ保持し、短めの保持にするとリスクとコストを下げられます。ただしユーザーから「私のアップロードはどこに行きましたか?」と問われたときに対応できるだけのタイムウィンドウは残してください。

最後に、スキャンが完了しないファイルへの対応を決めてください。最大スキャン時間と“有効期限”のタイムスタンプを設定し、期限切れになったらファイルを failed に移し、アクセスをブロックして限定回数だけ自動再試行するか、削除してユーザーに再アップロードを促すようにします。

リスクを減らす一時ストレージパターン

一時ストレージはほとんどのアップロード問題が起きる場所です。ファイルはシステム内にあるが安全かどうかわからないため、簡単にロックダウンでき偶発的に公開されにくい場所が必要です。

ローカルディスクは単一サーバーでは動きますが脆弱です。複数のアプリサーバーにスケールするとストレージ共有、ファイルコピー、権限の一貫性維持が必要になります。オブジェクトストレージ(S3スタイルのバケットやクラウドコンテナ)は、アクセスルールが集中管理されログも明確なため、ドキュメントが多いアプリでは安全な選択であることが多いです。

ファイルアップロードのウイルススキャンでシンプルなパターンは「隔離」と「クリーン」を分離することです。これを二つのバケットやコンテナで実現するとミスが起きにくくなりますし、単一バケット内で厳格なプレフィックス構造にすることでコストや管理のしやすさを優先することもできます。

プレフィックスを使う場合は混同できないようにしてください。次のようなレイアウトを推奨します: quarantine/<tenant_id>/<upload_id>clean/<tenant_id>/<document_id>。ユーザー提供の名前を使わないでください。同じパスを異なる状態で再利用してはいけません。

以下のルールを守ってください:

  • 隔離に対してパブリック読み取りを許可しないこと(たとえ一時的でも)
  • サーバー側でオブジェクト名を生成し、クライアント名を使わないこと
  • テナントやアカウントでパーティション分けして被害範囲を限定すること
  • メタデータ(所有者、ステータス、チェックサム)はファイル名ではなくデータベースに保存すること

保存時は暗号化を行い、復号できる権限を厳しく制限してください。アップロードAPIは隔離に書き込み、スキャナは隔離から読み取りクリーンへ書き込めるようにし、パブリック向けアプリはクリーンからのみ読み取るべきです。クラウドがキーのポリシーをサポートしていれば、復号権限を最小限のロールに紐づけてください。

大きなファイルは追加の注意が必要です。マルチパートアップロードでは、最終パートがコミットされ、期待されるサイズとチェックサムが記録されるまでオブジェクトを「準備完了」としてマークしないでください。一般的に安全な方法は、パーツを隔離にアップロードしてから、スキャン通過後にオブジェクトをコピーまたは昇格(promote)してクリーンに移すことです。

例:AppMaster で作る顧客ポータルでは、すべてのアップロードを「保留」と扱い隔離バケットに保存し、スキャン結果が「clean」に反転するまでダウンロードボタンを表示しない、という方式がとれます。

アーキテクチャの選択肢:インラインスキャン vs バックスグラウンドスキャン

ファイルアップロードのウイルススキャンを導入するとき、一般的に選ぶフローは二つ:インラインでスキャンする(ユーザーが待つ)か、バックグラウンドでスキャンする(アップロードは受け付けるがアクセスをブロックする)か、です。どちらが正しいかは「セキュリティレベル」だけで決まるわけではなく、速度、信頼性、アップロード頻度により決まります。

オプション1:インラインスキャン(ユーザーが待つ)

インラインスキャンは、スキャナが結果を返すまでアップロードリクエストが完了しません。アップロード→スキャン→受け入れ/拒否の一段で済むためシンプルに感じられます。

ファイルが小さくアップロードが稀で待ち時間を予測しやすい場合にはインラインは受け入れられます。例えばチームツールでPDFを数枚アップロードするだけなら3〜10秒の遅延は許容されるかもしれません。欠点はスキャンが遅いとアプリ全体が遅くなることです。タイムアウトやリトライ、モバイルネットワークの不安定さが悪いUXを生む可能性があります。

オプション2:バックグラウンドスキャン(非同期)

非同期スキャンはファイルを先に保存し「隔離」にマークし、スキャンジョブをキューに流します。ユーザーは高速に「アップロードを受領しました」という応答を得ますが、スキャンが通るまではダウンロードやプレビューができません。

この方法は大容量ファイルや大量のアップロード、高負荷時間帯に適しており、作業を分散させアプリの応答性を保てます。スキャンワーカーをメインのWeb/APIサーバーとは別にスケールできる点も利点です。

実用的なハイブリッドとして、インラインで簡単なチェック(許可ファイルタイプ、サイズ制限、基本的なフォーマット検証)を行い、本格的なウイルススキャンはバックグラウンドで行うという方法があります。これにより明らかな問題は早期に弾け、すべてのユーザーに待ち時間を強いる必要がなくなります。

選び方の簡単な指針:

  • 小さいファイル、低頻度、即座に結果が必要なワークフロー:インラインスキャン
  • 大きなファイル、多数のアップロード、スキャン時間が不確定:バックグラウンドスキャン
  • アップロード応答時間のSLAが厳しい:バックグラウンドスキャン+明確なステータスUI
  • 混在するワークロード:ハイブリッド(早期チェック+非同期フルスキャン)

AppMaster で構築する場合、この選択は同期APIエンドポイント(インライン)か、スキャン作業をエンキューして結果到着時にファイルステータスを更新するビジネスプロセス(非同期)に自然にマッピングされます。

ステップバイステップ:非同期スキャンキューの構築

安全なアップロードワークフローを構築する
隔離状態をモデル化し、ダウンロードのゲートをコードを書かずに強制します。
AppMasterを試す

非同期スキャンは、アップロードを受け付けて隔離に固定し、バックグラウンドでスキャンする方式です。ユーザーはスキャナが安全と判断するまでアクセスできません。ドキュメントが多いアプリでは、通常これが最も実用的なマルウェアスキャンの設計です。

1) キューメッセージを定義する(小さく保つ)

キューはやることリストとして扱ってください。各アップロードはファイルを指す1件のメッセージを作成し、ファイル自体を含めないでください。

シンプルなメッセージには通常:

  • ファイルID(またはオブジェクトキー)とテナント/プロジェクトID
  • アップロードしたユーザーID
  • アップロード時刻とチェックサム(任意だが便利)
  • 試行回数(または別のリトライカウンタ)

生のバイト列をキューに入れるのは避けてください。大きなペイロードは制限を破る、コストが増える、露出が増える原因になります。

2) ワーカーフローを作る(取得、スキャン、記録)

ワーカーはメッセージを引き、隔離ストレージからファイルを取得し、スキャンし、その結果を書き戻します。

明確なフローは:

  • 隔離ストレージ(プライベートバケットやプライベートボリューム)からIDでファイルを取得
  • スキャナ(AVエンジンやスキャンサービス)を実行
  • 結果をデータベースに書き込む:ステータス(clean、infected、error)、スキャナ名/バージョン、タイムスタンプ
  • clean の場合:ファイルを承認済みストレージに移動するか、ダウンロード可能にするフラグを反転
  • infected の場合:隔離したままにする(または削除)し、関係者へ通知

3) 冪等性を担保する(再処理しても安全に)

ワーカーはクラッシュする、メッセージが二度配信される、リトライが発生することがあります。同じファイルを二度スキャンしても問題にならない設計にしてください。files.status のような唯一の真実のソースを使い、有効な状態遷移のみを許可します。例えば:uploaded -> scanning -> clean/infected/error。ワーカーが clean を見たら何もしないでメッセージをACKすべきです。

4) 同時実行制御(スキャンの嵐を避ける)

ワーカーとテナントごとに上限を設定してください。同時に走れるスキャン数を制限し、大きなファイル用に別キューを検討します。これにより一つの顧客がスキャナ容量を独占するのを防げます。

5) 障害対応:リトライと監査軌跡

一時的なエラー(スキャナのタイムアウト、ネットワーク問題)にはリトライを使い、最大試行回数を少なく設定します。その後はデッドレターキューに送って手動レビューに回します。

監査ログを保持してください:誰がドキュメントをアップロードし、いつ隔離に入ったか、どのスキャナが動いたか、何を決定したか、誰が承認または削除したか。カスタマーポータルやコンプライアンスではこのログがスキャン本体と同じくらい重要です。

アクセス制御:隔離ファイルを本当にプライベートに保つ

隔離はデータベース上のステータスだけではありません。それは「検査が完了するまで誰もファイルを開けない」という約束です。最も安全なルールはシンプルです:一時的なものであっても、隔離ファイルをパブリックURLで配信しないこと。

良いダウンロードフローは地味で厳格です。アプリは画像を取得するような扱いではなく、保護されたアクションとして毎回ダウンロードを扱うべきです。

推奨フロー:

  1. ダウンロードを要求する
  2. そのファイルへのユーザーの権限をチェックする
  3. ファイルのステータスを確認する(quarantined、clean、rejected)
  4. ステータスが clean のときだけファイルを配信する

署名付きURLを使う場合でも考え方は同じ:権限とステータスチェック後にのみ生成し、有効期限は短くしてください。有効期限を短くすることで、ログやスクリーンショット、転送されたメールでリンクが漏れても被害を減らせます。

ロールベースのアクセス制御は「特例ロジック」が穴になるのを防ぎます。ドキュメント重視のアプリで典型的な役割は:

  • Uploader:自分のアップロードとスキャンステータスを見られる
  • Reviewer:クリーンなファイルを閲覧でき、隔離ファイルは安全なレビュー用ツール内でのみ見ることができる
  • Admin:調査、再スキャン、必要に応じたオーバーライドができる
  • External user:明示的に共有されたドキュメントだけにアクセス可能

ID推測への対策も行ってください。増分ID(12345など)を公開しないでください。オペークなIDを使い、ユーザーごと・ファイルごとの認可チェックを常に行ってください(単に「ログイン済みならOK」ではだめです)。プライベートバケットでも、不注意なAPIエンドポイントが隔離コンテンツを漏らす恐れがあります。

ファイルアップロードのウイルススキャンで現実に起きる失敗の多くはアクセス層で起きます。AppMaster のようなプラットフォームでは、これらのチェックをAPIエンドポイントとビジネスロジックに組み込み、隔離がデフォルトでプライベートになるように設計してください。

リリース、拒否、リトライ:スキャン結果の処理

安全なドキュメントポータルを作る
スキャン通過までダウンロードがブロックされる安全なカスタマーポータルを構築します。
アプリを開始

ファイルがスキャンを終えたら、最も重要なのは明確な一つの状態に移し、次のステップを予測可能にすることです。スキャン結果はゲートのように扱い、ゲートが開くまで何もダウンロード可能にしないでください。

一般的な結果は次のとおりです:

  • Clean:隔離からリリースし通常のアクセスを許可する。
  • Infected:アクセスを恒久的にブロックし、感染ファイルの対応ワークフローを起動する。
  • Unsupported:スキャナが評価できない型(またはパスワード保護されたファイル)。ブロックしたままにする。
  • Scan error:一時的な失敗(タイムアウト、サービス利用不可)。ブロックしたままにする。

ユーザー向けメッセージは明確かつ落ち着いた文言にしてください。「あなたのアカウントが侵害されました」のような恐ろしい表現は避けます。良い例は:「ファイルを確認中です。作業を続けてください。」ファイルがブロックされた場合は次に何ができるかを示します:「別のファイル形式をアップロードしてください」や「後でもう一度お試しください」など。サポートされないファイルについては具体的に伝えます(例:「暗号化されたアーカイブはスキャンできません」)。

感染ファイルについては早い段階で削除するか保持するかを決めてください。削除はリスクを下げ簡潔ですが、監査のために保持する場合は隔離領域で厳しくアクセスを制限し短期間にし、誰が見られるかのログを残すこと(理想的にはセキュリティ管理者のみ)が必要です。

リトライは有用ですが、一時的なエラーに限るべきです。無限のバックログを生まないように小さなリトライポリシーを設定してください:

  • タイムアウトやスキャナのダウン時にリトライ
  • “infected” や “unsupported” はリトライしない
  • リトライ上限(例:3回)を設定し、その後は failed にする
  • 試行間にバックオフを入れ、過負荷を避ける

最後に、繰り返す失敗はユーザーの問題ではなく運用の問題として扱ってください。短時間に多数のファイルが “scan error” になるならチームにアラートを出し、新しいリリースを止めることを検討してください。AppMaster ではデータベースでこれらの状態をモデル化し、内蔵のメッセージングで通知を流せるため、問題を迅速に知ることができます。

例:大量ドキュメントを扱うカスタマーポータル

アップロードの監査ログを追加する
誰がアップロードし、いつスキャンされ、最終判断がどうなったかを記録する監査フィールドを追加します。
試してみる

カスタマーポータルで顧客がプロジェクトごとに請求書や契約書をアップロードするとします。ここはファイルアップロードのウイルススキャンが重要になる典型例です。ユーザーがデスクトップからそのままドラッグするファイルや、他の人から転送されたファイルが混ざるためです。

顧客がPDFをアップロードすると、ポータルはそれを一時的な非公開場所に保存し、データベースのレコードを作成してステータスを Pending scan に設定します。ファイルはこの時点でダウンロード可能として表示されません。スキャンワーカーがキューからファイルを引き、スキャン実行後にレコードを Clean または Blocked に更新します。

UIでは、顧客はファイルがすぐに表示されるが明確に Pending バッジが付くのを見ます。ファイル名とサイズは表示してアップロードが成功したことが分かるようにし、ダウンロードボタンはスキャンが clean になるまで無効にします。スキャンが想定より長くかかる場合は「このファイルは安全性を確認中です。数分後にお試しください。」のような簡潔なメッセージを表示できます。

スキャナによりドキュメントがフラグされた場合、顧客は Blocked と短い非技術的な説明を見ます:「このファイルはセキュリティチェックに失敗しました。」サポートと管理者は別のビューでスキャン理由と次のアクションを確認できます。彼らは:

  • ブロックしたままにして新しいアップロードを依頼する
  • 削除して理由を記録する
  • ポリシーで許される場合に誤検知としてのみ手動でマークする

「昨日アップロードしたのに無くなった」という紛争が起きた場合は、適切なログが重要です。アップロード受領、スキャン開始、スキャン終了、ステータス変更、誰が何をしたかのタイムスタンプだけでなく、ファイルハッシュ、元ファイル名、アップローダーアカウント、IPアドレス、スキャナ結果コードも保存してください。AppMaster で構築すれば Data Designer と Business Process フローでこれらのステータスと監査フィールドを管理し、通常ユーザーに隔離ファイルを見せないようにできます。

実際のセキュリティギャップを生むよくある間違い

ほとんどのアップロードセキュリティ失敗は派手なハックではありません。設計上の小さな選択が、安全でないファイルを普通のドキュメントと同じように扱わせてしまうことが原因です。

古典的な問題はレース条件です:アプリがアップロードを受け入れ、ダウンロード用URLを返し、ユーザー(あるいは別のサービス)がスキャン完了前にファイルを取得できてしまう場合です。ファイルを「アップロード済み」と「利用可能」に分けて扱ってください。

よくあるミスは:

  • クリーンと隔離を同じバケット/フォルダに混在させ、命名規則に頼ること。一つの誤った権限やパスの推測で隔離は無意味になります。
  • 拡張子やMIMEタイプ、クライアントサイドチェックを信頼すること。攻撃者は何でも .pdf にリネームできます。
  • スキャナのダウンタイムを想定しないこと。スキャナが遅いかオフラインだとファイルが永遠に pending に残り、チームが危険な手動オーバーライドを入れがちです。
  • バックグラウンドワーカーがメインAPIと同じ認可ルールを通らないこと。ワーカーが「どのファイルでも読める」権限を持つと静かな権限昇格になります。
  • 隔離アイテムに推測しやすいID(増分番号など)を公開すること。コンテンツは保護されていると思っても、怠慢なAPIが漏らすことがあります。

テストの不足もギャップの一つです。チームは少数の小さなクリーンファイルでテストして終わりにしがちです。大きなアップロード、破損ファイル、パスワード保護されたドキュメントもテストしてください。これらがスキャナやパーサーが失敗またはタイムアウトする典型的なケースです。

実例:カスタマーポータルのユーザーが「contract.pdf」としてアップロードしたものが、実はアーカイブ内のリネームされた実行ファイルだったとします。ポータルが即時にそれを配信したり、サポートチームが適切なチェックなしに隔離にアクセスできると、他のユーザーに直接配信される道を作ってしまいます。

出荷前のクイックチェックリスト

隔離とクリーンを分離する
各テナントごとにプライベートな隔離ストレージとクリーンなストレージ経路を設計します。
始める

ファイルアップロードのウイルススキャンを出荷する前に、チームが「大丈夫だろう」と思い込みやすい箇所をもう一度確認してください。目標はシンプルです:誰かがURLを推測したり、リクエストを再試行したり、古いキャッシュリンクを使っただけで安全でないファイルが読めてしまってはいけません。

ユーザーフローから始めてください。どんなダウンロード、プレビュー、または「ファイルを開く」アクションも、アップロード時だけでなく要求時に現在のスキャンステータスを再チェックするべきです。これによりレース条件(すぐにクリックする人)、遅延したスキャン結果、再スキャン時のエッジケースから守れます。

最低限の出荷チェックリスト:

  • 隔離ストレージはデフォルトで非公開:パブリックバケットアクセスなし、"リンクを知っている人なら誰でも" の設定なし、オブジェクトストレージからの直接配信なし
  • すべてのファイルレコードに所有者(ユーザー、チーム、テナント)と pending、clean、infected、failed のような明確なライフサイクルステートがある
  • スキャンキューとワーカーに有界のリトライ、明確なバックオフルールがあり、詰まったり繰り返し失敗したときにアラートが出る
  • アップロード、スキャン結果、ダウンロード試行(ブロックされた試行を含む)の監査ログが存在し、誰が、いつ、なぜを記録している
  • 手動オーバーライドは稀なケース用に存在するが、管理者専用で記録され、時間制限付き(黙って "mark clean" するボタンは不可)

最後に、システムをエンドツーエンドで観測できるようにしてください:「現在スキャン保留中のファイルは何件か?」「どのテナントで失敗が出ているか?」といった質問に答えられることが重要です。AppMaster を使うなら Data Designer でファイルのライフサイクルをモデル化し、Business Process Editor で状態チェックを強制してルールを一貫させてください。

次のステップ:設計を動くアプリにする

まずファイルが取りうる正確な状態と、それぞれの状態で何が許されるかを書き出してください。シンプルで明示的に:"uploaded", "queued", "scanning", "clean", "infected", "scan_failed" のようにし、各状態の横にアクセスルールを書きます。誰がファイルを見られるか、ダウンロードできるか、信頼されていない間に削除できるかを明確にします。

次に、自分たちのトラフィック量とユーザー体験目標に合うアプローチを選んでください。インラインスキャンは説明が簡単ですがアップロードを遅くします。非同期スキャンはドキュメントが多いアプリに向いていますが、状態管理、キュー、保留UIが必要になります。

設計から構築へ移す実用的な方法は、実際のドキュメント(PDF、Officeファイル、画像、アーカイブ)と現実的なユーザー行動(複数アップロード、キャンセル、リトライ)を使ってフロー全体をプロトタイプすることです。"スキャナが動く" だけで終わらず、隔離ファイルを偶発的に配信しないことを検証してください。

1週間で実行できるシンプルな構築計画:

  • ファイル状態、遷移、アクセスルールを1ページに定義する
  • インライン、非同期、ハイブリッドのどれを使うか選び、トレードオフを書き残す
  • アップロード -> 隔離ストレージ -> スキャンジョブ -> 結果コールバック を実装し、監査ログを追加する
  • ユーザーが見るUI状態(pending、blocked、failed、approved)を作る
  • 最初から監視を入れる:バックログサイズ、失敗率、クリーンになるまでの時間

ノーコードで構築する場合、AppMaster はファイルメタデータ(ステータス、所有者、チェックサム、タイムスタンプ)をモデル化し、アップロードとレビュー画面を構築し、ビジネスロジックとキュー処理でスキャンワークフローをオーケストレーションできます。これにより、権限、ストレージ分離、信頼できるリトライの部分を早期に検証して固められます。

最後に「良い」の基準を数字で決めてください。事前にアラート閾値を設定して、ユーザーより先に詰まったスキャンや増加する失敗を検知できるようにします。

始めやすい
何かを作成する 素晴らしい

無料プランで AppMaster を試してみてください。
準備が整ったら、適切なサブスクリプションを選択できます。

始める