aws-ecr-cross-account-push
A repository resource policy grants ecr:PutImage to a principal in another AWS account, creating a cross-account supply-chain push trust.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?repository) ==
ContainerRegistry
?repository.provider_type in ['AWS::ECR::Repository']
?resource_policy grants ecr:PutImage (directly or via ecr:*) to a principal whose account != ?repository.account
emit
| source type | ResourcePolicy |
|---|---|
| target type | ExternalIdentity |
| source | ?resource_policy |
| target | <external principal / account named in the repository policy> |
| permissions | ecr:PutImage ecr:BatchCheckLayerAvailability ecr:InitiateLayerUpload ecr:UploadLayerPart ecr:CompleteLayerUpload |
| conditions | resource_policy |
| state logic | ACTIVE when the repository policy statement granting PutImage to the external principal is present, not expired, and not blocked by an SCP in the owning account's organization. CONDITIONAL(resource_policy) when the grant includes a Condition (e.g. aws:SourceArn, aws:PrincipalOrgID, aws:PrincipalTag) that must hold — downgrade to CONDITIONAL(condition_expression) in that case. BLOCKED when an SCP in the owning org denies cross-account ECR push. |
Narrative
{resource_policy.name} (the repository policy of {repository.name}) grants ecr:PutImage to {external.name} in account {external.account}, establishing a cross-account push trust. The external principal can push a poisoned image from their account into this repository, injecting attacker code into every workload in account {repository.account} that pulls from this repository (container-chains rolls up the supply-chain paths).
Raw rule rules/derived/aws/ecr.yaml
id: aws-ecr-cross-account-push
emits: CrossAccountTrust
description: A repository resource policy grants ecr:PutImage to a principal in another AWS account, creating
a cross-account supply-chain push trust.
match:
- - repository: null
- HasPolicy
- resource_policy: null
where:
- node_type(?repository) == ContainerRegistry
- ?repository.provider_type in ['AWS::ECR::Repository']
- ?resource_policy grants ecr:PutImage (directly or via ecr:*) to a principal whose account != ?repository.account
emit:
source_type: ResourcePolicy
target_type: ExternalIdentity
source: ?resource_policy
target: <external principal / account named in the repository policy>
permissions:
- ecr:PutImage
- ecr:BatchCheckLayerAvailability
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
conditions:
- resource_policy
state_logic: "ACTIVE when the repository policy statement granting PutImage to the external principal\
\ is present, not expired, and not blocked by an SCP in the owning account's organization. CONDITIONAL(resource_policy)\
\ when the grant includes a Condition (e.g. aws:SourceArn, aws:PrincipalOrgID, aws:PrincipalTag) that\
\ must hold \u2014 downgrade to CONDITIONAL(condition_expression) in that case. BLOCKED when an SCP\
\ in the owning org denies cross-account ECR push."
confidence: 0.92
derived_from:
- ?repository HasPolicy ?resource_policy (grants ecr:PutImage cross-account)
false_positive_note: "Cross-account pull (BatchGetImage + GetDownloadUrlForLayer) is NOT a push trust;\
\ emit CrossAccountTrust only when PutImage is granted. Validate that the external account-id in the\
\ policy statement is distinct from ?repository.account; a policy with Principal '*' but no cross-account\
\ implication (e.g. aws:PrincipalAccount condition restricting to same account) does not create a\
\ cross-account trust. Honor Condition blocks: aws:PrincipalOrgID restricts to org members, not all\
\ external accounts \u2014 mark as CONDITIONAL(condition_expression)."
narrative: '{resource_policy.name} (the repository policy of {repository.name}) grants ecr:PutImage
to {external.name} in account {external.account}, establishing a cross-account push trust. The external
principal can push a poisoned image from their account into this repository, injecting attacker code
into every workload in account {repository.account} that pulls from this repository (container-chains
rolls up the supply-chain paths).'