A CDK construct for scanning S3 buekcts for malware / viruses using the attachmentAV API. This construct automatically scans files uploaded to an S3 bucket using the attachmentAV API and tags files with scan results or even deletes infected files.
- 🛡️ Automatic Malware Scanning: Scans files up to 5GB using asynchronous attachmentAV API
- ⚡ Flexible Triggers: Choose between S3 event notifications (default) or EventBridge
- 🔐 Secure API Key Storage: Uses AWS Systems Manager Parameter Store for API key management
- 📊 CloudWatch Logging: Structured scan results logged for monitoring and auditing
- ✉️ Object Tagging: Tags all scanned files with scan results for additional processing
- 🚨 Automatic Malware Deletion: Support for automatic deletion of infected files
- 🎯 Configurable Filters: Support for S3 key prefix and suffix filtering
- 🚀 Production Ready: Includes IAM roles, permissions, and error handling
┌──────────┐ ┌──────────────┐ ┌───────────────┐
│ S3 │─────▶│ Scanner │─────▶│ attachmentAV │
│ Bucket │ │ Lambda │ │ API │
└──────────┘ └──────────────┘ └───────────────┘
▲ │ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ SSM │ │
│ │ Parameter │ │
│ │ Store │ │
│ └──────────────┘ │
───────────────────────────────────── │
│ ▼
┌──────────────┐ ┌───────────────┐
│ CloudWatch │◀─────│ Callback │
│ Logs │ │ Lambda │
└──────────────┘ └───────────────┘
This repository provides an example implementation of a TypeScript construct to scan S3 buckets with attachmentAV API.
You can copy/paste the files from ./lib into a subfolder in your CDK project and use the construct as described below.
Here is a list of dependencies you may need to install:
aws-cdk-lib(typically already installed in a CDK project)constructs(typically already installed in a CDK project)@types/aws-lambdafor TypeScript types of AWS Lambda@aws-sdk/client-ssmfor SSM Parameter Store access@aws-sdk/client-s3for S3 access@aws-sdk/s3-request-presignerto generate presigned URLs for secure S3 access
If you just want to give the construct a try, check out the repository and deploy the example stack from ./integration:
npm install
cd integration
npx cdk deploy- attachmentAV API Key: Sign up at attachmentav.com to obtain an API key
- AWS SSM Parameter: Store your API key as a SecureString parameter
aws ssm put-parameter \
--name "/attachmentav/api-key" \
--value "your-api-key-here" \
--type "SecureString" \
--description "attachmentAV API key for malware scanning"import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { AttachmentAVBucketScan } from "./lib";
const app = new cdk.App();
const stack = new cdk.Stack(app, 'MyStack');
// Create or reference an S3 bucket
const bucket = new s3.Bucket(stack, 'MyBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
});
// Add attachmentAV scanning
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
});The construct creates two Lambda Functions: a Scanner function to trigger the attachmentAV API and a Callback
function to receive a callback from the attachmentAV API. The Scanner function is invoked by S3 event notifications.
The Callback function logs the scan result and tags the S3 object.
Use EventBridge instead of direct S3 event notifications:
import { AttachmentAVBucketScan, TriggerStrategy } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
triggerStrategy: TriggerStrategy.EVENTBRIDGE,
});Automatically delete S3 objects if the scan result detected a threat:
import { AttachmentAVBucketScan } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
deleteInfectedObject: true,
});Disable that S3 objects are tagged with the scan result:
import { AttachmentAVBucketScan } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
tagObjectWithScanResult: false,
});Scan only files in a specific folder:
import { AttachmentAVBucketScan } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
s3KeyPrefix: 'uploads/', // Only scan files in uploads/ folder
});Scan only specific file types:
import { AttachmentAVBucketScan } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
s3KeySuffix: '.pdf', // Only scan PDF files
});Specify a custom attachmentAV API endpoint (e.g., for different regions):
import { AttachmentAVBucketScan } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
apiUrl: 'https://us.developer.attachmentav.com', // US region endpoint
});Provide your attachmentAV API tenant id (get if from GET /v1/whoami) to enable callback verification:
import { AttachmentAVBucketScan } from "./lib";
new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
tenantId: 'abc123',
});The construct exposes Lambda functions for further customization:
import { AttachmentAVBucketScan } from "./lib";
const scanner = new AttachmentAVBucketScan(stack, 'AttachmentAVScanner', {
bucket: bucket,
apiKeyParameterName: '/attachmentav/api-key',
});
// Access the scanner Lambda
scanner.scannerFunction.addEnvironment('CUSTOM_VAR', 'value');
// Access the callback Lambda
scanner.callbackFunction.grantInvoke(someOtherRole);
// Get the callback URL
const callbackUrl = scanner.callbackUrl.url;| Property | Type | Required | Default | Description |
|---|---|---|---|---|
bucket |
s3.IBucket |
Yes | - | The S3 bucket to monitor for new uploads |
apiKeyParameterName |
string |
Yes | - | SSM Parameter Store parameter name containing the attachmentAV API key |
triggerStrategy |
TriggerStrategy |
No | S3_EVENT |
Strategy for triggering the scanner Lambda |
tagObjectWithScanResult |
boolean |
No | true |
If S3 objects should be tagged with their scan result |
deleteInfectedObject |
boolean |
No | false |
If S3 objects should be deleted if a threat is detected |
s3KeyPrefix |
string |
No | - | Optional S3 key prefix filter (e.g., 'uploads/') |
s3KeySuffix |
string |
No | - | Optional S3 key suffix filter (e.g., '.pdf') |
apiUrl |
string |
No | https://eu.developer.attachmentav.com |
The attachmentAV API base URL |
tenantId |
string |
No | abc123 |
The tenant id of your attachmentAV API subscription |
TriggerStrategy.S3_EVENT(default): Direct S3 event notification to Lambda. Most efficient with lowest latency.TriggerStrategy.EVENTBRIDGE: Route S3 events through EventBridge. Allows for more complex event filtering and routing.
Scan results are logged to CloudWatch Logs with structured JSON:
{
"bucket": "bucket-name",
"key": "test-document.pdf",
"versionId": "dkfu328c-39ffk",
"status": "clean",
"finding": "none",
"size": 1024576,
"download_time": 0.1,
"scan_time": 0.1,
"realFileType": "Adobe Portable Document Format (PDF)",
"timestamp": "2026-01-08T10:30:45.123Z"
}clean: No threats detectedinfected: Malware or virus foundno: Unable to determine (scan failed or file type not supported)
Two log groups are created:
/aws/lambda/<stack-name>-ScannerFunction-<hash>- Scanner Lambda logs/aws/lambda/<stack-name>-CallbackFunction-<hash>- Callback Lambda logs
Find all infected files:
fields @timestamp, bucket, key, versionId, status, finding, realFileType
| filter status = "infected"
| sort @timestamp desc
Count scans by status:
fields status
| stats count() by status
- ✅ API keys stored securely in SSM Parameter Store (encrypted at rest)
- ✅ IAM roles follow least-privilege principle
- ✅ Scanner Lambda only has read access to S3
- ✅ Presigned URLs used for secure file access
- ✅ Callback Lambda verifies attachmentAV webhook
- attachmentAV API: See attachmentAV pricing
- AWS Lambda: Pay per invocation and duration
- AWS SSM Parameter Store: Free for standard parameters
- CloudWatch Logs: Pay for storage and ingestion
- S3: Standard S3 pricing applies
- Maximum file size: 5GB (attachmentAV async API limit)
npm run buildnpm run watchnpm run testThis project is licensed under the MIT License.
For issues related to:
- This construct: Open a GitHub issue
- attachmentAV API: Contact [email protected]
- AWS CDK: See AWS CDK GitHub