AWS Inspector Vulnerability Scanning at Scale
Deploying AWS Inspector across multi-account environments for continuous vulnerability scanning of EC2 instances, container images, and Lambda functions

Introduction
AWS Inspector v2 provides agentless and agent-based vulnerability scanning for EC2 instances, ECR container images, and Lambda functions. Unlike its predecessor, Inspector v2 operates continuously rather than on scheduled assessments, reporting findings in near-real-time as new CVEs are published or as workloads change.
In organizations running 500+ EC2 instances and 200+ container images, Inspector v2 identifies an average of 3,000-5,000 vulnerabilities per week. The challenge is not finding vulnerabilities but building effective prioritization and remediation workflows. This article covers the deployment strategy, finding management, and integration patterns for Inspector at scale.
Inspector v2 Coverage
| Target | Scanning Method | Detection Scope | Continuous |
|---|---|---|---|
| EC2 (SSM Agent) | Agent-based | OS packages, app dependencies | Yes |
| EC2 (Agentless) | EBS snapshot | OS packages | Yes |
| ECR Images | Registry scan | OS + app dependencies | On push + continuous |
| Lambda Functions | Code analysis | App dependencies, code issues | Yes |
| Lambda Layers | Code analysis | Layer dependencies | Yes |
Multi-Account Deployment
Enable Inspector via AWS Organizations
# Designate delegated administrator (from management account)
aws inspector2 enable-delegated-admin-account \
--delegated-admin-account-id 111111111111
# Enable Inspector across all member accounts
aws inspector2 batch-enable \
--account-ids '["222222222222","333333333333","444444444444"]' \
--resource-types '["EC2","ECR","LAMBDA","LAMBDA_CODE"]'
Terraform Multi-Account Setup
# In the delegated admin account
resource "aws_inspector2_organization_configuration" "main" {
auto_enable {
ec2 = true
ecr = true
lambda = true
lambda_code = true
}
}
# Enable Inspector in the delegated admin account
resource "aws_inspector2_enabler" "admin" {
account_ids = [data.aws_caller_identity.current.account_id]
resource_types = ["EC2", "ECR", "LAMBDA", "LAMBDA_CODE"]
}
Finding Severity Distribution
Based on scanning across a 500-instance environment over 3 months:
| Severity | Average Findings | % of Total | Median Remediation Time |
|---|---|---|---|
| Critical | 45 | 1.2% | 3 days |
| High | 280 | 7.5% | 7 days |
| Medium | 1,450 | 38.8% | 30 days |
| Low | 1,960 | 52.5% | 90 days |
| Total | 3,735 | 100% | - |
Finding Type Breakdown
| Package Type | Findings | Source | Example |
|---|---|---|---|
| OS packages (apt/yum) | 62% | NVD, vendor advisories | CVE-2024-xxxx in openssl |
| Python (pip) | 18% | OSV, PyPI advisories | CVE in requests library |
| Node.js (npm) | 12% | npm audit, Snyk DB | Prototype pollution |
| Java (Maven) | 5% | Maven advisories | Log4j variants |
| Other | 3% | Various | Go, Ruby, .NET |
Suppression Rules for Noise Reduction
Not all findings require immediate action. Suppression rules reduce alert fatigue:
# Suppress findings for accepted risks
aws inspector2 create-filter \
--name "AcceptedRisk-DevEnvironment" \
--action SUPPRESS \
--description "Suppress low/medium findings in dev accounts" \
--filter-criteria '{
"awsAccountId": [{
"comparison": "EQUALS",
"value": "555555555555"
}],
"severity": [{
"comparison": "EQUALS",
"value": "LOW"
}, {
"comparison": "EQUALS",
"value": "MEDIUM"
}]
}'
# Suppress findings for end-of-life AMIs being replaced
aws inspector2 create-filter \
--name "EOL-AMI-Replacement" \
--action SUPPRESS \
--description "AMIs scheduled for replacement by Q2" \
--filter-criteria '{
"resourceTags": [{
"comparison": "EQUALS",
"key": "ScheduledReplacement",
"value": "2026-Q2"
}],
"severity": [{
"comparison": "NOT_EQUALS",
"value": "CRITICAL"
}]
}'
Automated Remediation Pipeline
EventBridge Integration
{
"source": ["aws.inspector2"],
"detail-type": ["Inspector2 Finding"],
"detail": {
"severity": ["CRITICAL", "HIGH"],
"status": ["ACTIVE"],
"type": [{
"prefix": "Package"
}]
}
}
Lambda Remediation Handler
import boto3
import json
def handler(event, context):
"""Process Inspector findings and trigger remediation."""
finding = event['detail']
severity = finding['severity']
resource_type = finding['resources'][0]['type']
if resource_type == 'AWS_EC2_INSTANCE':
handle_ec2_finding(finding)
elif resource_type == 'AWS_ECR_CONTAINER_IMAGE':
handle_ecr_finding(finding)
elif resource_type == 'AWS_LAMBDA_FUNCTION':
handle_lambda_finding(finding)
def handle_ec2_finding(finding):
"""Trigger SSM patching for EC2 vulnerabilities."""
ssm = boto3.client('ssm')
instance_id = finding['resources'][0]['details']['awsEc2Instance']['instanceId']
# Create patch baseline exception or trigger immediate patching
ssm.send_command(
InstanceIds=[instance_id],
DocumentName='AWS-RunPatchBaseline',
Parameters={
'Operation': ['Install'],
'RebootOption': ['RebootIfNeeded']
},
Comment=f"Auto-remediation for {finding['title']}"
)
def handle_ecr_finding(finding):
"""Trigger image rebuild for container vulnerabilities."""
codebuild = boto3.client('codebuild')
image_uri = finding['resources'][0]['details']['awsEcrContainerImage']['imageHash']
# Trigger rebuild pipeline
codebuild.start_build(
projectName='container-rebuild-pipeline',
environmentVariablesOverride=[{
'name': 'VULNERABLE_IMAGE',
'value': image_uri,
'type': 'PLAINTEXT'
}]
)
def handle_lambda_finding(finding):
"""Create Jira ticket for Lambda dependency updates."""
# Lambda functions typically require code changes
# Route to development team via ticketing
create_jira_ticket(
summary=f"[Inspector] {finding['title']}",
description=finding['description'],
priority='High' if finding['severity'] == 'CRITICAL' else 'Medium'
)
Reporting and Metrics
Security Hub Integration
Inspector findings automatically flow to Security Hub for centralized visibility:
# Generate SBOM report for compliance
aws inspector2 create-sbom-export \
--report-format SPDX_2_3 \
--s3-destination '{
"bucketName": "compliance-reports",
"keyPrefix": "sbom/",
"kmsKeyArn": "arn:aws:kms:us-east-1:111111111111:key/abc-123"
}' \
--resource-filter-criteria '{
"accountId": [{"comparison": "EQUALS", "value": "111111111111"}]
}'
Coverage Metrics
# Check scanning coverage
aws inspector2 list-coverage \
--filter-criteria '{
"scanStatusCode": [{"comparison": "EQUALS", "value": "ACTIVE"}]
}' \
--query 'coveredResources[].{Type:resourceType,Status:scanStatus.statusCode}' \
--output table
| Metric | Target | Measurement |
|---|---|---|
| Coverage (EC2) | > 95% | Instances with active scanning |
| Coverage (ECR) | 100% | Repositories with scan-on-push |
| Critical MTTR | < 72 hours | Time from finding to remediation |
| High MTTR | < 7 days | Time from finding to remediation |
| Suppression rate | < 20% | Findings suppressed vs total |
| False positive rate | < 5% | Findings confirmed not applicable |
Cost Considerations
| Resource Type | Pricing | Example (500 instances) |
|---|---|---|
| EC2 scanning | $1.258/instance/month | $629/month |
| ECR initial scan | $0.09/image | $18/month (200 images) |
| ECR rescan | $0.01/image/rescan | $60/month |
| Lambda scanning | $0.30/function/month | $30/month (100 functions) |
| Total | ~$737/month |
Key Takeaways
- Deploy Inspector v2 organization-wide using delegated administrator to ensure consistent coverage across all accounts without per-account configuration.
- Focus remediation on critical and high severity findings which represent only 8.7% of total findings but carry the most risk.
- Use suppression rules aggressively for accepted risks, end-of-life systems, and development environments to reduce alert fatigue by 40-60%.
- Automate remediation workflows with EventBridge and Lambda to patch EC2 instances, rebuild container images, and create tickets for code-level fixes.
- Monitor coverage gaps as Inspector cannot scan instances without SSM Agent or ECR repositories without scan-on-push enabled.
- Generate SBOM exports for compliance requirements, providing software composition visibility across your entire environment.
- Expected cost is $1-2 per resource per month, making Inspector one of the most cost-effective security tools relative to the vulnerability visibility it provides.
Recommended reading

Per-Team Cost Allocation in Shared Kubernetes Clusters: From Chaos to Clarity
Implementing accurate per-namespace cost allocation in multi-tenant Kubernetes clusters, covering request vs. usage attribution, shared resource amortization, and building showback dashboards that drive accountability.

Measuring and Eliminating Toil: From 40% to 12% of Engineering Time
A systematic approach to identifying, measuring, and automating toil—the repetitive operational work that scales linearly with service growth and prevents engineers from doing creative work.

Serverless Postgres in Production: Branching, Scale-to-Zero, and the End of Database Provisioning
Running Neon serverless Postgres in production for 8 months — covering database branching workflows, scale-to-zero economics, connection pooling, and migration from RDS.

Comments
No comments yet. Be the first to share your thoughts.