Skip to main content

ECR Repository Not Publicly Accessible

Overview

This check examines Amazon Elastic Container Registry (ECR) repositories and flags any that allow public access through their repository policies. A repository is considered publicly accessible when its policy contains a statement with Principal: "*", which permits anyone (including unauthenticated users) to access the repository.

Risk

A publicly accessible ECR repository creates serious security vulnerabilities:

  • Secrets exposure: Container images often contain embedded credentials, API keys, database connection strings, or configuration files that become accessible to anyone
  • Intellectual property leakage: Proprietary application code and business logic packaged in images can be pulled and analyzed by competitors or attackers
  • Supply chain attacks: If write permissions are granted publicly, attackers could push malicious images to your repository, potentially compromising your deployment pipeline
  • Infrastructure fingerprinting: Attackers can analyze your images to understand your software stack, dependencies, and potential vulnerabilities
  • Unexpected costs: Unrestricted pulls can lead to significant data transfer charges
  • Compliance violations: Public repositories may violate data protection regulations (GDPR, HIPAA, PCI-DSS)

This check is rated Critical severity because public ECR repositories can directly expose your application code and secrets to the internet.

Remediation Steps

Prerequisites

  • AWS account access with permissions to modify ECR repository policies
  • The repository name that needs to be made private
Required IAM permissions

You will need the following permissions:

  • ecr:GetRepositoryPolicy - View current repository policy
  • ecr:SetRepositoryPolicy - Modify the repository policy
  • ecr:DeleteRepositoryPolicy - Remove the repository policy entirely
  • ecr:DescribeRepositories - List and view repository details

AWS Console Method

Step 1: Find your publicly accessible repositories

  1. Sign in to the AWS Management Console
  2. Navigate to Amazon ECR (search for "ECR" in the search bar)
  3. Make sure you are in the correct region (e.g., us-east-1)
  4. Click Repositories in the left sidebar
  5. Select each repository and check its Permissions tab for policies containing "Principal": "*"

Step 2: Remove public access from the repository

Option A: Delete the entire repository policy (simplest approach if no policy is needed)

  1. Select the repository by clicking its name
  2. Click the Permissions tab
  3. Click Delete policy (or Edit policy JSON > select all > delete)
  4. Confirm the deletion

Option B: Edit the policy to remove public access (if you need to keep some permissions)

  1. Select the repository by clicking its name
  2. Click the Permissions tab
  3. Click Edit policy JSON
  4. Remove any statements that contain "Principal": "*"
  5. Replace with specific AWS account IDs or IAM role ARNs that need access
  6. Click Save

Step 3: (Optional) Grant access to specific accounts

If you need to share this repository with specific AWS accounts or roles:

  1. On the Permissions tab, click Edit policy JSON
  2. Add a policy statement with specific principals:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
}
  1. Replace 123456789012 with the actual AWS account ID
  2. Click Save
AWS CLI (optional)

View the current repository policy:

aws ecr get-repository-policy \
--repository-name <your-repository-name> \
--region us-east-1

Replace <your-repository-name> with the actual repository name.

Delete the repository policy entirely:

aws ecr delete-repository-policy \
--repository-name <your-repository-name> \
--region us-east-1

This removes all permissions, making the repository accessible only to the owning account.

Set a new policy with specific account access:

First, create a policy file named ecr-policy.json:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
}
]
}

Then apply the policy:

aws ecr set-repository-policy \
--repository-name <your-repository-name> \
--policy-text file://ecr-policy.json \
--region us-east-1

List all repositories and check policies in bulk:

# List all repositories
aws ecr describe-repositories \
--query 'repositories[*].repositoryName' \
--output text \
--region us-east-1

# Check each repository's policy (will error if no policy exists)
for repo in $(aws ecr describe-repositories --query 'repositories[*].repositoryName' --output text --region us-east-1); do
echo "Repository: $repo"
aws ecr get-repository-policy --repository-name "$repo" --region us-east-1 2>/dev/null || echo " No policy set"
echo ""
done
CloudFormation (optional)

When creating ECR repositories with CloudFormation, ensure repository policies do not include Principal: "*".

Create a private ECR repository (no policy needed for private access):

AWSTemplateFormatVersion: '2010-09-09'
Description: Private ECR repository without public access

Parameters:
RepositoryName:
Type: String
Description: Name of the ECR repository
Default: my-private-repo

Resources:
PrivateECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Ref RepositoryName
ImageScanningConfiguration:
ScanOnPush: true
EncryptionConfiguration:
EncryptionType: AES256
ImageTagMutability: IMMUTABLE
Tags:
- Key: Environment
Value: Production
- Key: Public
Value: 'false'

Outputs:
RepositoryArn:
Description: ARN of the ECR repository
Value: !GetAtt PrivateECRRepository.Arn
RepositoryUri:
Description: URI of the ECR repository
Value: !GetAtt PrivateECRRepository.RepositoryUri

Create an ECR repository with cross-account access (not public):

AWSTemplateFormatVersion: '2010-09-09'
Description: ECR repository with specific cross-account access

