![[Amazon GuardDuty] 100 GB のファイルを S3 マルウェアスキャンに通して、上限の境界・所要時間・料金を実測してみました](https://devio2024-media.developers.io/image/upload/f_auto,q_auto,w_3840/f_auto,q_auto/v1790871164/eyecatch/k3cuozozlxyjhmtrmur3.png)
[Amazon GuardDuty] 100 GB のファイルを S3 マルウェアスキャンに通して、上限の境界・所要時間・料金を実測してみました
1 はじめに
製造ビジネステクノロジー部の平内(SIN)です。
Amazon GuardDuty Malware Protection for S3 は、2025 年 9 月にスキャンできる 1 ファイルの上限が 5 GB から 100 GB に引き上げられました。
手元で作成中の UI で、サイズ上限を超えるファイルをエラー扱いしたかったのですが、ドキュメントの「100 GB」が 10 進(100,000,000,000 バイト)なのか 2 進(100 GiB = 107,374,182,400 バイト)なのか、そして 100 GB ちょうどは通るのかを、自分の利用に不安が残らないよう確かめておきたくなりました。
上限ぎりぎりのファイルを通すと何分くらいかかり、請求額がいくらぐらいになるかも知りたかったので、今回は 100 GB 前後の乱数ファイルを 1 バイト単位でサイズを変えて GuardDuty に通し、上限の境界・所要時間・請求額を実測してみました。
先に結論を書かせていただくと、次の 4 点でした。
| 問い | 実測の結果 |
|---|---|
| 上限の「100 GB」はどこまでか | 2 進の 100 GiB、107,374,182,400 バイトまで(この値を含む)。1 バイト超えると SKIPPED |
| 上限を超えるとどうなるか | スキャンせず、アップロード完了と同時に SKIPPED。タグは UNSUPPORTED で、理由(OBJECT_SIZE_LIMIT_EXCEEDED)は結果イベントにだけ出る |
| 100 GB の処理時間は | アップロード完了から 397〜467 秒(約 6.5〜8 分) |
| 利用費は | 100,000,000,000 バイトで約 $11.04。請求の「GB」は GiB でした |
なお、本稿はあくまで特定環境(東京リージョン、2026 年 11 月時点)で試した一例として捉えていただければと思います。
2 公式情報
2026 年 11 月時点で確認した内容です。
(1) 料金
東京リージョンの単価は次のとおりです。
| 項目 | 単価 | 無料枠 |
|---|---|---|
| スキャンしたデータ量 | $0.1185 / GB | 月 1 GB |
| スキャンしたオブジェクト数 | $0.282 / 1,000 件 | 月 1,000 件 |
https://aws.amazon.com/guardduty/pricing/](https://aws.amazon.com/guardduty/pricing/)
(2) 上限
クォータのページを見ると、最大オブジェクトサイズは 100 GB(調整不可)と書かれていました。
https://docs.aws.amazon.com/guardduty/latest/ug/malware-protection-s3-quotas-guardduty.html
上限を超えたときの扱いは、スキャン結果のページに書かれていました。
| 項目 | 値 |
|---|---|
| scanStatus | SKIPPED |
| scanResultStatus | UNSUPPORTED |
| statusReasons | OBJECT_SIZE_LIMIT_EXCEEDED |
| オブジェクトのタグ | UNSUPPORTED |
https://docs.aws.amazon.com/guardduty/latest/ug/monitoring-malware-protection-s3-scans-gdu.html
3 検証の構成
(1) 構成図
構成は次のとおりです。CDK で作成されたのは、スキャン対象のバケット、保護プラン、結果を残すログ、テストデータを作る EC2 です。

| 役割 | 内容 |
|---|---|
| S3 バケット | スキャン対象。保護プランは scan/ のプレフィックスだけを対象にする |
| 保護プラン | GuardDuty Malware Protection for S3。結果をオブジェクトタグでも返す |
| 結果ログ | Scan Result イベントを EventBridge で CloudWatch Logs に残す |
| 生成用 EC2 | 乱数を S3 にストリーミングで上げ、終わると自分で終了する |
GuardDuty の Detector(GuardDuty 本体の有効化)は不要です。Malware Protection for S3 は単独で利用できます。
https://docs.aws.amazon.com/guardduty/latest/ug/gdu-malware-protection-s3.html
なお、GuardDuty 本体が有効なアカウントでは、eicar.com の検出で次のような Finding も生成されました。ドキュメントには、本体を有効化していない(Detector が無い)場合は Finding を生成できない、と書かれています。本体が無効な状態は試していないので、実際に出ないことまでは確認できませんでした。
https://docs.aws.amazon.com/guardduty/latest/ug/monitoring-malware-protection-s3-scans-gdu.html

(2) 保護プラン
保護プランは CloudFormation の AWS::GuardDuty::MalwareProtectionPlan で作りました。
Github cdk/lib/stack.ts
// 要点抜粋
const plan = new guardduty.CfnMalwareProtectionPlan(this, 'MalwareProtectionPlan', {
role: planRole.roleArn,
protectedResource: { s3Bucket: { bucketName: bucket.bucketName, objectPrefixes: [SCAN_PREFIX] } },
// 結果をオブジェクトタグ GuardDutyMalwareScanStatus でも返す(アップロード前に有効化が必要)
actions: { tagging: { status: 'ENABLED' } },
})
// スキャン結果イベントを Logs に残す
new events.Rule(this, 'ScanResultRule', {
eventPattern: { source: ['aws.guardduty'], detailType: ['GuardDuty Malware Protection Object Scan Result'] },
targets: [new targets.CloudWatchLogGroup(logGroup)],
})
objectPrefixes でスキャン対象を scan/ 配下に絞っています。同じバケットには EC2 の生成ログも置くので、スキャンして課金されるのを検証用のファイルだけにするためです。
デプロイすると、GuardDuty コンソールの「S3 の Malware Protection」に保護されたバケットとして表示されます。

プラン用の IAM ロールは、公式のポリシーテンプレートのうち、SSE-S3 のバケットに必要なものだけを入れています。バケットが SSE-KMS の場合は、テンプレートにある KMS の許可もあわせて必要です。
https://docs.aws.amazon.com/guardduty/latest/ug/malware-protection-s3-iam-policy-prerequisite.html

(3) バージョン
| 項目 | 値 |
|---|---|
| AWS CDK | aws-cdk-lib 2.273.0 / aws-cdk(CLI)2.1145.0 |
| Node.js / pnpm | 22.23.4 / 10.24.0 |
| 生成用 EC2 | c7g.xlarge(arm64)、Amazon Linux 2023 の最新 AMI |
4 テストファイル
(1) 乱数のストリーミング
テストファイルは、同じリージョンに立てた EC2 から上げることにしました。手元の回線では 100 GB にかなり時間がかかりそうなのと、アップロードが終わった時刻を EC2 側のログに正確に残したかったからです。
乱数は指定したバイト数だけ作り、ディスクに置かずにそのまま S3 に流し込んでいます。大きな EBS は必要ありません。
Github scripts/userdata.sh
# 要点抜粋(EC2 のユーザーデータ)
# 100 GB を 1 本で上げるので、パートを大きめに・並列を増やす(パート数の上限 10,000 にも収まる)
aws configure set default.s3.multipart_chunksize 64MB
aws configure set default.s3.max_concurrent_requests 20
log "random upload start key=${PREFIX}${KEY} size=$SIZE"
head -c "$SIZE" /dev/urandom | aws s3 cp - "s3://$BUCKET/${PREFIX}${KEY}" --expected-size "$SIZE"
log "random upload end rc=$?"
標準入力から上げると AWS CLI は全体のサイズを知らないので、--expected-size を渡してパートの大きさを決めさせています。
EC2 からの 100 GB のアップロードは、約 4.5〜5 分で終わりました。
乱数にしたのは、固定のパターンだと圧縮や重複排除が効いて、読み取る量が変わってしまうかもしれないと思ったからです。
(2) EICAR
検出の確認には、EICAR のテスト文字列を使いました。当初は乱数の末尾に連結しようと考えたのですが、EICAR の仕様では、テスト文字列がファイルの先頭で、全体が 128 文字以内であることが必要だったので、別ファイルにしました。テスト文字列だけを入れた 68 バイトのファイル(eicar.com)を作り、乱数ファイルより先に上げるように userdata.sh に組み込んでいます。1 秒ほどで THREATS_FOUND が返るので、その回のスキャンが動いていることの確認にもなります。
(3) EC2
生成用の EC2 は、アップロードが終わると自分で shutdown して削除されるようになっています。ガードとして最悪でも 4 時間後には必ず終了するようにしました。
# 要点抜粋(EC2 のユーザーデータ)
shutdown -h +240
trap 'aws s3 cp /tmp/generate.log "s3://$BUCKET/logs/..."; shutdown -h now' EXIT
生成ログ(アップロードの開始・終了時刻)は S3 の logs/ に残しています。これが所要時間の起点になります。
5 計測方法
(1) 起点と終点
| 項目 | 何を使うか |
|---|---|
| 起点 | 生成ログのアップロード終了時刻(upload end) |
| 終点 | Scan Result イベントの time |
| 読み取り量 | CloudWatch の CompletedScanBytes |
Scan Result イベントの detail には、スキャンの開始・終了時刻のフィールドが見当たりませんでした。そこで、イベントそのものの time を終点にしています。eicar.com の結果イベントは次のような JSON で、scanStatus と scanResultStatus、検出した脅威の名前が入っています。

https://docs.aws.amazon.com/guardduty/latest/ug/monitor-with-eventbridge-s3-malware-protection.html
起点には、当初 S3 オブジェクトの LastModified を使うつもりでした。しかし、マルチパートアップロードで上げたオブジェクトの LastModified を見てみると、アップロードを開始した側の時刻になっていました。
| 項目 | 時刻(UTC) |
|---|---|
| 生成ログ: upload start | 21:28:42.701 |
| LastModified | 21:28:43 |
| 生成ログ: upload end | 21:33:16.971 |
100 GB ではアップロードに 4.5 分かかるので、LastModified を起点にすると、その分だけ所要時間が長く出てしまいます。そこで、EC2 側で記録したアップロード終了時刻を起点にすることにしました。
読み取り量の CompletedScanBytes は、ディメンションに「Resource Name」(バケット名)と「Malware Protection Plan Id」の両方を指定して取得します。
$ aws cloudwatch get-metric-statistics --namespace AWS/GuardDuty/MalwareProtection \
--metric-name CompletedScanBytes \
--dimensions Name="Resource Name",Value="$BUCKET" Name="Malware Protection Plan Id",Value="$PLAN_ID" \
--start-time ... --end-time ... --period 3600 --statistics Sum
コンソールで見る場合も同じで、「Resource Name, Malware Protection Plan Id」のディメンションの組み合わせを選ぶと出てきます。画面は 1 MB のファイルを通したときのものです。

(2) 確認スクリプト
タグ、生成ログ、結果イベント、CompletedScanBytes を表示するスクリプトが用意されています。
Github scripts/result.sh
$ ./scripts/generate.sh 107374182400 eq-100gib.bin
$ ./scripts/result.sh eq-100gib.bin
# => 出力例(要点抜粋)
== upload end of scan/eq-100gib.bin: 2026-10-25T10:19:50.305Z
== GuardDuty scan result events (/aws/events/guardduty-s3-malware-scan-100gb)
2026-10-25T10:27:00Z scan/eq-100gib.bin COMPLETED NO_THREATS_FOUND None
-> upload end 2026-10-25T10:19:50.305Z から 429.7 秒
== CompletedScanBytes (直近 2 日・1 時間ごと)
2026-10-25T18:37:00+09:00 207374182537.0
6 結果
(1) 10 進の 100 GB
最初に、10 進の 100 GB ちょうど(100,000,000,000 バイト)を通してみました。その前に、1 GB でリハーサルもしています。
| ファイル | サイズ(バイト) | 結果 | upload end から結果まで |
|---|---|---|---|
| eicar.com | 68 | COMPLETED / THREATS_FOUND | 約 1 秒 |
| 1 GB | 1,000,000,000 | COMPLETED / NO_THREATS_FOUND | 24.5 秒 |
| 10 進の 100 GB | 100,000,000,000 | COMPLETED / NO_THREATS_FOUND | 397.0 秒 |
10 進の 100 GB ちょうどは、SKIPPED にならずにスキャンされました。CompletedScanBytes の合計は 101,000,000,136 バイトで、100 GB と 1 GB と eicar.com 2 回分の合計に一致したので、最後まで読まれています。
スキャンが終わると、オブジェクトに GuardDutyMalwareScanStatus のタグが付きます。eicar.com は THREATS_FOUND でした。

(2) 境界テスト
10 進の 100 GB が通っただけでは、上限が 10 進の 100 GB なのか 100 GiB なのかは区別できません。そこで、1 バイト超えのサイズで挟みました。SKIPPED ならほぼ費用がかからず、COMPLETED なら約 $11〜12 かかるので、1 回ごとに結果を見てから次に進めています。
| 順番 | サイズ(バイト) | 意味 | SKIPPED なら | COMPLETED なら |
|---|---|---|---|---|
| ① | 100,000,000,001 | 10 進の 100 GB + 1 | 上限は 10 進の 100 GB で確定 | ② へ |
| ② | 107,374,182,401 | 100 GiB + 1 | ③ へ | 上限は 100 GiB より上 |
| ③ | 107,374,182,400 | 100 GiB ちょうど | 上限は ① と ③ の間 | 上限は 100 GiB で確定 |
結果は次のとおりです。
| サイズ(バイト) | 意味 | 結果 | upload end から結果まで | 読んだバイト数 |
|---|---|---|---|---|
| 100,000,000,000 | 10 進の 100 GB | COMPLETED / NO_THREATS_FOUND | 397.0 秒 | 全量 |
| 100,000,000,001 | 10 進の 100 GB + 1 | COMPLETED / NO_THREATS_FOUND | 467.2 秒 | 全量 |
| 107,374,182,400 | 100 GiB | COMPLETED / NO_THREATS_FOUND | 429.7 秒 | 全量 |
| 107,374,182,401 | 100 GiB + 1 | SKIPPED / UNSUPPORTED | 0 秒 | 0 |
上限は 2 進の 100 GiB、107,374,182,400 バイトでした。この値ちょうどはスキャンされ、1 バイト超えるとスキップされます。スキップ理由の説明にある「exceeds(超える)」のとおり、上限ちょうどは含まれる「以下」でした。これで、ドキュメントの「100 GB」を 100 GiB として、不安なく設計に使えます。
https://docs.aws.amazon.com/guardduty/latest/ug/monitoring-malware-protection-s3-scans-gdu.html
なお、② が SKIPPED だった時点で止める案もありましたが、1 バイト単位で言い切れるかどうかで情報の価値が変わると思い、③ を実施しました。
(3) 上限超え
100 GiB + 1 のファイルは、アップロードが完了するとほぼ同時に結果が出ました。
2026-10-25T09:57:43.977Z random upload end rc=0 # 生成ログ
2026-10-25T09:57:44Z scan/over-100gib.bin SKIPPED UNSUPPORTED ['OBJECT_SIZE_LIMIT_EXCEEDED']
スキャンは行われず、サイズだけで即座に判定されているようです。読み取り量は 0 で、CompletedScanBytes はこのファイルの分だけ増えていません。タグは UNSUPPORTED で、理由の OBJECT_SIZE_LIMIT_EXCEEDED は結果イベントにだけ出ます。タグだけで後続の処理を分ける運用だと、サイズ超過かそれ以外かは区別できないので、理由まで扱うなら EventBridge の結果イベントを受けたほうが良いように思います。
(4) 所要時間
100 GB 前後の 3 回は、397〜467 秒でした。
| サイズ(バイト) | upload end から結果まで | 実効スループット |
|---|---|---|
| 100,000,000,000 | 397.0 秒 | 約 252 MB/s |
| 100,000,000,001 | 467.2 秒 | 約 214 MB/s |
| 107,374,182,400 | 429.7 秒 | 約 250 MB/s |
1 GB が 24.5 秒だったので、時間はサイズに比例していません。大きなファイルを扱う設計では、100 GB で約 7 分前後と見ておき、少しのぶれは考慮しておいたほうが良いと思います。
7 請求額
(1) 10 進の 100 GB
10 進の 100 GB を通した日の請求を、CUR(Cost and Usage Report)で確認しました。
| UsageType | UsageAmount | 単価 | 金額 |
|---|---|---|---|
| APN1-MalwareProtectionS3DataScanned | 94.0635801600 GB | $0.1185 | $11.15 |
| APN1-MalwareProtectionS3ScanRequest | 4 件 | 無料枠内 | $0 |
この日にスキャンしたのは 101,000,000,136 バイト(100 GB + 1 GB + eicar.com 2 回分)です。2^30 で割ると 94.06358016 になり、UsageAmount と小数第 8 位まで一致しました。請求の「GB」は GiB で数えられています。
| ファイル | GiB | 金額 |
|---|---|---|
| 10 進の 100 GB(100,000,000,000 バイト) | 93.13 | 約 $11.04 |
| 1 GB のリハーサル | 0.93 | 約 $0.11 |
見込みの $11.85 に対して、100 GB 1 本の実額は約 $11.04 でした。
なお、CUR の行は ResourceId が空で、バケットや保護プランごとには分けられません。時間帯と件数・バイト数の一致から、検証の分の行を特定しています。
(2) 境界テスト
境界テストの日(① ② ③ と eicar.com 3 回)の請求です。
| UsageType | UsageAmount | 単価 | 金額 |
|---|---|---|---|
| APN1-MalwareProtectionS3DataScanned | 193.1322576497 GB | $0.1185 | $22.89 |
| APN1-MalwareProtectionS3ScanRequest | 6 件 | 無料枠内 | $0 |
スキャンしたのは ① の 100,000,000,001 バイト、③ の 107,374,182,400 バイト、eicar.com 3 回分で、2^30 で割ると 193.13225765 になり、UsageAmount と小数第 8 位まで一致しました。ScanRequest は 6 件なので、SKIPPED になった ② もリクエストとしては数えられています。ただ、データ量には ② の分が含まれていないので、上限を超えるファイルを置いてもデータ量の課金は発生しないと見てよさそうです。
8 最後に
大きなファイルを扱う場合は、上限を 100 GiB として設計し、それを超えるファイルは結果イベントの OBJECT_SIZE_LIMIT_EXCEEDED を受けて別の経路に回すのが良いのではないかと思います。1 バイト単位の境界を確かめるのに合計 $34 ほどかかりましたが、自分の利用で不安だった上限の単位と境界を、実測で確かめることができました。上限ぎりぎりのファイルの見積もりや、タイムアウト値を決めるときの参考になれば嬉しいです。
この記事で使用したコードは、以下に置きました。
9 参考リンク
- Amazon GuardDuty pricing
- Quotas in Malware Protection for S3
- Monitoring S3 object scans in Malware Protection for S3
- Monitoring S3 object scans with Amazon EventBridge
- Monitoring Malware Protection for S3 with CloudWatch metrics
- IAM role permissions for Malware Protection for S3
- GuardDuty Malware Protection for S3 の上限拡張(2025 年 9 月)
- EICAR Anti-Malware Testfile









