This check flags ECR repository policies that grant pull or push access to a principal in a different AWS account. Cross-account access can be legitimate, but a misconfigured or overly broad policy lets outside accounts read or tamper with your container images. Scope every cross-account grant to specific account IDs and actions, or remove the statement entirely.
Amazon Elastic Container Registry (ECR) stores the container images that run your production workloads. Most teams treat their registries as internal infrastructure, but ECR repositories support resource-based policies that can extend access to other AWS accounts. When one of those policies references a principal outside your account, you have a cross-account trust relationship that deserves scrutiny.
The ecr_crossaccount check looks at every repository policy in your account and raises a finding when it grants access to a principal that lives in a different AWS account. Sometimes that is exactly what you want, for example sharing a base image with a sibling account in your organization. Other times it is a leftover grant, a copy-paste mistake, or a policy that is far broader than the use case requires.
What this check detects
ECR repositories have two layers of access control:
- IAM identity policies attached to users and roles, which control what callers in your own account can do.
- Repository policies (resource-based policies) attached directly to a repository, which can grant access to principals in other accounts.
This check parses the repository policy document and inspects the Principal of each statement. If a principal belongs to an account ID that is not your own, the check flags the repository. It catches things like:
- An explicit ARN pointing at another account, for example
arn:aws:iam::222233334444:root. - A wildcard principal (
"Principal": "*") with no condition restricting the source account. - An overly permissive action list such as
ecr:*granted to an external account.
Note: A finding here does not automatically mean you are compromised. Cross-account sharing is a supported feature. The point of the check is to make sure every cross-account grant is intentional, documented, and scoped as tightly as the use case allows.
Why it matters
Container images are not just code. They often bundle dependencies, internal libraries, configuration templates, and sometimes baked-in secrets that should never have been there. Giving another account read access to your registry means giving it a copy of all of that.
Image exfiltration
If an attacker controls (or simply convinces you to add) an external account with ecr:GetDownloadUrlForLayer and ecr:BatchGetImage, they can pull your images and inspect every layer. Hardcoded API keys, database connection strings, and proprietary source code all become readable. This is one of the most common ways internal secrets leak outside an organization without anyone noticing, because a pull does not look like an attack in your logs.
Supply chain tampering
The more dangerous case is write access. If a cross-account policy grants ecr:PutImage or the catch-all ecr:*, an outside principal can push a malicious image to a tag your services pull. The next deployment pulls the tampered image and runs attacker-controlled code inside your environment.
Danger: A cross-account grant that includes ecr:PutImage or ecr:* on a repository your production workloads pull from is a direct supply chain risk. An external account could overwrite a trusted tag and your cluster would run the malicious image on the next pull. Treat write grants to external accounts as critical findings.
Wildcard principals are public exposure
A statement with "Principal": "*" and no aws:SourceAccount or aws:PrincipalOrgID condition effectively makes the repository accessible to anyone with an AWS account, assuming they can guess or discover the repository ARN. This is the ECR equivalent of a public S3 bucket and it shows up more often than you would expect.
How to fix it
Start by retrieving the current policy so you know exactly what you are dealing with.
Step 1: Inspect the existing repository policy
aws ecr get-repository-policy \
--repository-name my-service \
--region us-east-1 \
--query 'policyText' \
--output text | jq .
Look at each statement's Principal and Action. Identify which account IDs are referenced and confirm whether each one is supposed to have access.
Step 2: Decide the correct outcome
For each flagged statement, you have three options:
- Remove it if the cross-account access is no longer needed.
- Tighten it to a specific account ID and the minimum set of actions.
- Keep it as-is and document it if it is already correctly scoped and intentional.
Step 3a: Remove the policy entirely
If no cross-account access is required at all, delete the repository policy. Identity-based IAM policies in your own account continue to work.
Warning: Removing or changing a repository policy can break legitimate cross-account pulls. Before you delete, confirm that no other account or CI pipeline depends on this grant. A broken pull will cause deployment failures in the consuming account.
aws ecr delete-repository-policy \
--repository-name my-service \
--region us-east-1
Step 3b: Replace it with a tightly scoped policy
If the cross-account access is legitimate, replace the broad policy with one that names the exact account and grants read-only actions. Save the following as ecr-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPullFromAccount222233334444",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222233334444:root"
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
}
]
}
Apply it:
aws ecr set-repository-policy \
--repository-name my-service \
--region us-east-1 \
--policy-text file://ecr-policy.json
This grants pull access and nothing else. Note that the three actions above are the minimum required for a docker pull against ECR.
Tip: If you share images across many accounts in the same AWS Organization, use a condition on aws:PrincipalOrgID instead of listing every account ID. The grant stays valid as accounts join or leave the org, and you never have to touch the policy again.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrgPull",
"Effect": "Allow",
"Principal": "*",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
],
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-abcd1234ef"
}
}
}
]
}
Even though the principal is *, the aws:PrincipalOrgID condition limits access to accounts inside your organization, which is far safer than an unconditional wildcard.
Step 4: Verify
aws ecr get-repository-policy \
--repository-name my-service \
--region us-east-1 \
--query 'policyText' \
--output text | jq .
Confirm the policy now matches your intent, then re-run the Lensix check to clear the finding.
Fixing it with infrastructure as code
If you manage ECR with Terraform, define the policy as a resource so the access is version controlled and reviewable. Here is a scoped cross-account read-only example:
resource "aws_ecr_repository" "my_service" {
name = "my-service"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
data "aws_iam_policy_document" "ecr_cross_account" {
statement {
sid = "AllowPullFromConsumerAccount"
effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:aws:iam::222233334444:root"]
}
actions = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
]
}
}
resource "aws_ecr_repository_policy" "my_service" {
repository = aws_ecr_repository.my_service.name
policy = data.aws_iam_policy_document.ecr_cross_account.json
}
Because the account ID and actions are explicit in code, any future change to who can access the registry shows up in a pull request and gets reviewed before it ships.
How to prevent it from happening again
Manual fixes solve today's finding. Preventing the next one means catching bad policies before they reach an account.
Gate IaC in CI/CD
Run a policy-as-code tool against your Terraform or CloudFormation in the pipeline. Both Checkov and tfsec / Trivy understand ECR repository policies and can fail a build when a wildcard principal appears without a scoping condition.
# Scan a Terraform directory and fail on ECR policy issues
checkov -d ./infra --compact
# Or with Trivy in config-scan mode
trivy config ./infra
Add a custom rule that rejects any ECR policy where the principal is not in an allowlist of known account IDs. That turns your approved-accounts list into an enforced contract.
Block public access at the org level
Use a Service Control Policy (SCP) to deny the creation of ECR policies that reference principals outside your organization. An SCP applies to every account under the org unit and cannot be overridden by an account admin.
Warning: SCPs are blunt instruments. Test a deny policy in a non-production OU first. A poorly written condition can block your own automation from setting legitimate repository policies, which is a frustrating failure to debug under deadline.
Detect drift continuously
IaC gates only protect resources that go through the pipeline. Someone with console access can still add a cross-account grant by hand. Continuous scanning with Lensix catches those out-of-band changes by re-evaluating every repository policy on a schedule, so a manual edit surfaces as a finding rather than sitting unnoticed for months.
Best practices
- Default to read-only. External accounts almost never need to push. Grant only the three pull actions unless you have a documented reason for write access.
- Prefer
aws:PrincipalOrgIDover account lists when sharing within an organization. It scales cleanly and removes the temptation to use an open wildcard. - Never use an unconditional
"Principal": "*". If you see one, treat it as public and fix it immediately. - Enable scan-on-push and immutable tags. Immutable tags prevent an attacker, or an accident, from overwriting a trusted tag, which closes off the most damaging supply chain attack path.
- Audit cross-account grants quarterly. Trust relationships outlive the projects that created them. Review which accounts still need access and revoke the ones that do not.
- Use a dedicated shared-services account for golden base images, then grant other accounts read access to that one registry instead of scattering cross-account policies across every team's repos.
Tip: Pair this check with CloudTrail monitoring on SetRepositoryPolicy and PutImage events. A real-time alert when either API is called against a shared repository gives you a second line of defense behind your IaC gates.
Cross-account access in ECR is a feature, not a flaw, but it is one that quietly widens your blast radius every time it is used carelessly. Keep each grant explicit, minimal, and reviewed, and your container registry stays a controlled distribution point rather than an open door into your environment.

