Skip to content

AWS provider

The AWS provider uses a boto3.Session scoped to a single region. It supports named AWS CLI profiles via --profile and honors the AWS_ENDPOINT_URL environment variable (for LocalStack).

Every AWS rule is one module under pipeline_check/core/checks/aws/rules/<id>_<slug>.py, auto-discovered by AWSRuleChecks and given a shared ResourceCatalog so enumerations run once per scan.

Services covered

Service Check IDs
CodeBuild CB-001..011
CodePipeline CP-001..007
CodeDeploy CD-001, CD-002, CD-003
ECR ECR-001..007
IAM IAM-001..008
PBAC (CodeBuild roles/VPC, pipeline role scoping) PBAC-001..005
S3 S3-001, S3-002, S3-003, S3-004, S3-005
CloudTrail CT-001, CT-002, CT-003
CloudWatch Logs CWL-001, CWL-002
CloudWatch Alarms CW-001
Secrets Manager SM-001, SM-002
CodeArtifact CA-001, CA-002, CA-003, CA-004
CodeCommit CCM-001, CCM-002, CCM-003
Lambda LMB-001, LMB-002, LMB-003, LMB-004
KMS KMS-001, KMS-002
SSM Parameter Store SSM-001, SSM-002
EventBridge EB-001, EB-002
AWS Signer SIGN-001, SIGN-002

CLI usage

# Default: scan us-east-1 with the ambient boto3 credential chain
pipeline_check --pipeline aws

# Pick a region
pipeline_check --pipeline aws --region eu-west-1

# Use a named AWS CLI profile (~/.aws/credentials)
pipeline_check --pipeline aws --profile prod-readonly

# Scope the scan to a single resource (e.g. one CodePipeline)
pipeline_check --pipeline aws --target my-release-pipeline

# Point boto3 at a local endpoint (LocalStack, etc.)
AWS_ENDPOINT_URL=http://localhost:4566 pipeline_check --pipeline aws

Credentials are resolved through the standard boto3 credential chain. Environment variables, ~/.aws/credentials, IMDS on EC2, container credentials on ECS/Fargate, EKS Pod Identity, or SSO. To scan a different account, assume the role first with aws sts assume-role (or aws sso login / aws-vault) and export the resulting credentials, then invoke pipeline_check. The provider does not currently accept an --assume-role-arn flag.

Required IAM permissions

The scanner is read-only: every API call is a List*, Describe*, Get*, or BatchGet*. It never mutates state. Per-resource API calls are scoped to the active --region; IAM, S3, and STS are global and are reached through the same regional session.

Quickest path: managed policies

If you don't care about least-privilege, attach one of:

  • arn:aws:iam::aws:policy/SecurityAudit: covers everything below except a few Get*Policy calls; produces the same findings minus S3-005 (bucket policies), LMB-004 (Lambda resource policy), KMS-002 (key policy), CA-003/CA-004 (CodeArtifact policies), ECR-003 (repo policy), SM-002 (secret resource policy).
  • arn:aws:iam::aws:policy/ReadOnlyAccess: covers every action the scanner uses, plus a great deal more. Convenient but broad.

For least-privilege, use the policy below.

Permission map by service

Each row lists the check IDs that depend on the actions in that service. If you skip a service entirely, you can drop its row from the policy and the scanner will emit a <PREFIX>-000 degraded finding (INFO) for that service rather than failing.

Service Check IDs that need it Required actions
CodeBuild CB-001..011, PBAC-001 codebuild:ListProjects, codebuild:BatchGetProjects, codebuild:ListSourceCredentials
CodePipeline CP-001..007, PBAC-005 (also feeds S3 artifact-bucket discovery) codepipeline:ListPipelines, codepipeline:GetPipeline
CodeDeploy CD-001..003 codedeploy:ListApplications, codedeploy:ListDeploymentGroups, codedeploy:BatchGetDeploymentGroups
ECR ECR-001..007 ecr:DescribeRepositories, ecr:GetRepositoryPolicy, ecr:GetLifecyclePolicy, ecr:DescribePullThroughCacheRules
Inspector v2 ECR-007 inspector2:BatchGetAccountStatus
IAM IAM-001..008, PBAC-002, CICD-role enumeration iam:ListRoles, iam:ListUsers, iam:ListAccessKeys, iam:GetAccessKeyLastUsed, iam:ListRolePolicies, iam:GetRolePolicy, iam:ListAttachedRolePolicies, iam:GetPolicy, iam:GetPolicyVersion
CloudTrail CT-001..003 cloudtrail:DescribeTrails, cloudtrail:GetTrailStatus
CloudWatch Logs CWL-001, CWL-002 logs:DescribeLogGroups
CloudWatch Alarms CW-001 cloudwatch:DescribeAlarms
Secrets Manager SM-001, SM-002 secretsmanager:ListSecrets, secretsmanager:GetResourcePolicy
CodeArtifact CA-001..004 codeartifact:ListDomains, codeartifact:ListRepositories, codeartifact:DescribeRepository, codeartifact:GetDomainPermissionsPolicy, codeartifact:GetRepositoryPermissionsPolicy
CodeCommit CCM-001..003 codecommit:ListRepositories, codecommit:GetRepository, codecommit:GetRepositoryTriggers, codecommit:ListAssociatedApprovalRuleTemplatesForRepository
Lambda LMB-001..004 lambda:ListFunctions, lambda:GetFunctionCodeSigningConfig, lambda:GetFunctionUrlConfig, lambda:GetPolicy
KMS KMS-001, KMS-002 kms:ListKeys, kms:DescribeKey, kms:GetKeyRotationStatus, kms:GetKeyPolicy
SSM Parameter Store SSM-001, SSM-002 ssm:DescribeParameters
EventBridge EB-001, EB-002 events:ListRules, events:ListTargetsByRule
Signer SIGN-001, SIGN-002 signer:ListSigningProfiles
S3 S3-001..005 (artifact buckets discovered via CodePipeline) s3:GetPublicAccessBlock, s3:GetBucketEncryption, s3:GetBucketVersioning, s3:GetBucketLogging, s3:GetBucketPolicy
EC2 PBAC-003 (CodeBuild VPC security groups) ec2:DescribeSecurityGroups
STS CCM-003 (current account ID for cross-account trigger detection) sts:GetCallerIdentity

Copy-paste IAM policy

