Every file that lands in S3 gets scanned. Clean files move through. Infected files get quarantined. Plug it into Lambda, your app code, or anything that can make an HTTP call.
A file lands in S3 — however you already do uploads. Presigned URLs, direct PUTs, SDK uploads, Transfer Family. Whatever you use today.
AttachmentScanner checks the object against multiple commercial antivirus engines. Seconds for common file sizes. A single JSON verdict back.
Clean → approve (move to final bucket, notify the user). Infected → quarantine or delete. Your rules, with a full audit log.
You already have s3:ObjectCreated events firing. Drop in a small Lambda that calls AttachmentScanner, then moves the clean file to the final bucket (or quarantines the rest).
Call AttachmentScanner from the same handler that receives the upload. Scan before the file ever hits S3 — reject bad files at the edge instead of cleaning up after.
EventBridge, Step Functions, SQS consumers, a scheduled worker sweeping an existing bucket. It’s one POST request — anything that can make HTTP calls can drive it.
Every object that makes it to your production bucket has been scanned by multiple commercial antivirus engines. No silent passes, no “we scan most of them”.
Infected files go to a separate bucket, the origin gets deleted, and every verdict is logged. You know what was flagged, when, and by which engine.
When the report asks for evidence of file-upload antivirus scanning, you have it — engine names, timestamps, request IDs. The things auditors actually ask for.
Scan in the region the bucket already lives in. No residency headaches, no surprise transfer costs. us-east-1, eu-west-1, ap-southeast-2.
No custom runtime, no extra dependencies. Presign, POST, branch on the verdict. Examples in every major language are in the docs.
const presigned = await getSignedUrl(s3, new GetObjectCommand({ Bucket, Key }), { expiresIn: 900 }); const scan = await fetch(`${SCANNER_URL}/v1.0/scans`, { method: "POST", headers: { Authorization: `Bearer ${API_TOKEN}` }, body: JSON.stringify({ url: presigned }), }).then(r => r.json()); if (scan.status === "found") await quarantine(Bucket, Key); else await moveToFinal(Bucket, Key);
Regional clusters in three AWS regions. Dedicated clusters and AWS PrivateLink available on request for compliance workloads.
AttachmentScanner only needs a short-lived presigned GET URL per object. No bucket-wide access, no long-lived AWS credentials held by us.
File bytes are discarded as soon as the scan finishes. We keep metadata (filename, hash, verdict, timestamp) for your scan history — not the file.
Keep scan traffic off the public internet for compliance workloads. Works cleanly with SSE-S3 and SSE-KMS without flattening your encryption story.
async: true plus a callback_url and we’ll POST the verdict back when scanning completes, so your Lambda doesn’t have to stay hot. For very large archives, scan on upload and gate downstream access on the async result rather than blocking the request.s3:ObjectCreated:* to a small Lambda that presigns a GET URL, calls /v1.0/scans, and copies the object into a clean bucket (or a quarantine bucket) based on the verdict. EventBridge works the same way if you prefer routing through an event bus.url field. AttachmentScanner fetches the object over HTTPS with just that URL — we never hold AWS credentials or need broader bucket permissions.DeleteObject) and surface an error to the client. The async flow means the object is already stored by the time the verdict lands — your callback handler copies to a quarantine bucket, deletes the origin, revokes any shared links, and (typically) notifies affected users. Don’t silently delete: keep an audit record of what was flagged and why.14-day free trial. No credit card. Multi-engine scanning in three regions from day one.