AWS Inspector v2 provides automated vulnerability scanning for EC2, ECR container images, and Lambda functions. Organizations enable it, see the dashboard turn green, and assume they're covered. But Inspector v2 has well-defined coverage gaps: it doesn't scan running containers (only images at push time), Lambda layers have partial coverage, suppression rules can silence critical findings indefinitely, and the EPSS/CVSS scoring doesn't always reflect actual exploitability in your specific configuration. This guide covers testing Inspector v2's completeness rather than trusting it.
| Resource Type | Inspector v2 Coverage | What It Misses |
|---|---|---|
| EC2 instances | OS packages, network reachability | Running containers on EC2, custom-compiled binaries, non-SSM instances |
| ECR images | OS packages, language dependencies at push time | Vulns introduced during runtime, running containers with drift, non-ECR registries |
| Lambda functions | Lambda layer packages, function code dependencies | Container-based Lambda images (partial), custom runtimes, layer versions not attached |
| ECS/EKS workloads | Via ECR image scans | Running pod drift from image, init containers, sidecars from non-ECR sources |
| RDS / databases | Not covered | Database engine CVEs entirely missing |
| Code vulnerabilities | Basic code scanning (limited languages) | Business logic, injection, OWASP Top 10 application flaws |
# Check Inspector v2 enabled regions and resource types
aws inspector2 get-configuration --region us-east-1 | \
python3 -c "
import json,sys
config = json.load(sys.stdin)
print('EC2 scanning:', config.get('ec2Configuration',{}).get('scanMode','NOT_ENABLED'))
print('ECR scanning:', config.get('ecrConfiguration',{}).get('rescanDuration','NOT_ENABLED'))
print('Lambda scanning:', config.get('lambdaConfiguration',{}).get('scanMode','NOT_ENABLED'))
"
# Check accounts enrolled in delegated admin (multi-account)
aws inspector2 list-delegated-admin-accounts
# Find EC2 instances NOT covered by Inspector (no SSM agent or not in enabled account)
aws inspector2 list-coverage \
--filter-criteria '{"ec2InstanceTags":[{"comparison":"NOT_EQUALS","key":"inspector-enabled","value":"true"}]}' \
--query 'coveredResources[*].resourceId'
# More reliable: find EC2 instances running without SSM agent
aws ec2 describe-instances \
--query 'Reservations[*].Instances[*].[InstanceId,State.Name]' \
--output text | while read id state; do
ssm_status=$(aws ssm describe-instance-information \
--filters "Key=InstanceIds,Values=$id" \
--query 'InstanceInformationList[0].PingStatus' --output text 2>/dev/null)
[ "$ssm_status" = "None" ] && echo "NO SSM: $id"
done
Inspector findings contain CVE details, affected packages, and remediation recommendations. A principal with inspector2:ListFindings can enumerate all vulnerabilities across an account — high-value reconnaissance for an attacker with partial AWS access.
# Check who can access Inspector findings
aws iam get-account-authorization-details | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
dangerous_actions = ['inspector2:ListFindings','inspector2:GetFindings','inspector2:BatchGetFindingDetails']
for policy in data.get('Policies', []):
for pv in policy.get('PolicyVersionList', []):
if not pv.get('IsDefaultVersion'): continue
doc = pv.get('Document', {})
for stmt in doc.get('Statement', []):
actions = stmt.get('Action', [])
if isinstance(actions, str): actions = [actions]
if any(a in actions or '*' in a for a in dangerous_actions):
print(f\"Policy: {policy['PolicyName']} — grants inspector2 read\")
"
# Test: if you have inspector2:ListFindings and inspector2:BatchGetFindingDetails
# you can enumerate all CVEs in all scanned workloads:
aws inspector2 list-findings \
--filter-criteria '{"findingStatus":[{"comparison":"EQUALS","value":"ACTIVE"}]}' \
--query 'findings[*].{id:findingArn,cve:packageVulnerabilityDetails.vulnerabilityId,severity:severity.label,resource:resources[0].id}' \
--output table | head -30
# Check if findings are accessible cross-account via delegated admin
aws inspector2 list-findings \
--query 'findings[*].awsAccountId' --output text | sort | uniq -c
# List all suppression rules — critical findings may be silenced indefinitely
aws inspector2 list-filters \
--action SUPPRESS \
--query 'filters[*].{name:name,reason:reason,criteria:filterCriteria}' | \
python3 -c "
import json,sys
filters = json.load(sys.stdin)
for f in filters:
print(f\"Suppression: {f['name']}\")
print(f\" Reason: {f.get('reason','NO REASON PROVIDED')}\")
criteria = f.get('criteria', {})
for key, val in criteria.items():
print(f\" Criteria: {key} = {val}\")
print()
"
# Check for overly broad suppression rules:
# - Rules that suppress by severity (silencing all CRITICAL findings)
# - Rules with no expiration date
# - Rules with vague reasons like 'accepted risk' or 'false positive'
# Identify suppressed CRITICAL/HIGH findings that are actually exploitable
aws inspector2 list-findings \
--filter-criteria '{
"findingStatus":[{"comparison":"EQUALS","value":"SUPPRESSED"}],
"severity":[{"comparison":"EQUALS","value":"CRITICAL"}]
}' \
--query 'findings[*].{cve:packageVulnerabilityDetails.vulnerabilityId,score:inspectorScore,resource:resources[0].id}' | \
python3 -c "
import json,sys
findings = json.load(sys.stdin)
print(f'Suppressed CRITICAL findings: {len(findings)}')
for f in findings[:10]:
print(f\" CVE: {f.get('cve','?')} Score: {f.get('score','?')} Resource: {f.get('resource','?')}\")
"
# Inspector v2 scans ECR images at push time with optional continuous monitoring
# Key gap: an image scanned clean on Monday may have new CVEs disclosed on Tuesday
# Inspector's continuous rescan catches this IF the image is still referenced
# Check ECR rescan settings (should be CONTINUOUS not LIFETIME or DAYS_90)
aws inspector2 get-configuration | \
python3 -c "
import json,sys
cfg = json.load(sys.stdin)
ecr = cfg.get('ecrConfiguration', {})
print('ECR rescan duration:', ecr.get('rescanDuration','NOT_CONFIGURED'))
# LIFETIME = rescan as long as image exists = BEST
# DAYS_180 or DAYS_90 = stops rescanning after N days
# Manual only = only scans at push = WORST
"
# Find ECR images with no recent scan results (potential blind spot)
aws ecr describe-repositories --query 'repositories[*].repositoryName' --output text | \
tr '\t' '\n' | while read repo; do
latest=$(aws ecr describe-images --repository-name "$repo" \
--query 'sort_by(imageDetails, &imagePushedAt)[-1].[imageDigest,imagePushedAt]' \
--output text 2>/dev/null)
if [ -z "$latest" ]; then continue; fi
digest=$(echo "$latest" | cut -f1)
scan=$(aws ecr describe-image-scan-findings \
--repository-name "$repo" \
--image-id imageDigest=$digest \
--query 'imageScanStatus.status' --output text 2>/dev/null)
echo "$repo: latest image scan = $scan"
done
# Check for images in non-ECR registries used in EKS/ECS (Inspector won't scan these)
kubectl get pods --all-namespaces -o json | \
python3 -c "
import json,sys
pods = json.load(sys.stdin)['items']
external = set()
for p in pods:
for c in p['spec'].get('containers', []) + p['spec'].get('initContainers', []):
img = c.get('image', '')
if not any(x in img for x in ['.dkr.ecr.', 'amazonaws.com']):
external.add(img.split(':')[0])
for img in sorted(external):
print(f'Non-ECR image: {img}')
"
# Inspector v2 scans Lambda function code and layers for known CVEs
# Gap: only scans functions in enabled regions, only active $LATEST or published versions
# Check Lambda scanning is enabled
aws inspector2 get-configuration | python3 -c "
import json,sys
cfg = json.load(sys.stdin)
lmb = cfg.get('lambdaConfiguration', {})
print('Lambda scan mode:', lmb.get('scanMode', 'NOT_CONFIGURED'))
# PACKAGE_ONLY = dependency scanning only (most common)
# CODE_AND_PACKAGE = includes basic code analysis
"
# Find Lambda functions with Inspector findings
aws inspector2 list-findings \
--filter-criteria '{"resourceType":[{"comparison":"EQUALS","value":"AWS_LAMBDA_FUNCTION"}]}' \
--query 'findings[*].{fn:resources[0].id,cve:packageVulnerabilityDetails.vulnerabilityId,severity:severity.label}' \
--output table
# Check Lambda layers — Inspector scans layers attached to functions
# But layers NOT attached to any function are not scanned
aws lambda list-layers --query 'Layers[*].LayerName' --output text | \
tr '\t' '\n' | while read layer; do
# Check if any function uses this layer
fns=$(aws lambda list-functions \
--query "Functions[?contains(Layers[*].Arn, \`$layer\`)].FunctionName" \
--output text 2>/dev/null)
[ -z "$fns" ] && echo "Orphaned layer (not scanned): $layer"
done
# Test for runtime EOL — Inspector reports vulnerabilities but not EOL runtimes
# EOL runtimes may have unpatched CVEs that will never be fixed
aws lambda list-functions \
--query 'Functions[*].[FunctionName,Runtime]' --output text | \
grep -E "python2|nodejs12|nodejs14|ruby2.5|java8$|go1.x" | \
while read fn rt; do echo "EOL runtime: $fn ($rt)"; done
# Inspector v2 EC2 scanning requires SSM agent — instances without SSM are blind spots
# Check SSM agent status across all EC2 instances
aws ssm describe-instance-information \
--query 'InstanceInformationList[*].[InstanceId,PingStatus,PlatformType]' \
--output table
# Find EC2 instances where SSM is offline or never connected
aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
--query 'Reservations[*].Instances[*].InstanceId' --output text | \
tr '\t' '\n' | while read id; do
status=$(aws ssm describe-instance-information \
--filters "Key=InstanceIds,Values=$id" \
--query 'InstanceInformationList[0].PingStatus' --output text 2>/dev/null)
[ "$status" != "Online" ] && echo "Inspector blind spot: $id (SSM: $status)"
done
# Check Inspector EC2 network reachability scan
# Inspector checks if vulns are network-reachable (port open + CVE in exposed package)
# Test: does Inspector correctly identify network-exposed CVEs?
aws inspector2 list-findings \
--filter-criteria '{
"resourceType":[{"comparison":"EQUALS","value":"AWS_EC2_INSTANCE"}],
"findingType":[{"comparison":"EQUALS","value":"NETWORK_REACHABILITY"}]
}' \
--query 'findings[*].{instance:resources[0].id,port:networkReachabilityDetails.openPortRange.begin,severity:severity.label}' \
--output table | head -20
# Verify: for a finding claiming port 443 is reachable with CVE-XXXX,
# actually test whether the CVE is exploitable via that port
# Inspector's 'network reachable' is a best-effort assessment, not pentest validation
# Inspector v2 can export SBOM (Software Bill of Materials) per resource
# Cross-validate with external CVE databases to catch findings Inspector missed
# Export SBOM for a specific ECR image
aws inspector2 create-sbom-export \
--report-format CYCLONEDX_1_4 \
--s3-destination bucket="my-security-bucket",keyPrefix="sbom/",kmsKeyArn="arn:aws:kms:..." \
--filter-criteria '{"resourceType":[{"comparison":"EQUALS","value":"AWS_ECR_CONTAINER_IMAGE"}]}'
# After export, download and analyze with grype or trivy for comparison
aws s3 cp s3://my-security-bucket/sbom/sbom.json /tmp/sbom.json
grype sbom:/tmp/sbom.json | grep -E "Critical|High" | head -20
# Compare grype findings vs Inspector findings for the same image
aws inspector2 list-findings \
--filter-criteria '{"resourceId":[{"comparison":"EQUALS","value":"YOUR_IMAGE_URI"}]}' \
--query 'findings[*].packageVulnerabilityDetails.vulnerabilityId' \
--output text | tr '\t' '\n' | sort > /tmp/inspector-cves.txt
# Diff shows CVEs grype finds that Inspector missed (database lag, coverage gaps)
grype "IMAGE_URI" -q --only-fixed 2>/dev/null | awk '{print $5}' | sort > /tmp/grype-cves.txt
comm -23 /tmp/grype-cves.txt /tmp/inspector-cves.txt
| Security Test | Method | Risk |
|---|---|---|
| Uninstructed accounts not enrolled | inspector2:list-members — missing accounts | Critical |
| Suppressed CRITICAL CVEs with no reason | list-filters --action SUPPRESS | Critical |
| EC2 with SSM offline = no scanning | ssm:describe-instance-information | High |
| inspector2:ListFindings too permissive | IAM authorization details review | High |
| ECR rescan on push only | get-configuration ecrConfiguration.rescanDuration | High |
| Non-ECR images in EKS pods | kubectl pod image audit | High |
| Lambda EOL runtimes | list-functions runtime filter | Medium |
Ironimo tests whether your AWS Inspector deployment actually covers your attack surface — checking for SSM gaps, suppression rule misuse, non-ECR images, and cross-validating findings against independent vulnerability databases.
Start free scan