Guide · AWS Inspector · ECR Container Scanning
AWS Inspector ECR Container Scanning — Enhanced Scanning, SBOM Export, and Filter Rules
ECR provides two scanning modes: basic scanning (built-in, OS packages only, on-push only, no continuous monitoring) and enhanced scanning (powered by Inspector v2, OS + language packages, on-push plus automatic re-scanning on new CVE publication). For MCP servers packaged as container images, enhanced scanning is the correct choice — it covers the Node.js, Python, or Go packages in your image alongside OS packages, and it continues scanning images already in the registry when new CVEs are published against packages you shipped weeks ago. The critical operational difference is that enhanced scanning can generate new findings on an image you pushed months ago because a zero-day was disclosed today — basic scanning never does this.
TL;DR
Enable Inspector v2 with aws inspector2 enable --resource-types ECR, then switch each ECR repository from basic to enhanced scanning via the ECR console or API. Enhanced scanning scans on push and re-scans automatically when new CVEs match installed packages. For Inspector overview see the Inspector overview. For EC2 scanning see the EC2 guide. For Lambda scanning see the Lambda guide. For finding management see the findings guide.
Basic vs. enhanced scanning
| Feature | Basic scanning (ECR built-in) | Enhanced scanning (Inspector v2) |
|---|---|---|
| Packages scanned | OS packages only (yum/apt) | OS packages + language packages (pip, npm, gem, Maven, Go modules, NuGet, Cargo) |
| Scan triggers | On push only (or manual trigger) | On push + automatic re-scan on new CVE publication |
| Findings in Security Hub | No | Yes — ASFF format, automatically forwarded if Security Hub is enabled |
| SBOM export | No | Yes — CycloneDX or SPDX via Inspector ExportSbom API |
| Suppression rules | No | Yes — Inspector filter rules by CVE ID, package name, image tag |
| Multi-account visibility | Per-account only | Centralized in Inspector delegated admin account |
| Cost | Free (included in ECR) | $0.09 per image scanned (see pricing) |
Switching a repository to enhanced scanning
Inspector v2 must be enabled in the account (aws inspector2 enable --resource-types ECR) before repositories can be switched to enhanced scanning. After enabling Inspector, switch repositories individually or configure account-wide scanning:
# Option A: switch a specific repository to enhanced scanning
aws ecr put-image-scanning-configuration \
--repository-name my-mcp-server \
--image-scanning-configuration scanOnPush=true
# Option B: set enhanced scanning as the default for all new repositories
# (via Inspector scanning settings, not ECR API)
aws inspector2 update-ec2-deep-inspection-configuration \
--activate-deep-inspection
# Verify a repository's scanning type
aws ecr describe-repositories \
--repository-names my-mcp-server \
--query 'repositories[0].{Name:repositoryName,ScanOnPush:imageScanningConfiguration.scanOnPush}'
Switching from basic to enhanced does not retroactively scan images already in the repository — only images pushed after the switch are scanned. To scan an existing image, re-push it or use aws ecr start-image-scan (note: this triggers a one-time basic scan, not enhanced). Enhanced scanning is triggered by Inspector on the image layer digests as they exist in the registry — the image does not need to be pulled or run.
# View scan findings for a specific image
aws inspector2 list-findings \
--filter-criteria '{
"ecrImageRepositoryName": [{"comparison":"EQUALS","value":"my-mcp-server"}],
"ecrImageTags": [{"comparison":"EQUALS","value":"v1.2.3"}],
"findingType": [{"comparison":"EQUALS","value":"PACKAGE_VULNERABILITY"}]
}' \
--sort-criteria '{"field":"INSPECTOR_SCORE","sortOrder":"DESC"}' \
--query 'findings[].{Score:inspectorScore,Severity:severity,CVE:packageVulnerabilityDetails.vulnerabilityId,Package:packageVulnerabilityDetails.vulnerablePackages[0].name,FixedIn:packageVulnerabilityDetails.vulnerablePackages[0].fixedInVersion}' \
--output table
Language package scanning coverage
Enhanced scanning extracts and analyzes package manifests from all image layers. The package managers covered and how Inspector detects them:
| Language / ecosystem | Files Inspector reads | Coverage notes |
|---|---|---|
| Python | site-packages metadata (METADATA, PKG-INFO), requirements.txt | pip-installed packages; conda environments not covered |
| Node.js | node_modules/*/package.json | All npm/yarn/pnpm installed packages; devDependencies in final image are included if not pruned |
| Ruby | Gemfile.lock, gemspec | bundler-installed gems |
| Java | pom.xml, build.gradle (fat JAR manifests) | Maven/Gradle; fat JARs (Spring Boot, Quarkus) analyzed by layer extraction |
| Go | go.sum, binary GOPATH metadata | Go modules embedded in compiled binary; Inspector reads binary metadata, not just go.sum |
| .NET | *.deps.json, *.nuspec | NuGet packages in .NET Core apps |
| Rust | Cargo.lock | Cargo packages if lock file is in image |
A common source of false negatives: if your Dockerfile uses a multi-stage build and the final stage is a distroless or scratch image, Inspector may not find language package metadata because the package manager artifacts were in the build stage and not copied to the final image. Inspector reads what is in the final image layers — if node_modules is not in the final image, it is not scanned. This is actually the correct security posture (smaller attack surface) but means Inspector will not find Node.js package CVEs for distroless Node images that bundle only the compiled output.
Continuous re-scanning on new CVEs
This is the feature that justifies enhanced over basic scanning for production MCP servers. When a new CVE is published that affects a package version in an image already in your ECR registry, Inspector automatically creates a new finding for that image without any push event. The image does not need to be re-pushed — Inspector compares its package inventory (collected at push time) against the CVE database continuously.
# View findings created AFTER the initial push scan for an image
# (findings where createdAt > image push date indicate CVE-triggered re-scans)
PUSH_DATE="2026-09-01T00:00:00Z"
aws inspector2 list-findings \
--filter-criteria '{
"ecrImageRepositoryName": [{"comparison":"EQUALS","value":"my-mcp-server"}],
"firstObservedAt": [{"startInclusive":"'"$PUSH_DATE"'","endInclusive":"2099-01-01T00:00:00Z"}]
}' \
--query 'findings[].{CVE:packageVulnerabilityDetails.vulnerabilityId,Created:createdAt,Score:inspectorScore}' \
--output table
The practical implication: images tagged :latest or versioned tags that are not being actively deployed but are still in your ECR registry will accumulate new findings over time. A common mistake is to declare victory after the initial push scan returns zero HIGH/CRITICAL findings, then return 3 months later to find 15 new findings from CVEs published in the interim. Build a CloudWatch alarm or EventBridge rule to alert on new HIGH/CRITICAL findings for any image tagged :production.
# EventBridge rule: alert on new CRITICAL findings for ECR resources
aws events put-rule \
--name "inspector-ecr-critical-findings" \
--event-pattern '{
"source": ["aws.inspector2"],
"detail-type": ["Inspector2 Finding"],
"detail": {
"severity": ["CRITICAL"],
"resources": [{"type": ["AWS_ECR_CONTAINER_IMAGE"]}],
"status": ["ACTIVE"]
}
}' \
--state ENABLED
SBOM export
Inspector can export a Software Bill of Materials (SBOM) for any scanned container image in CycloneDX 1.4 or SPDX 2.3 format. SBOMs are required for FedRAMP, DoD CMMC, and increasingly for enterprise procurement — and they provide a human-readable inventory of every package in an image for audit and compliance purposes.
# Export SBOM for a specific image to S3
aws inspector2 create-sbom-export \
--report-format CYCLONEDX_1_4 \
--s3-destination '{
"bucketName": "my-sbom-bucket",
"keyPrefix": "sboms/my-mcp-server/v1.2.3/",
"kmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc12345"
}' \
--filter-criteria '{
"ecrImageRepositoryName": [{"comparison":"EQUALS","value":"my-mcp-server"}],
"ecrImageTags": [{"comparison":"EQUALS","value":"v1.2.3"}]
}'
# Check export status
aws inspector2 get-sbom-export \
--report-id "your-report-id" \
--query '{Status:status,S3:s3Destination}'
The SBOM export is asynchronous. For large registries with many images, exports typically complete within 5–15 minutes. The exported CycloneDX JSON lists every component (package name, version, PURL, CPE) in the image along with any associated vulnerabilities known at export time. Include SBOM generation in your CI pipeline for every image that will be deployed to production.
Filter rules for suppression
Inspector filter rules (suppression rules) let you hide findings that are accepted risks, already tracked in another system, or known false positives. Rules apply to existing and future findings immediately — they do not delete findings, they change their status to SUPPRESSED.
# Suppress a specific CVE across all images tagged :development
aws inspector2 create-filter \
--name "suppress-CVE-in-dev-images" \
--description "CVE-2024-XXXXX in libc not exploitable in containerized MCP data path — JIRA-5678" \
--filter-criteria '{
"vulnerabilityId": [{"comparison":"EQUALS","value":"CVE-2024-XXXXX"}],
"ecrImageTags": [{"comparison":"EQUALS","value":"development"}]
}' \
--action SUPPRESS \
--reason "Dev images not exposed to internet; compensating network control via SG"
# Suppress all MEDIUM and below findings for a specific repository
# (use cautiously — only if you have a documented acceptance policy)
aws inspector2 create-filter \
--name "suppress-medium-below-staging" \
--description "MEDIUM and lower accepted in staging — triaged monthly" \
--filter-criteria '{
"ecrImageRepositoryName": [{"comparison":"EQUALS","value":"my-mcp-server-staging"}],
"severity": [{"comparison":"EQUALS","value":"MEDIUM"},{"comparison":"EQUALS","value":"LOW"},{"comparison":"EQUALS","value":"INFORMATIONAL"}]
}' \
--action SUPPRESS \
--reason "Staging environment; remediated in production track per vulnerability management policy"
List and audit existing suppression rules regularly — stale rules that suppress CVEs that now have working exploits are a common source of missed vulnerabilities:
aws inspector2 list-filters \
--action SUPPRESS \
--query 'filters[].{Name:name,Created:createdAt,Criteria:filterCriteria}' \
--output json | jq '.[] | {name, created: .Created, cve: .Criteria.vulnerabilityId[0].value}'
Failure modes reference
| Failure | Symptom | Fix |
|---|---|---|
| Images not scanned after push | No Inspector findings appear for new push; ECR shows basic scanning | Repository is still on basic scanning; run put-image-scanning-configuration or configure enhanced scanning in Inspector settings |
| Language packages missing from findings | Only OS packages in findings for a Node.js/Python image | Distroless or scratch final stage — language package metadata not in final image layers; add package lock files to final stage or switch to a base image that retains package metadata |
| Findings appear on old images unexpectedly | Images pushed months ago suddenly have new HIGH findings | Expected behavior — new CVE published that affects a package version in the image; review and remediate or suppress with documented justification |
| SBOM export stuck in PENDING | create-sbom-export returns report-id but status stays PENDING | S3 bucket must allow Inspector write access; check bucket policy allows inspector2.amazonaws.com as principal with s3:PutObject; also verify KMS key policy allows Inspector to use the key if encrypted |
| ECR repository shows enhanced but zero findings | Repository switched to enhanced, images pushed, but no findings in Inspector | Only images pushed after switching to enhanced scanning are scanned; re-push or retag older images to trigger initial scan |