Save the following as pipeline-check-readonly.json and attach it to the role or user the scanner runs as. Every action is read-only and every resource is * because boto3 list/describe APIs do not accept resource-level conditions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PipelineCheckReadOnlyScan",
      "Effect": "Allow",
      "Action": [
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus",
        "cloudwatch:DescribeAlarms",
        "codeartifact:DescribeRepository",
        "codeartifact:GetDomainPermissionsPolicy",
        "codeartifact:GetRepositoryPermissionsPolicy",
        "codeartifact:ListDomains",
        "codeartifact:ListRepositories",
        "codebuild:BatchGetProjects",
        "codebuild:ListProjects",
        "codebuild:ListSourceCredentials",
        "codecommit:GetRepository",
        "codecommit:GetRepositoryTriggers",
        "codecommit:ListAssociatedApprovalRuleTemplatesForRepository",
        "codecommit:ListRepositories",
        "codedeploy:BatchGetDeploymentGroups",
        "codedeploy:ListApplications",
        "codedeploy:ListDeploymentGroups",
        "codepipeline:GetPipeline",
        "codepipeline:ListPipelines",
        "ec2:DescribeSecurityGroups",
        "ecr:DescribePullThroughCacheRules",
        "ecr:DescribeRepositories",
        "ecr:GetLifecyclePolicy",
        "ecr:GetRepositoryPolicy",
        "events:ListRules",
        "events:ListTargetsByRule",
        "iam:GetAccessKeyLastUsed",
        "iam:GetPolicy",
        "iam:GetPolicyVersion",
        "iam:GetRolePolicy",
        "iam:ListAccessKeys",
        "iam:ListAttachedRolePolicies",
        "iam:ListRolePolicies",
        "iam:ListRoles",
        "iam:ListUsers",
        "inspector2:BatchGetAccountStatus",
        "kms:DescribeKey",
        "kms:GetKeyPolicy",
        "kms:GetKeyRotationStatus",
        "kms:ListKeys",
        "lambda:GetFunctionCodeSigningConfig",
        "lambda:GetFunctionUrlConfig",
        "lambda:GetPolicy",
        "lambda:ListFunctions",
        "logs:DescribeLogGroups",
        "s3:GetBucketEncryption",
        "s3:GetBucketLogging",
        "s3:GetBucketPolicy",
        "s3:GetBucketVersioning",
        "s3:GetPublicAccessBlock",
        "secretsmanager:GetResourcePolicy",
        "secretsmanager:ListSecrets",
        "signer:ListSigningProfiles",
        "ssm:DescribeParameters",
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    }
  ]
}

The policy is 2.5 KB, well inside the 6,144-byte limit for a customer-managed policy and the 10,240-byte limit for an inline role policy.

Trust policy for an IAM role

If you run the scanner from CI (e.g. GitHub Actions with OIDC), pair the policy above with a trust policy that lets your CI system assume the role. Below is an example for GitHub Actions OIDC; adapt for your provider.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

Behavior when permissions are missing

The scanner does not fail closed when the principal lacks an action. Instead, the per-service enumeration records the error and the orchestrator emits one <PREFIX>-000 finding (INFO severity) per degraded service, for example CT-000, LMB-000, KMS-000. Every rule that depends on that service is suppressed for the run. Operators can therefore see exactly which permission gaps are masking findings.

Two exceptions are tolerated silently because their endpoints are optional:

  • ecr:DescribePullThroughCacheRules: not all regions/accounts have PTC; only ECR-006 is suppressed if it fails.
  • inspector2:BatchGetAccountStatus: only ECR-007 is suppressed if Inspector v2 is not enabled in the region.

What it covers

71 checks · 0 have an autofix patch (--fix).

Check Title Severity Fix
CA-001 CodeArtifact domain has no KMS encryptionKey configured MEDIUM
CA-002 CodeArtifact repository has a public external connection HIGH
CA-003 CodeArtifact domain policy allows cross-account wildcard CRITICAL
CA-004 CodeArtifact repo policy grants codeartifact:* with Resource '*' HIGH
CB-001 Secrets in plaintext environment variables CRITICAL
CB-002 Privileged mode enabled HIGH
CB-003 Build logging not enabled MEDIUM
CB-004 Build timeout missing or at the AWS maximum (480 min) LOW
CB-005 Outdated managed build image MEDIUM
CB-006 CodeBuild source auth uses long-lived token HIGH
CB-007 CodeBuild webhook has no filter group MEDIUM
CB-008 CodeBuild buildspec is inline (not sourced from a protected repo) HIGH
CB-009 CodeBuild image not pinned by digest MEDIUM
CB-010 CodeBuild webhook allows fork-PR builds without actor filtering HIGH
CB-011 CodeBuild buildspec contains indicators of malicious activity CRITICAL
CCM-001 CodeCommit repository has no approval rule template attached HIGH
CCM-002 CodeCommit repository not encrypted with customer KMS CMK MEDIUM
CCM-003 CodeCommit trigger targets SNS/Lambda in a different account MEDIUM
CD-001 Automatic rollback on failure not enabled MEDIUM
CD-002 AllAtOnce deployment config, no canary or rolling strategy HIGH
CD-003 No CloudWatch alarm monitoring on deployment group MEDIUM
CP-001 No approval action before deploy stages HIGH
CP-002 Artifact store not encrypted with customer-managed KMS key MEDIUM
CP-003 Source stage using polling instead of event-driven trigger LOW
CP-004 Legacy ThirdParty/GitHub source action (OAuth token) HIGH
CP-005 Production Deploy stage has no preceding ManualApproval MEDIUM
CP-007 CodePipeline v2 PR trigger accepts all branches HIGH
CT-001 No active CloudTrail trail in region HIGH
CT-002 CloudTrail log-file validation disabled MEDIUM
CT-003 CloudTrail trail is not multi-region MEDIUM
CW-001 No CloudWatch alarm on CodeBuild FailedBuilds metric LOW
CWL-001 CodeBuild log group has no retention policy LOW
CWL-002 CodeBuild log group not KMS-encrypted MEDIUM
EB-001 No EventBridge rule for CodePipeline failure notifications MEDIUM
EB-002 EventBridge rule has a wildcard target ARN HIGH
ECR-001 Image scanning on push not enabled HIGH
ECR-002 Image tags are mutable HIGH
ECR-003 Repository policy allows public access CRITICAL
ECR-004 No lifecycle policy configured LOW
ECR-005 Repository encrypted with AES256 rather than KMS CMK MEDIUM
ECR-006 ECR pull-through cache rule uses an untrusted upstream HIGH
ECR-007 Inspector v2 enhanced scanning disabled for ECR MEDIUM
IAM-001 CI/CD role has AdministratorAccess policy attached CRITICAL
IAM-002 CI/CD role has wildcard Action in attached policy HIGH
IAM-003 CI/CD role has no permission boundary MEDIUM
IAM-004 CI/CD role can PassRole to any role HIGH
IAM-005 CI/CD role trust policy missing sts:ExternalId HIGH
IAM-006 Sensitive actions granted with wildcard Resource MEDIUM
IAM-007 IAM user has access key older than 90 days HIGH
IAM-008 OIDC-federated role trust policy missing audience or subject pin HIGH
KMS-001 KMS customer-managed key has rotation disabled MEDIUM
KMS-002 KMS key policy grants wildcard KMS actions HIGH
LMB-001 Lambda function has no code-signing config HIGH
LMB-002 Lambda function URL has AuthType=NONE HIGH
LMB-003 Lambda function env vars may contain plaintext secrets HIGH
LMB-004 Lambda resource policy allows wildcard principal CRITICAL
PBAC-001 CodeBuild project has no VPC configuration HIGH
PBAC-002 CodeBuild service role shared across multiple projects MEDIUM
PBAC-003 CodeBuild security group allows 0.0.0.0/0 all-port egress MEDIUM
PBAC-005 CodePipeline stage action roles mirror the pipeline role HIGH
S3-001 Artifact bucket public access block not fully enabled CRITICAL
S3-002 Artifact bucket server-side encryption not configured HIGH
S3-003 Artifact bucket versioning not enabled MEDIUM
S3-004 Artifact bucket access logging not enabled LOW
S3-005 Artifact bucket missing aws:SecureTransport deny MEDIUM
SIGN-001 No AWS Signer profile defined for Lambda deploys MEDIUM
SIGN-002 AWS Signer profile is revoked or inactive HIGH
SM-001 Secrets Manager secret has no rotation configured HIGH
SM-002 Secrets Manager resource policy allows wildcard principal CRITICAL
SSM-001 SSM Parameter with secret-like name is not a SecureString HIGH
SSM-002 SSM SecureString uses the default AWS-managed key MEDIUM

