An AWS CDK Construct that blocks deployments to ECS, Lambda, and other services when ECR Image Scanning detects vulnerabilities, and optionally verifies container image signatures.
It scans specified container images during CDK deployment using Basic or Enhanced (Amazon Inspector) scanning, and can verify image signatures with Notation (AWS Signer) or Cosign (Sigstore).
- Block any construct's deployment — block ECS, Lambda, or any CDK construct on vulnerability detection via
blockConstructs - Signature verification — verify image signatures with Notation (AWS Signer) or Cosign (Sigstore) before scanning
- Notify without failing — get alerts via SNS without blocking deployment. Great for gradual adoption
- Scan logs output — results go to S3 or CloudWatch Logs
- SBOM generation — output Software Bill of Materials in CycloneDX or SPDX format to S3 via Amazon Inspector
This construct supports three scanning modes.
With Basic scanning, the construct starts a scan via API during deployment, or checks existing scan-on-push results.
Enhanced scanning (Amazon Inspector) only supports scan-on-push, but additionally enables SBOM generation.
Signature Only mode skips vulnerability scanning entirely and only verifies image signatures.
| Feature | Basic Scanning | Enhanced Scanning | Signature Only |
|---|---|---|---|
| Start scan via API | ✅ (startScan: true) |
— | — |
| Check scan-on-push results | ✅ (startScan: false) |
✅ | — |
| SBOM generation | — | ✅ | — |
| Signature verification | ✅ (optional) | ✅ (optional) | ✅ (required) |
When using ScanConfig.basic({ startScan: true }) (the default), the construct starts a scan via the ECR StartImageScan API during deployment — no additional ECR configuration is required.
For the following modes, scan-on-push must be enabled on your ECR repository or account before deployment:
ScanConfig.basic({ startScan: false })— requires Basic scan-on-push to be enabled on the repositoryScanConfig.enhanced()— requires Enhanced scanning (Amazon Inspector) to be enabled on the account, with the repository included in Inspector's coverage
ScanConfig.signatureOnly() does not require scan-on-push, as it only verifies image signatures without scanning.
If scan-on-push is not configured and no prior scan results exist, the deployment will fail with an error. The construct checks the registry / repository scanning configuration when no scan results are found, so misconfigurations that mean the scan will never run — the repository not matching any Enhanced scanning filter, Enhanced scanning not being enabled on the account, or Basic scan-on-push being disabled — fail immediately with a descriptive error instead of waiting for the pollingTimeout (14 minutes by default). See docs/scan-coverage-check.md for the full decision table.
Tip:
startScan: trueworks even when scan-on-push is already enabled. If a scan has already been triggered, the construct simply uses the existing scan results.
npm install ecr-scan-verifierThe following code is a minimal example that scans the image and blocks the ECS deployment if vulnerabilities are detected.
import { EcrScanVerifier, ScanConfig } from 'ecr-scan-verifier';
// Target image to scan
const image = new DockerImageAsset(this, 'DockerImage', {
directory: resolve(__dirname, './'),
});
// Example of an ECS construct that uses the image
const ecs = new YourECSConstruct(this, 'YourECSConstruct', {
dockerImage: image,
});
// Scan the image before deploying to ECS
new EcrScanVerifier(this, 'ImageScanner', {
repository: image.repository,
imageTag: image.assetHash,
scanConfig: ScanConfig.basic(),
// If vulnerabilities are detected, the ECS deployment will be blocked
blockConstructs: [ecs],
});You can specify which image to scan by tag or digest:
// Scan by tag (default: 'latest')
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
imageTag: 'v1.0',
});
// Scan by digest (if the value starts with 'sha256:', it is treated as a digest)
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
imageTag: 'sha256:abc123...',
});Use ScanConfig to choose between Basic and Enhanced scanning:
import { ScanConfig } from 'ecr-scan-verifier';
// Basic scanning (default) — starts a scan via StartImageScan API
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic({ startScan: true }),
});
// Basic scanning — polls for existing scan results (useful when scan-on-push is configured)
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic({ startScan: false }),
});
// Enhanced scanning — uses Amazon Inspector (scan-on-push only)
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.enhanced(),
});See Prerequisites for the scan-on-push requirements of each mode.
Important: If Enhanced scanning (Amazon Inspector) is enabled on your account, you must use
ScanConfig.enhanced(). UsingScanConfig.basic()with an Enhanced scanning account will result in a deployment error.
You can specify which severity levels trigger a failure:
import { Severity } from 'ecr-scan-verifier';
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
severity: [Severity.CRITICAL, Severity.HIGH],
});Available severity levels: CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL, UNDEFINED.
You can ignore specific CVEs:
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
ignoreFindings: ['CVE-2023-37920', 'CVE-2024-12345'],
});You can choose where to output the scan logs using ScanLogsOutput: S3 or CloudWatch Logs. If not specified, scan logs are written to the Scanner Lambda function's default log group.
import { ScanLogsOutput } from 'ecr-scan-verifier';
const scanLogsBucket = new Bucket(this, 'ScanLogsBucket');
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
scanLogsOutput: ScanLogsOutput.s3({
bucket: scanLogsBucket,
prefix: 'scan-logs/', // Optional
}),
});import { ScanLogsOutput } from 'ecr-scan-verifier';
const scanLogsLogGroup = new LogGroup(this, 'ScanLogsLogGroup');
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
scanLogsOutput: ScanLogsOutput.cloudWatchLogs({ logGroup: scanLogsLogGroup }),
});You can customize the Scanner Lambda function's log group with defaultLogGroup.
If you use EcrScanVerifier construct multiple times in the same stack, you have to set the same log group for defaultLogGroup for each construct. When you set different log groups for each construct, a warning message will be displayed.
const logGroup = new LogGroup(this, 'LogGroup');
new EcrScanVerifier(this, 'Scanner1', {
repository,
scanConfig: ScanConfig.basic(),
defaultLogGroup: logGroup,
});
new EcrScanVerifier(this, 'Scanner2', {
repository,
scanConfig: ScanConfig.basic(),
defaultLogGroup: new LogGroup(this, 'AnotherLogGroup'), // NG: different log group from Scanner1
defaultLogGroup: logGroup, // OK: Use the same log group as Scanner1 to avoid warning
});You can generate SBOM (Software Bill of Materials) using Amazon Inspector's CreateSbomExport API.
Note: SBOM export is only available with Enhanced scanning. Amazon Inspector only includes images that it actively monitors in SBOM exports. This requires the repository to match a CONTINUOUS_SCAN rule in Inspector's registry scanning configuration (set via aws ecr put-registry-scanning-configuration). Repositories covered only by a SCAN_ON_PUSH rule (or no rule at all) are silently skipped: CreateSbomExport returns SUCCEEDED and the S3 prefix folder is created, but no SBOM files are written under it. Even Inspector marking the image as scanned in the console is not sufficient — only CONTINUOUS_SCAN-monitored images produce SBOM files. The default is CONTINUOUS_SCAN for all repositories (*); verify yours with aws ecr get-registry-scanning-configuration.
import { SbomOutput, ScanConfig } from 'ecr-scan-verifier';
const sbomBucket = new Bucket(this, 'SbomBucket');
const sbomEncryptionKey = new Key(this, 'SbomEncryptionKey');
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.enhanced({
sbomOutput: SbomOutput.cycloneDx14({
bucket: sbomBucket,
prefix: 'sbom/', // Optional (default: no prefix, written to the bucket root)
encryptionKey: sbomEncryptionKey,
}),
}),
});Available SBOM formats:
SbomOutput.cycloneDx14()— CycloneDX 1.4 JSON formatSbomOutput.spdx23()— SPDX 2.3 JSON format
You can verify container image signatures before scanning using Notation (AWS Signer) or Cosign (Sigstore).
Signature verification is performed before the vulnerability scan during deployment. If verification fails and failOnUnsigned is true (the default), the deployment will fail.
import { SignatureVerification, ScanConfig } from 'ecr-scan-verifier';
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
signatureVerification: SignatureVerification.notation({
trustedIdentities: ['arn:aws:signer:us-east-1:123456789012:/signing-profiles/MyProfile'],
}),
});import { readFileSync } from 'fs';
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
signatureVerification: SignatureVerification.cosignPublicKey({
publicKey: readFileSync('path/to/cosign.pub', 'utf-8'),
}),
});import { Key } from 'aws-cdk-lib/aws-kms';
const cosignKey = Key.fromKeyArn(this, 'CosignKey', 'arn:aws:kms:...');
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
signatureVerification: SignatureVerification.cosignKms({
key: cosignKey,
}),
});You can configure how long the Scanner Lambda waits for the ECR scan to complete via pollingTimeout. Lower this value to fail fast on stuck scans, or keep the default when using Enhanced scanning (Amazon Inspector), whose initial scan after pushing a new image can take several minutes.
If the scan does not complete within pollingTimeout, the deployment fails with ECR image scan timed out.
Note that the timeout only applies to scans that can actually run. If the scanning configuration shows the scan will never start (e.g., the repository does not match any Enhanced scanning filter, or Basic scan-on-push is disabled with startScan: false), the deployment fails immediately without waiting for the timeout.
import { Duration } from 'aws-cdk-lib';
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.enhanced(),
pollingTimeout: Duration.minutes(5), // default: Duration.minutes(14)
});The value must be between 1 and 840 seconds (the Scanner Lambda has a 900 second timeout and reserves 60 seconds for SBOM export, signature verification, and notifications).
You can configure the amount of memory for the Scanner Lambda via memorySize. Increase this value when Enhanced scanning (Amazon Inspector) reports a large number of findings; loading them all can exceed the Lambda memory and crash the function with Runtime.OutOfMemory.
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.enhanced(),
memorySize: 1024, // default: 512
});The value must be between 128 and 10240 MB (the AWS Lambda limits). The Scanner Lambda is a singleton shared by all EcrScanVerifier constructs in the same stack, so the memory size of the first construct is used.
You can configure an SNS topic via vulnsNotificationTopic to receive notifications when vulnerabilities are detected.
By default, the construct fails the deployment when vulnerabilities are found.
You can set failOnVulnerability: false to receive SNS notifications without blocking the deployment.
const notificationTopic = new Topic(this, 'VulnerabilityNotificationTopic');
new EcrScanVerifier(this, 'Scanner', {
repository,
scanConfig: ScanConfig.basic(),
vulnsNotificationTopic: notificationTopic,
failOnVulnerability: false, // Notify but don't fail deployment
});