Parameters:
RepositoryName:
Type: String
Description: Name of the ECR repository
Default: shared-repo
TrustedAccountId:
Type: String
Description: AWS Account ID to grant access to
AllowedPattern: '[0-9]{12}'
ConstraintDescription: Must be a valid 12-digit AWS account ID

Resources:
SharedECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Ref RepositoryName
ImageScanningConfiguration:
ScanOnPush: true
EncryptionConfiguration:
EncryptionType: AES256
ImageTagMutability: IMMUTABLE
RepositoryPolicyText:
Version: '2012-10-17'
Statement:
- Sid: AllowCrossAccountPull
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${TrustedAccountId}:root'
Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
- ecr:BatchCheckLayerAvailability
Tags:
- Key: Environment
Value: Production
- Key: SharedWith
Value: !Ref TrustedAccountId

Outputs:
RepositoryArn:
Description: ARN of the ECR repository
Value: !GetAtt SharedECRRepository.Arn
RepositoryUri:
Description: URI of the ECR repository
Value: !GetAtt SharedECRRepository.RepositoryUri

Important: Never use "Principal": "*" in RepositoryPolicyText. Always specify explicit AWS account IDs or IAM role ARNs.

Terraform (optional)

Create a private ECR repository (default - no policy needed):

provider "aws" {
region = "us-east-1"
}

resource "aws_ecr_repository" "private_repo" {
name = "my-private-repo"
image_tag_mutability = "IMMUTABLE"

image_scanning_configuration {
scan_on_push = true
}

encryption_configuration {
encryption_type = "AES256"
}

tags = {
Environment = "Production"
Public = "false"
}
}

# No aws_ecr_repository_policy resource = repository is private by default

Create an ECR repository with specific cross-account access:

provider "aws" {
region = "us-east-1"
}

variable "trusted_account_id" {
description = "AWS Account ID to grant access to"
type = string
default = "123456789012"
}

resource "aws_ecr_repository" "shared_repo" {
name = "shared-repo"
image_tag_mutability = "IMMUTABLE"

image_scanning_configuration {
scan_on_push = true
}

encryption_configuration {
encryption_type = "AES256"
}

tags = {
Environment = "Production"
SharedWith = var.trusted_account_id
}
}

# Grant access to specific accounts only - NOT public
resource "aws_ecr_repository_policy" "cross_account_policy" {
repository = aws_ecr_repository.shared_repo.name

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCrossAccountPull"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.trusted_account_id}:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
}
]
})
}

Fix an existing public repository by removing the policy:

# Import and remove the policy from an existing repository
# First, import: terraform import aws_ecr_repository.existing_repo my-repo-name

resource "aws_ecr_repository" "existing_repo" {
name = "my-repo-name"
# ... other configuration
}

# Do NOT create an aws_ecr_repository_policy resource
# If one exists from before, use: terraform state rm aws_ecr_repository_policy.old_policy

Important: Never use "Principal": "*" in ECR repository policies. Always specify explicit AWS account IDs, IAM role ARNs, or IAM user ARNs.

Verification

After completing the remediation:

  1. Go to Amazon ECR > Repositories in the AWS Console
  2. Select your repository and click the Permissions tab
  3. Verify that the policy either does not exist or does not contain "Principal": "*"
  4. Re-run the Prowler check to confirm the issue is resolved
CLI verification commands

Verify the repository policy is removed or updated:

aws ecr get-repository-policy \
--repository-name <your-repository-name> \
--region us-east-1

If the policy was deleted, you will see an error: RepositoryPolicyNotFoundException. This means the repository is private.

If a policy exists, verify the output does not contain "Principal": "*".

Check all repositories for public access:

for repo in $(aws ecr describe-repositories --query 'repositories[*].repositoryName' --output text --region us-east-1); do
policy=$(aws ecr get-repository-policy --repository-name "$repo" --query 'policyText' --output text --region us-east-1 2>/dev/null)
if echo "$policy" | grep -q '"Principal"[[:space:]]*:[[:space:]]*"\*"'; then
echo "WARNING: $repo has public access!"
fi
done

Additional Resources

Notes

  • Private vs. Public ECR: This check applies to private ECR repositories with overly permissive policies. AWS also offers Amazon ECR Public for intentionally public images. If you need public container distribution, use ECR Public with appropriate content governance.
  • VPC endpoints: For enhanced security, configure VPC endpoints for ECR to keep all traffic within your VPC and avoid internet exposure.
  • Image scanning: Enable image scanning on push to detect vulnerabilities in your container images before deployment.
  • Immutable tags: Use immutable image tags to prevent image tampering and ensure deployment consistency.
  • Lifecycle policies: Implement ECR lifecycle policies to automatically clean up old images and reduce storage costs and attack surface.
  • Cross-account sharing: When sharing images across accounts, always specify explicit account IDs or role ARNs rather than using wildcards.
  • Audit regularly: Periodically review ECR repository policies to ensure no accidental public exposure has occurred.