CA-001: CodeArtifact domain has no KMS encryptionKey configured

MEDIUM CICD-SEC-9 CWE-311

When no encryptionKey is configured on the domain, AWS uses its own managed key, keeping the key policy under AWS's control. That removes your ability to scope or audit Decrypt operations, and you can't revoke key access without recreating the domain. A customer-managed CMK puts those controls back in your hands. Note: the CodeArtifact API returns the resolved KMS key ARN in this field; the check flags only the absent-key case because the ARN alone does not reliably identify whether the key is AWS-managed or customer-managed without a separate kms:DescribeKey call.

Recommended action

Recreate the CodeArtifact domain with an encryption-key argument pointing at a customer-managed CMK. Domain encryption is set at creation and cannot be changed after.

CA-002: CodeArtifact repository has a public external connection

HIGH CICD-SEC-3 CWE-829

An external connection to public:npmjs / public:pypi / public:nuget / public:maven-central fetches packages from the public registry on first resolution. A typo-squat (request vs requests) or a compromised upstream lands in the cache the first time anyone names it; every subsequent build pulls the cached substitute. The pull-through cache with an allow-list is the same risk shape solved by an explicit allowlist.

Recommended action

Route public package consumption through a pull-through cache repository governed by an allow-list of package names, and point build-time repos at that cache rather than directly at public:npmjs/public:pypi. Unscoped public upstreams expose builds to dependency-confusion and typosquatting attacks.

CA-003: CodeArtifact domain policy allows cross-account wildcard

CRITICAL CICD-SEC-8 CWE-732

A wildcard-principal Allow on a CodeArtifact domain lets any AWS account reach the domain's permissions surface. The exact damage depends on the action set, but at minimum it lets external accounts read package names and versions, which is enough for typosquat-against-private-package attacks. aws:PrincipalOrgID is the org-level rescue without enumerating accounts.

Recommended action

Remove Allow statements with Principal: '*' from every CodeArtifact domain permissions policy, or restrict them with an aws:PrincipalOrgID condition so only accounts in your org can consume packages from the domain.

CA-004: CodeArtifact repo policy grants codeartifact:* with Resource '*'

HIGH CICD-SEC-2 CWE-732

codeartifact:* on Resource: '*' collapses the entire repository's authority into one grant: the holder can read, write, delete, dispose, and re-publish every package. Even for a service principal that nominally only consumes packages, the grant lets a compromise of that consumer rewrite every dependency the team relies on.

Recommended action

Scope Allow statements to specific codeartifact: actions (e.g. codeartifact:ReadFromRepository) and to specific package-group ARNs. Wildcard action + wildcard resource is the classic over-broad grant that lets a consumer also publish.

CB-001: Secrets in plaintext environment variables

CRITICAL CICD-SEC-6 CWE-798

Flags a plaintext env var when either (a) its name matches a secret-like pattern (PASSWORD, TOKEN, API_KEY, ...) or (b) its value matches a known credential shape (AKIA/ASIA access keys, GitHub tokens, Slack xox* tokens, JWTs). Plaintext values are visible in the AWS console, CloudTrail, and build logs to anyone with read access.

Recommended action

Move secrets to AWS Secrets Manager or SSM Parameter Store and reference them using type SECRETS_MANAGER or PARAMETER_STORE in the CodeBuild environment variable configuration.

CB-002: Privileged mode enabled

HIGH CICD-SEC-7 CWE-269

Privileged mode grants the build container root access to the host's Docker daemon. A compromised build can escape the container or tamper with the host. Only flip this on for real Docker-in-Docker workloads and keep the buildspec under branch-protected review.

Recommended action

Disable privileged mode unless the project explicitly requires Docker-in-Docker builds. If required, ensure the buildspec is tightly controlled, peer-reviewed, and sourced from a trusted repository with branch protection.

CB-003: Build logging not enabled

MEDIUM CICD-SEC-10 CWE-778

A CodeBuild project with neither CloudWatch Logs nor S3 logging enabled leaves no durable record of what the build did. The CodeBuild console shows the last execution's logs for a short retention window, but anything older, and any automated review of historical activity during incident response, is gone.

Recommended action

Enable CloudWatch Logs or S3 logging in the CodeBuild project configuration to maintain a durable audit trail of all build activity.

CB-004: Build timeout missing or at the AWS maximum (480 min)

LOW CICD-SEC-7 CWE-400

A CodeBuild project at AWS's 480-minute maximum is rarely deliberate. Without a tighter ceiling, a runaway test loop, a fork-PR cryptomining payload, or a build that hangs on stdin keeps the build host (and its IAM role) live for the full eight hours, racking up cost and extending the compromise window.

Recommended action

Set a build timeout appropriate for your expected build duration (typically 15–60 minutes) to limit the blast radius of a runaway or abused build.

CB-005: Outdated managed build image

MEDIUM CICD-SEC-7 CWE-1104

Only AWS-managed aws/codebuild/standard:N.0 images are version-checked. Custom or third-party images pass here, CB-009 handles the separate concern of tag vs digest pinning for custom images.

Known false-positive modes

  • One version behind the current aws/codebuild/standard is a hygiene warning, not a production issue, and defaults to MEDIUM confidence. The rule emits HIGH only when the project is two or more versions behind. Custom or third-party images are not version-checked here; CB-009 handles tag-vs-digest pinning for those.

Recommended action

Update the CodeBuild environment image to aws/codebuild/standard:7.0 or later to ensure the build environment receives the latest security patches.

CB-006: CodeBuild source auth uses long-lived token

HIGH CICD-SEC-6 CWE-798

OAUTH / PERSONAL_ACCESS_TOKEN / BASIC_AUTH source credentials are stored long-lived on the account and used by every CodeBuild project that points at the SCM provider. Rotating the upstream PAT requires manual re-credentialing here too. CodeConnections (CodeStar) is the AWS-managed alternative with token refresh and revocation.

Recommended action

Switch to an AWS CodeConnections (CodeStar) connection and reference it from the source configuration. Delete any stored source credentials of type OAUTH, PERSONAL_ACCESS_TOKEN, or BASIC_AUTH via delete_source_credentials.

CB-007: CodeBuild webhook has no filter group

MEDIUM CICD-SEC-1 CWE-284

