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.

derived aws emits CrossAccountTrust

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'repository': None} HasPolicy {'resource_policy': None}

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 typeResourcePolicy
target typeExternalIdentity
source?resource_policy
target<external principal / account named in the repository policy>
permissionsecr:PutImage ecr:BatchCheckLayerAvailability ecr:InitiateLayerUpload ecr:UploadLayerPart ecr:CompleteLayerUpload
conditionsresource_policy
state logicACTIVE 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).'
move · open · esc close