[Amazon GuardDuty] 100 GB のファイルを S3 マルウェアスキャンに通して、上限の境界・所要時間・料金を実測してみました

[Amazon GuardDuty] 100 GB のファイルを S3 マルウェアスキャンに通して、上限の境界・所要時間・料金を実測してみました

Amazon GuardDuty Malware Protection for S3 の 100 GB ファイルスキャン上限について、実測で検証しました。上限は 10 進か 2 進か、超過時の挙動、処理時間や費用がいくらかかるのかを、1 バイト単位で確かめてみました。
2026.11.03

1 はじめに

製造ビジネステクノロジー部の平内(SIN)です。

Amazon GuardDuty Malware Protection for S3 は、2025 年 9 月にスキャンできる 1 ファイルの上限が 5 GB から 100 GB に引き上げられました。

https://aws.amazon.com/about-aws/whats-new/2025/09/malware-protection-s3-file-size-archive-scanning-limits

手元で作成中の 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 が返るので、その回のスキャンが動いていることの確認にもなります。

https://www.eicar.org/download-anti-malware-testfile/

(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

https://docs.aws.amazon.com/guardduty/latest/ug/monitor-cloudwatch-metrics-s3-malware-protection.html

コンソールで見る場合も同じで、「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 ほどかかりましたが、自分の利用で不安だった上限の単位と境界を、実測で確かめることができました。上限ぎりぎりのファイルの見積もりや、タイムアウト値を決めるときの参考になれば嬉しいです。

この記事で使用したコードは、以下に置きました。

https://github.com/furuya02/guardduty-s3-malware-scan-100gb

9 参考リンク

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事