A CodeBuild webhook with no filter groups fires on every push and every PR from any actor, including fork PRs from outside the org. Anyone able to open a PR triggers the build with whatever IAM authority the project's role carries. Filter groups (branch + actor + event type) are the gate.

Recommended action

Define filter groups restricting triggers to specific branches, actors, and event types.

CB-008: CodeBuild buildspec is inline (not sourced from a protected repo)

HIGH CICD-SEC-4 CWE-829

An inline buildspec (source.buildspec set to YAML text, or a S3 URL) bypasses the protections that cover your source code. A user with codebuild:UpdateProject can rewrite the build commands without touching the repository, no PR review, no branch protection, no audit of what changed. Store buildspec.yml in the repo instead.

Recommended action

Remove the inline buildspec and store buildspec.yml in the source repository under branch protection. Anyone with codebuild:UpdateProject can silently rewrite an inline buildspec; repository-sourced buildspecs inherit the repo's review and protection controls.

CB-009: CodeBuild image not pinned by digest

MEDIUM CICD-SEC-3 CWE-494 CWE-829

CodeBuild pulls the environment image on every build. A tag pointer can be moved by whoever controls the registry; a digest cannot. AWS-managed aws/codebuild/... images are exempt. Those are covered by CB-005 and are not part of the tag-mutation threat model.

Recommended action

Pin custom CodeBuild images by @sha256:<digest>. Tag-based references (:latest, :1.2.3) can be silently overwritten to point at a malicious layer that is pulled on the next build.

CB-010: CodeBuild webhook allows fork-PR builds without actor filtering

HIGH CICD-SEC-4 CWE-284

GitHub/Bitbucket webhook filter groups that fire on pull-request events will build forks by default. Because CodeBuild runs with the project's own IAM role (not the PR author's), a fork PR can execute arbitrary code with CI privileges and exfiltrate secrets. Restrict to known contributors with an ACTOR_ACCOUNT_ID pattern group.

Recommended action

Add an ACTOR_ACCOUNT_ID filter pattern to every webhook filter group that accepts PULL_REQUEST_CREATED / PULL_REQUEST_UPDATED / PULL_REQUEST_REOPENED, or remove those PR event types. Without actor filtering, any fork can trigger a build that runs with the project's service role.

CB-011: CodeBuild buildspec contains indicators of malicious activity

CRITICAL CICD-SEC-4 CICD-SEC-7 CWE-506 CWE-913

Scans the source.buildspec text on every CodeBuild project for concrete attack indicators: reverse shells, base64-decoded execution, miner binaries/pools, Discord/Telegram webhooks, credential-dump pipes, audit-erasure commands. CB-011 is CRITICAL by design, a true positive is evidence of compromise, not a hygiene improvement. Repo-sourced buildspecs (not inlined) return NOT APPLICABLE because the text isn't visible to the scanner; CB-008 already flags the inline form as a governance gap.

Known false-positive modes

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Recommended action

Treat as a potential compromise. Identify which principal or pipeline ran the CodeBuild project recently, rotate its service role's credentials, audit CloudTrail for outbound activity to the matched hosts, and, if an inline buildspec is in use (CB-008), enforce repo-sourced buildspecs under branch protection so the next malicious edit requires a PR.

CCM-001: CodeCommit repository has no approval rule template attached

HIGH CICD-SEC-1 CWE-284

Approval-rule templates are CodeCommit's analog of GitHub's branch-protection require-review. Without one associated, the repository accepts merges from any push-permitted principal, including the PR author themselves, without any second-pair-of-eyes gate.

Recommended action

Create a CodeCommit approval-rule template requiring at least one approval from a designated pool of reviewers and associate it with every repository. Without one, any PR author with push rights can self-approve and merge.

CCM-002: CodeCommit repository not encrypted with customer KMS CMK

MEDIUM CICD-SEC-9 CWE-311

Same shape as CA-001 / ECR-005 / S3 default encryption: the AWS-owned default key keeps the key policy under AWS, removing your ability to scope or audit Decrypt operations. Source code in the repo deserves the same key-policy + CloudTrail story you'd apply to artifacts in S3. Note: the CodeCommit API returns the resolved KMS key ARN in kmsKeyId; the check flags only the absent-key case because the ARN alone does not reliably identify whether the key is AWS-managed or customer-managed without a separate kms:DescribeKey call.

Recommended action

Recreate the repository with a kmsKeyId argument pointing at a customer-managed KMS key. CodeCommit encryption is set at creation and cannot be changed afterwards.

CCM-003: CodeCommit trigger targets SNS/Lambda in a different account

MEDIUM CICD-SEC-8 CWE-441

A repo trigger pointing at an SNS topic or Lambda in a different account fires under the receiving account's permissions on every push. Sometimes this is the intended shape (a centralized notifications account), but a cross-account fan-out from a compromised repo can drive actions in the receiving account that the source-account owner can't directly observe.

Recommended action

Move trigger targets into the same account as the repository or explicitly document the cross-account relationship. Cross-account triggers extend the blast radius of a repository compromise to whatever the target ARN can do.

CD-001: Automatic rollback on failure not enabled

MEDIUM CICD-SEC-1 CWE-754

Without autoRollbackConfiguration, a CodeDeploy deployment that fails leaves the failed revision live until an operator notices. The default is opt-in, not opt-out, deployments fail-open, not fail-back.

Recommended action

Enable autoRollbackConfiguration with at least the DEPLOYMENT_FAILURE event so CodeDeploy automatically reverts to the last successful revision when a deployment fails.

CD-002: AllAtOnce deployment config, no canary or rolling strategy

HIGH CICD-SEC-1 CWE-754

AllAtOnce shifts 100% of traffic to the new revision in one step. There's no gradient to halt on if a CloudWatch alarm trips mid-rollout, the bad revision is already serving every request. Canary / linear configs introduce the shift-then-watch shape that lets monitors catch a regression before it's universal.

Recommended action

Switch to a canary or linear deployment configuration (e.g. CodeDeployDefault.LambdaCanary10Percent5Minutes or a custom rolling config) so that defects are caught before they affect all instances or traffic.

CD-003: No CloudWatch alarm monitoring on deployment group

MEDIUM CICD-SEC-10 CWE-778

Alarm-based rollback is what lets a canary configuration actually stop a bad deploy mid-flight. Without alarms wired into alarmConfiguration, CodeDeploy's only signal that the deploy went wrong is the deployment-state machine itself, which doesn't notice an application-level regression. CD-002's canary work and this rule's alarm-based halt are paired.

Recommended action

Add CloudWatch alarms (e.g. error rate, 5xx count, latency p99) to the deployment group's alarmConfiguration. Enable automatic rollback on DEPLOYMENT_STOP_ON_ALARM to halt bad deployments.

CP-001: No approval action before deploy stages

HIGH CICD-SEC-1 CWE-284

A pipeline that goes Source -> Build -> Deploy with no Approval action means every commit on the source branch ships, with no human ack between code-merged and code-running-in-prod. The Manual approval action is the intentional pause point, combine with CP-005 for production-tagged stages specifically.

Recommended action

Add a Manual approval action to a stage that precedes every Deploy stage that targets a production or sensitive environment.

CP-002: Artifact store not encrypted with customer-managed KMS key

MEDIUM CICD-SEC-9 CWE-311

The pipeline's S3 artifact store holds intermediate build outputs handed between stages. Default SSE-S3 (AES256) encrypts at rest but uses an AWS-owned key whose policy you can't scope. A customer-managed CMK gives the same key-policy + CloudTrail Decrypt-event audit story you'd apply to Lambda code, Secrets Manager, or any other build output.

Recommended action

Configure a customer-managed AWS KMS key as the encryptionKey for each artifact store. This enables key rotation, fine-grained access policies, and CloudTrail auditing of decrypt operations.

CP-003: Source stage using polling instead of event-driven trigger

LOW CICD-SEC-4 CWE-400

PollForSourceChanges=true polls the source repo every minute or two. Beyond the API-quota and latency cost, polling produces a less-useful CloudTrail story than event-driven triggers. You see the poll calls, not the specific commit that started the pipeline. EventBridge / CodeCommit triggers tie each pipeline start to the originating event.

Known false-positive modes

  • PollForSourceChanges=true is the CFN default for CodeCommit sources, so legacy templates can carry the flag without an active design decision behind it. The rule is advisory (consider EventBridge / CodeStarSourceConnection) rather than a real risk; defaults to LOW confidence so CI gates default-filter it.

Recommended action

Set PollForSourceChanges=false and configure an Amazon EventBridge rule or CodeCommit trigger to start the pipeline on change. This reduces latency, API usage, and improves auditability.

CP-004: Legacy ThirdParty/GitHub source action (OAuth token)

HIGH CICD-SEC-6 CWE-798

The legacy ThirdParty/GitHub source-action provider stores a long-lived OAuth token in the pipeline's action configuration. The token has whatever scope the granting GitHub user has, never rotates, and isn't directly revocable from the AWS side. CodeConnections (formerly CodeStar Connections) replaces this with an AWS-managed connection that the GitHub user can revoke.

Recommended action

Migrate to owner=AWS, provider=CodeStarSourceConnection and reference a CodeConnections connection ARN.

CP-005: Production Deploy stage has no preceding ManualApproval

MEDIUM CICD-SEC-1 CWE-284

The complement to CP-001: this rule fires only on stages whose name contains prod / production / live. Even teams that intentionally skip approvals for dev / staging deploys usually want a human in the loop for a production-tagged target.

Recommended action

Add a Manual approval action immediately before any stage whose name contains prod / production / live. CP-001 covers the generic case; this rule specifically looks at production-tagged stages where the blast radius of an unreviewed deploy is largest.

CP-007: CodePipeline v2 PR trigger accepts all branches

HIGH CICD-SEC-4 CWE-284

V2 pipelines added native PR triggers; without a branches.includes filter, any PR, including fork PRs from outside the org, fires the pipeline. The build stage runs with whatever IAM authority the pipeline's role carries, which is the full attack surface a fork-PR compromise can reach.

Recommended action

On V2 pipelines, add an includes filter under the trigger's branches block (and optionally pullRequest.events) so only PRs targeting specific branches run. Without a filter, any fork-PR can execute the pipeline's build and deploy stages.

CT-001: No active CloudTrail trail in region

HIGH CICD-SEC-10 CWE-778

CloudTrail is the only AWS-native source of record for management-plane API calls. A region with no active trail blinds incident responders: a pipeline compromise is invisible once the in-memory CloudWatch buffer rolls over.

Recommended action

Create a CloudTrail trail that logs management events in this region and start logging. Without a trail, CodeBuild/CodePipeline/IAM API activity, including credential changes during a compromise, has no durable audit record.

CT-002: CloudTrail log-file validation disabled

MEDIUM CICD-SEC-10 CWE-354

CloudTrail logs are S3 objects. Without log-file validation, an attacker with s3:PutObject on the trail bucket can edit log files to remove evidence of their activity, and there's no digest to compare against. With validation on, every hour of logs is summarized in a signed digest file under CloudTrail-Digest/.

Recommended action

Set LogFileValidationEnabled=true on every CloudTrail trail. Log validation produces a signed digest file alongside each log object so tampering by an attacker who also has S3 write access can be detected after the fact.

CT-003: CloudTrail trail is not multi-region

MEDIUM CICD-SEC-10 CWE-778

An attacker who knows your CloudTrail trail is regional deliberately operates from a different region. Multi-region trails capture management events from every region into a single trail, closing the gap without you having to enumerate which regions you actually use.

Recommended action

Convert the trail to a multi-region trail. A single-region trail misses activity in every other region, an attacker aware of the scope can drive reconnaissance or persistence from an unlogged region.

CW-001: No CloudWatch alarm on CodeBuild FailedBuilds metric

LOW CICD-SEC-10 CWE-778

Failure-rate signals are how on-call learns about an unfamiliar build crashing in a loop, an attacker probing the build environment, or a CI quota being exhausted. CloudWatch captures the FailedBuilds metric automatically, the alarm is the missing fan-out.

Recommended action

Create a CloudWatch alarm on the AWS/CodeBuild namespace FailedBuilds metric (aggregated or per-project). Without one, repeated build failures during a compromise, or a runaway fork-PR build, won't reach on-call.

CWL-001: CodeBuild log group has no retention policy

LOW CICD-SEC-10 CWE-778

CloudWatch Logs created by CodeBuild default to Never Expire retention. Build logs frequently echo secrets accidentally (a set -x script, an env dump in an error trace), so unbounded retention extends the exposure window for every secret a build has ever leaked. A short-but-finite retention also caps cost.

Recommended action

Set a retention policy on every /aws/codebuild/* log group. The default is 'Never Expire', which both racks up storage cost and keeps logs indefinitely past any compliance window.

CWL-002: CodeBuild log group not KMS-encrypted

MEDIUM CICD-SEC-9 CWE-311

CloudWatch Logs default encryption is service-managed, fine for confidentiality, but no audit trail or scoping. Build logs are a frequent secret-leak vector (CWL-001's rationale extended), so the same key-policy + Decrypt-event story you'd apply to S3 / Lambda / Secrets Manager is warranted here too.

Recommended action

Associate a customer-managed KMS key with every /aws/codebuild/* log group via associate-kms-key. Logs often contain secret material accidentally echoed by builds; encrypting them with a CMK means the key policy controls who can read the logs, not just S3/CloudWatch IAM.

EB-001: No EventBridge rule for CodePipeline failure notifications

MEDIUM CICD-SEC-10 CWE-778

Pipeline failure events are emitted to EventBridge automatically; the missing piece is a rule that pipes them to somewhere a human reads (SNS, Slack, PagerDuty). Without it, failures only surface via the CodePipeline console, which no one watches.

Recommended action

Create an EventBridge rule matching detail-type: 'CodePipeline Pipeline Execution State Change' and state: FAILED, and point it at an SNS topic or chat webhook. Without it, pipeline failures during an incident (a compromise triggering rollback, for example) go unnoticed.

EB-002: EventBridge rule has a wildcard target ARN

HIGH CICD-SEC-8 CWE-441

Wildcard target ARNs (e.g. arn:aws:lambda:us-east-1:123456789012:function:*) match every resource that fits the prefix. This is rarely intentional, usually a copy-paste from a more permissive resource ARN, and means the rule fans out to a much larger set of consumers than the author meant.

Recommended action

Replace wildcard target ARNs with specific resource ARNs. EventBridge targets with * route events to any resource that matches the prefix, frequently triggering unintended Lambda invocations or SNS sends.

ECR-001: Image scanning on push not enabled

HIGH CICD-SEC-3 CWE-1104

scan-on-push runs a CVE check against the image's OS package layers at the moment it lands in ECR. Without it, an image with a known CVE deploys silently. The ECR basic scanner is free; ECR-007 covers the Inspector v2 enhanced scanner that adds language-ecosystem CVEs (npm, pip, gem).

Recommended action

Enable imageScanningConfiguration.scanOnPush on the repository. Consider also enabling Amazon Inspector continuous scanning for ongoing CVE detection against images already in the registry.

ECR-002: Image tags are mutable

HIGH CICD-SEC-9 CWE-494

Mutable tags mean :latest, :v1.0, and :stable can be re-pushed silently, the same tag points to different image content over time. Pinning by digest (sha256:...) in deployment manifests is the only durable reference; IMMUTABLE on the repo enforces the property registry-side so a forgotten digest reference doesn't drift.

Recommended action

Set imageTagMutability=IMMUTABLE on the repository. Reference images by digest (sha256:...) in deployment manifests for strongest immutability guarantees.

ECR-003: Repository policy allows public access

CRITICAL CICD-SEC-8 CWE-732

A wildcard-principal repo policy means anyone on the internet can pull images. Sometimes intentional (a publicly-distributed base image), but should be a deliberate exposure, typically via the ECR Public registry rather than a private repo with a public policy. The default for build-output images should never be public.

Recommended action

Remove wildcard principals from the repository policy. Grant access only to specific AWS account IDs or IAM principals that require it.

ECR-004: No lifecycle policy configured

LOW CICD-SEC-7 CWE-1104

Without a lifecycle policy, untagged images and old tagged images accumulate indefinitely. Stale images keep CVE attack surface available, anyone who can pull from the repo can pull the old, unpatched version even after a newer build has shipped. Lifecycle expiry is the housekeeper that closes that window.

Recommended action

Add a lifecycle policy that expires untagged images after a short period (e.g. 7 days) and limits the number of tagged images retained, reducing exposure to images with known CVEs.

ECR-005: Repository encrypted with AES256 rather than KMS CMK

MEDIUM CICD-SEC-9 CWE-311

Same shape as CP-002 / CWL-002 / CCM-002: AES256 (the AWS-managed default) gives confidentiality at rest but no key-policy or CloudTrail Decrypt-event story. Container images are arguably sensitive intellectual property, the same key-policy + audit shape as build outputs in S3 is warranted.

Recommended action

Set encryptionType=KMS with a customer-managed key ARN.

ECR-006: ECR pull-through cache rule uses an untrusted upstream

HIGH CICD-SEC-3 CWE-829

AWS supports pull-through cache for ECR Public, Quay, K8s, GitHub Container Registry, GitLab, and Docker Hub. A rule pointing at registry-1.docker.io without an authenticated credential silently caches whatever the public namespace resolves to.

Recommended action

Scope pull-through cache rules to AWS-trusted registries (ECR Public, Quay.io with authentication, or a vetted private registry). Avoid wildcard or unauthenticated upstreams, a malicious image there gets cached into your account registry on first pull.

ECR-007: Inspector v2 enhanced scanning disabled for ECR

MEDIUM CICD-SEC-3 CWE-1104

ECR-001's basic on-push scan covers OS-level packages, apt / yum / apk lineage. Most production CVE risk is in language ecosystems (npm, pip, gem, mvn) which the basic scanner ignores. Inspector v2 enhanced scanning closes that gap and runs continuously, so a CVE published two weeks after a build still surfaces against the deployed image.

Recommended action

Enable Amazon Inspector v2 for the ECR scan type on this account. Basic ECR scanning on-push only covers OS packages; Inspector v2 enhanced scanning adds language-ecosystem CVEs and runs continuously as new vulnerabilities are published.

IAM-001: CI/CD role has AdministratorAccess policy attached

CRITICAL CICD-SEC-2 CWE-269

A CI/CD service role with AdministratorAccess attached turns any pipeline compromise into account compromise. The classic anti-pattern: the role started narrow, the pipeline grew, someone attached AdministratorAccess to unblock a deploy, and it never came off.

Recommended action

Replace AdministratorAccess with least-privilege policies.

IAM-002: CI/CD role has wildcard Action in attached policy

HIGH CICD-SEC-2 CWE-269

Action: '*' on an attached policy is functionally equivalent to AdministratorAccess: the role can call every API in every service. The wildcard absorbs every new IAM action AWS adds, so the role's authority grows without any local change. Service-prefix wildcards like s3:* are caught by IAM-006, not this rule.

Recommended action

Replace wildcard actions with specific IAM actions.

IAM-003: CI/CD role has no permission boundary

MEDIUM CICD-SEC-2 CWE-732

A permissions boundary is the maximum-permission ceiling for a role. Without one, every future PR that attaches another inline / managed policy raises the role's effective authority indefinitely. With a boundary in place, the policy churn happens beneath a fixed cap that your security team owns separately.

Recommended action

Attach a permissions boundary defining max permissions.

IAM-004: CI/CD role can PassRole to any role

HIGH CICD-SEC-2 CWE-269

iam:PassRole with Resource: '*' lets the principal hand any role to any service. Combined with a service that runs your code (Lambda, ECS, CodeBuild, EC2 Instance Profiles), this is role-hop privilege escalation: launch an ephemeral resource configured with a higher-privileged role, run code under that identity, exfil. Scoping by ARN + iam:PassedToService removes the escalation path.

Recommended action

Restrict iam:PassRole to specific role ARNs and add an iam:PassedToService condition.

IAM-005: CI/CD role trust policy missing sts:ExternalId

HIGH CICD-SEC-2 CWE-441

A trust policy that lets an external AWS account assume the role without an sts:ExternalId condition is vulnerable to the confused-deputy pattern: a third-party SaaS configured with your role ARN can also be used by another customer of that SaaS to assume your role (if they know the ARN). sts:ExternalId ties the role to a specific tenancy.

Recommended action

Add a Condition requiring sts:ExternalId for external principals.

IAM-006: Sensitive actions granted with wildcard Resource

MEDIUM CICD-SEC-2 CWE-732

IAM-002 catches Action: "*". IAM-006 catches the more common "scoped action, unscoped resource" pattern on sensitive services (S3/KMS/SecretsManager/SSM/IAM/STS/DynamoDB/Lambda/EC2).

Recommended action

Scope the Resource element to specific ARNs (buckets, keys, secrets, roles).

IAM-007: IAM user has access key older than 90 days

HIGH CICD-SEC-6 CWE-798

Every user in the account is evaluated. CI/CD tooling that still uses IAM users (older Jenkins agents, GitHub Actions pre-OIDC, third-party schedulers) shows up here. The 90-day window matches the common compliance baseline; rotate sooner if the key is used from on-prem or an untrusted runner.

Recommended action

Rotate or delete IAM access keys older than 90 days. Long-lived static credentials are the #1 way compromised CI credentials get reused across environments, prefer short-lived STS tokens via OIDC federation or an assumed role.

IAM-008: OIDC-federated role trust policy missing audience or subject pin

HIGH CICD-SEC-2 CWE-284

IAM-005 already covers cross-account AWS principals. This rule targets the OIDC federation path specifically because the blast radius of a missed audience/subject pin is the entire identity provider's tenant base (e.g. all GitHub users, not just your org). For GitHub repo: subjects it also fires when the subject is present but wildcards the repo or ref segment, or trusts the pull_request context, since a fork PR then mints the role's token.

Recommended action

Every Allow statement that trusts a federated OIDC provider (token.actions.githubusercontent.com, GitLab, CircleCI, Terraform Cloud, etc.) must pin both the audience (...:aud = sts.amazonaws.com) and a specific subject (...:sub matching one repo AND ref, e.g. repo:myorg/myrepo:ref:refs/heads/main or ...:environment:production). An org wildcard (repo:myorg/*), a ref wildcard (repo:myorg/myrepo:*), or the pull_request context all let an untrusted workflow run (including a fork PR) assume the role.

KMS-001: KMS customer-managed key has rotation disabled

MEDIUM CICD-SEC-6 CWE-321

Annual rotation regenerates the underlying key material for the same CMK ARN. Existing ciphertexts can still be decrypted (KMS keeps old material around), but new encrypts use the new material, so a cryptographic exposure (side-channel, an accidental export, an old compromised offline backup) only protects ciphertexts from before the rotation.

Recommended action

Enable annual rotation on every customer-managed KMS key used for CI/CD artifact, log, and secret encryption. Unrotated CMKs keep the same key material indefinitely, so a single cryptographic exposure (side-channel, accidental export) is permanent.

KMS-002: KMS key policy grants wildcard KMS actions

HIGH CICD-SEC-2 CWE-732

kms:* on a key policy is administrative authority over the cipher boundary: CancelKeyDeletion, ScheduleKeyDeletion, ReEncrypt, UpdateKeyDescription, and the data-plane decrypt actions all collapse into one grant. A CI/CD principal almost never needs more than the data-plane subset (Decrypt / GenerateDataKey / Encrypt).

Recommended action

Replace kms:* grants with specific actions needed by the caller (e.g. kms:Decrypt, kms:GenerateDataKey). Key-policy wildcard grants let any holder of the principal re-key, schedule deletion, or export material at will.

LMB-001: Lambda function has no code-signing config

HIGH CICD-SEC-9 CWE-347

Lambda code-signing config + a Signer profile (SIGN-001) validates that an uploaded zip was signed by a known profile before it's allowed to run. Without one, anyone who reaches lambda:UpdateFunctionCode, a CI/CD role compromise, a misattached IAM policy, can replace the function's code with no chain-of-custody check.

Recommended action

Create an AWS Signer profile, reference it from an aws_lambda_code_signing_config with untrusted_artifact_on_deployment = Enforce and attach that config to the function. Without one, the Lambda runtime will execute any code that a principal with lambda:UpdateFunctionCode uploads.

LMB-002: Lambda function URL has AuthType=NONE

HIGH CICD-SEC-8 CWE-306

A Lambda function URL with AuthType=NONE is a public HTTPS endpoint. Anyone who knows the URL can invoke. This is sometimes deliberate (a webhook receiver) but the deliberate version typically signs / validates inside the function, the rule fires regardless because the IAM-side control isn't there.

Recommended action

Set the function URL auth_type to AWS_IAM and grant lambda:InvokeFunctionUrl through IAM. NONE exposes the function to the public internet without authentication.

LMB-003: Lambda function env vars may contain plaintext secrets

HIGH CICD-SEC-6 CWE-798

Lambda env vars are world-readable to any principal with lambda:GetFunctionConfiguration, much wider than the principal that can invoke the function. They also persist in CloudFormation drift, change-sets, and CloudTrail events. A secret in a Lambda env var is essentially exposed to anyone with read access to the account.

Recommended action

Move secrets out of Lambda environment variables and into Secrets Manager or SSM Parameter Store. Environment variables are visible to anyone with lambda:GetFunctionConfiguration and persist in CloudTrail events, which keeps the secret in audit logs.

LMB-004: Lambda resource policy allows wildcard principal

CRITICAL CICD-SEC-8 CWE-732

A wildcard-principal Allow on a Lambda function resource policy lets anyone invoke. The legitimate case is a service principal (API Gateway, S3 events) where AWS fills in the SourceArn/SourceAccount at invoke time, without those conditions, any account using that service can invoke.

Recommended action

Remove Allow statements with Principal: '*' from every Lambda function resource policy, or scope them with a SourceArn / SourceAccount condition. Service principals (e.g. apigateway.amazonaws.com) are the common legitimate case, ensure they carry a condition.

PBAC-001: CodeBuild project has no VPC configuration

HIGH CICD-SEC-5 CWE-284

A CodeBuild project with no VPC configuration runs in AWS-managed network space, egress to the public internet is unrestricted, every package registry / CDN / arbitrary endpoint is reachable. Inside a VPC, security-group + VPC-endpoint policies become the egress gate, which is the only practical way to limit a compromised build's exfiltration paths.

Recommended action

Configure the CodeBuild project to run inside a VPC with appropriate subnets and security groups. Use a NAT gateway or VPC endpoints to control outbound internet access and restrict build nodes to only the network resources they require.

PBAC-002: CodeBuild service role shared across multiple projects

MEDIUM CICD-SEC-5 CWE-284

One CodeBuild service role across many projects means a compromise of any project's build environment grants access to whatever resources every other project's build needs. Per-project roles cap the radius, a backdoor in the foo-tests build can't reach the deploy-prod build's secrets if they each have their own role.

Recommended action

Create a dedicated IAM service role for each CodeBuild project, scoped to only the permissions that specific project requires. This limits the blast radius if one project's build is compromised.

PBAC-003: CodeBuild security group allows 0.0.0.0/0 all-port egress

MEDIUM CICD-SEC-5 CWE-862

A security-group egress rule of 0.0.0.0/0 on all ports/protocols means a compromised build can connect to any endpoint on the internet, typosquat-package registry, C2 server, attacker-owned dump endpoint. Even when the build is inside a VPC (PBAC-001), this egress rule negates the network-side gating.

Recommended action

Restrict CodeBuild security-group egress to the specific endpoints builds need (package registries, artifact repositories, STS). A wildcard egress rule lets a compromised build exfiltrate to anywhere on the internet.

PBAC-005: CodePipeline stage action roles mirror the pipeline role

HIGH CICD-SEC-5 CWE-862

When stage actions don't set their own roleArn, they fall back to the pipeline-level role, which is the union of every stage's needs. A compromise of any one stage (typically the build, which runs untrusted code) gains the deploy stage's authority, including production deploy credentials. Per-action roles cap the radius.

Recommended action

Give each stage action (Source, Build, Deploy) its own narrowly-scoped IAM role via roleArn on the action declaration. Sharing the pipeline-level role means a compromise of one action (e.g. a build) gains the permissions the deploy stage also needs.

S3-001: Artifact bucket public access block not fully enabled

CRITICAL CICD-SEC-9 CWE-732

S3 Block Public Access is the bucket-level circuit breaker that supersedes any future ACL or bucket-policy edit. Without all four settings enabled, a misconfigured CloudFormation change or a stray aws s3api call can re-expose the bucket to the public, even if the bucket had previously been private.

Recommended action

Enable all four S3 Block Public Access settings on the artifact bucket: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, and RestrictPublicBuckets.

S3-002: Artifact bucket server-side encryption not configured

HIGH CICD-SEC-9 CWE-311

Default bucket encryption applies SSE-S3 (AES256) to every PutObject. As of January 2023, AWS enables this on all new buckets automatically, but existing buckets created before then can still be unencrypted unless explicitly configured. Without it, individual objects can be uploaded without encryption (the client gets to choose).

Recommended action

Enable default bucket encryption using at minimum AES256 (SSE-S3). For stronger key control, use SSE-KMS with a customer-managed key.

S3-003: Artifact bucket versioning not enabled

MEDIUM CICD-SEC-9 CWE-494

Versioning makes overwrites and deletes recoverable: the previous content of an object survives until lifecycle expires it. Without versioning, an artifact overwrite (a bad pipeline run, a malicious replacement, a typo'd aws s3 cp) is unrecoverable, the original bytes are gone.

Recommended action

Enable S3 versioning on the artifact bucket so that previous artifact versions are retained and rollback is possible. Combine with a lifecycle rule to expire old versions after a retention period.

S3-004: Artifact bucket access logging not enabled

LOW CICD-SEC-10 CWE-778

S3 server access logging records every API operation against the bucket, who, when, what object, what method. CloudTrail data events overlap but cost more; access logs are the cheap baseline. Without them, an exfiltration via GetObject doesn't leave a trail you can investigate.

Recommended action

Enable S3 server access logging for the artifact bucket and direct logs to a separate, centralized logging bucket with restricted write access.

S3-005: Artifact bucket missing aws:SecureTransport deny

MEDIUM CICD-SEC-9 CWE-319

S3 endpoints accept HTTP and HTTPS by default. Without an explicit Deny on aws:SecureTransport=false, a plaintext request, typically from a misconfigured client or a SDK with a stale endpoint, is honored if signed. The bucket policy Deny is the only enforcement; no account-level switch covers it.

Recommended action

Add a Deny statement for s3:* with Bool aws:SecureTransport=false.

SIGN-001: No AWS Signer profile defined for Lambda deploys

MEDIUM CICD-SEC-9 CWE-347

AWS Signer profiles are the upstream of LMB-001's code-signing config. Without a profile defined, no function in the account can enforce code-signing, LMB-001's recommendation has nothing to point at. The profile is the foundation; the per-function code-signing config attaches it.

Recommended action

Create an AWS Signer profile with platform AWSLambda-SHA384-ECDSA and reference it from every Lambda code-signing config used by the pipeline. Without a profile, LMB-001 remediation isn't possible and release artifacts can't be signed at build time.

SIGN-002: AWS Signer profile is revoked or inactive

HIGH CICD-SEC-9 CWE-347

A revoked or canceled Signer profile invalidates every signature it ever produced. Lambda functions configured to enforce code-signing fail to deploy until the profile is replaced (or, if UntrustedArtifactOnDeployment = Warn, deploy with a CloudWatch warning the operator rarely reads).

Recommended action

Rotate the signing profile: create a replacement and update every code-signing config that references the revoked profile. A revoked or canceled profile invalidates every signature it produced, lambdas relying on it will fail verification.

SM-001: Secrets Manager secret has no rotation configured

HIGH CICD-SEC-6 CWE-798

Only secrets actually referenced by CodeBuild are checked, secrets used purely by application workloads are out of scope for a CI/CD scanner.

Recommended action

Enable automatic rotation on every Secrets Manager secret referenced by a CodeBuild project or CodePipeline. Unrotated secrets persist indefinitely, so a single leak (e.g. a build log that echoed the value) compromises the secret for its full lifetime.

SM-002: Secrets Manager resource policy allows wildcard principal

CRITICAL CICD-SEC-8 CWE-732

A wildcard-principal Allow on a Secrets Manager resource policy means any principal in any AWS account can call GetSecretValue (subject to conditions, if any). Always combine with at least aws:SourceAccount or aws:PrincipalOrgID, the lift-and-shift cross-account secret-access pattern needs scoping.

Recommended action

Remove Allow statements whose Principal is * from every Secrets Manager resource policy, or scope them with a Condition restricting the source account/org (aws:PrincipalOrgID). A wildcard-principal policy allows any AWS account to call GetSecretValue on the secret.

SSM-001: SSM Parameter with secret-like name is not a SecureString

HIGH CICD-SEC-6 CWE-312

An SSM String parameter is plaintext at rest and at API; ssm:GetParameter without any KMS Decrypt authority returns the value. SecureString adds KMS-encryption + the WithDecryption=true flag (which forces an explicit KMS authorization step). Secret-named parameters (TOKEN, PASSWORD, KEY) are almost always intended to be SecureString and rarely should not be.

Recommended action

Recreate the parameter with Type=SecureString and migrate consumers to the new name if needed. Plain String parameters are visible via ssm:GetParameter without any KMS authorization.

SSM-002: SSM SecureString uses the default AWS-managed key

MEDIUM CICD-SEC-9 CWE-311

alias/aws/ssm is the AWS-managed default for SecureString. Its key policy is fixed and account-wide. A customer-managed key gives you the same per-parameter key-policy + CloudTrail audit story you'd apply to Secrets Manager (which always uses a CMK).

Recommended action

Recreate SecureString parameters with KeyId pointing at a customer-managed KMS key. The default alias/aws/ssm key is shared across the account and its key policy cannot be audited or scoped per parameter.


Adding a new AWS check

  1. Drop a single module in pipeline_check/core/checks/aws/rules/<id>_<slug>.py exporting a RULE (metadata) and a check(catalog: ResourceCatalog) -> list[Finding] callable. The orchestrator (AWSRuleChecks) auto-discovers it and this doc's table picks it up on the next regen.
  2. If the check needs a new enumeration, add a cached method to ResourceCatalog in pipeline_check/core/checks/aws/_catalog.py so every dependent rule reads from the same in-memory snapshot.
  3. Add the check ID to pipeline_check/core/standards/data/owasp_cicd_top_10.py (and any other standard that applies).
  4. Add unit tests in tests/aws/rules/test_<name>.py using the make_catalog fixture.
  5. (Recommended) Add a Terraform parity rule in pipeline_check/core/checks/terraform/{extended,services,phase3}.py so shift-left scans stay at parity with the runtime provider.
  6. Regenerate this doc:
python scripts/gen_provider_docs.py aws