Skip to content

widdix/attachmentav-example-cdk-serverless-amazon-s3-virus-malware-scan

Repository files navigation

S3 malware / virus scanner powered by attachmentAV CDK Construct

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.

Features

  • 🛡️ 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

Architecture

┌──────────┐      ┌──────────────┐      ┌───────────────┐
│ S3       │─────▶│ Scanner      │─────▶│ attachmentAV  │
│ Bucket   │      │ Lambda       │      │ API           │
└──────────┘      └──────────────┘      └───────────────┘
       ▲                  │                      │
       │                  │                      │
       │                  ▼                      │
       │          ┌──────────────┐               │
       │          │ SSM          │               │
       │          │ Parameter    │               │
       │          │ Store        │               │
       │          └──────────────┘               │
       ─────────────────────────────────────     │
                                           │     ▼
                  ┌──────────────┐      ┌───────────────┐
                  │ CloudWatch   │◀─────│ Callback      │
                  │ Logs         │      │ Lambda        │
                  └──────────────┘      └───────────────┘

Installation

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-lambda for TypeScript types of AWS Lambda
  • @aws-sdk/client-ssm for SSM Parameter Store access
  • @aws-sdk/client-s3 for S3 access
  • @aws-sdk/s3-request-presigner to 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

Prerequisites

  1. attachmentAV API Key: Sign up at attachmentav.com to obtain an API key
  2. AWS SSM Parameter: Store your API key as a SecureString parameter

Setting up the API Key

aws ssm put-parameter \
  --name "/attachmentav/api-key" \
  --value "your-api-key-here" \
  --type "SecureString" \
  --description "attachmentAV API key for malware scanning"

Usage

Basic Example

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.

Using EventBridge

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,
});

Deleting Infected Objects

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 Object Tagging

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,
});

With Prefix Filter

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
});

With Suffix Filter

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
});

Custom API URL

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
});

Callback Verification

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',
});

Accessing Lambda Functions

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;

Configuration

Props

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

Trigger Strategies

  • 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

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"
}

Status Values

  • clean: No threats detected
  • infected: Malware or virus found
  • no: Unable to determine (scan failed or file type not supported)

Monitoring

CloudWatch Logs

Two log groups are created:

  1. /aws/lambda/<stack-name>-ScannerFunction-<hash> - Scanner Lambda logs
  2. /aws/lambda/<stack-name>-CallbackFunction-<hash> - Callback Lambda logs

Example Log Queries for CloudWatch Logs Insights

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

Security

  • ✅ 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

Costs

  • 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

Limitations

  • Maximum file size: 5GB (attachmentAV async API limit)

Development

Build

npm run build

Watch Mode

npm run watch

Run Tests

npm run test

License

This project is licensed under the MIT License.

Links

Support

For issues related to: