Skip to content

CloudFormation provider

Scans a CloudFormation template (YAML or JSON), no live AWS credentials required. Short-form intrinsics (!Ref, !Sub, !GetAtt, !Join, !If, …) are normalized to their JSON-form equivalents at parse time so rules operate on one uniform structure.

Every AWS-mirrored check ID (CB-, CP-, CD-, ECR-, IAM-, PBAC-, S3-, CT-, CWL-, SM-, CA-, CCM-, LMB-, KMS-, SSM-, EB-, SIGN-, CW-) maps one-to-one to its AWS-provider counterpart. The semantics are identical, only the data source differs (template properties instead of boto3 list/describe). CF-* rules are CloudFormation-only and have no AWS-runtime analogue.

Producer workflow

pipeline_check --pipeline cloudformation --cfn-template path/to/template.yaml
# or point at a directory and every *.yml / *.yaml / *.json / *.template is scanned
pipeline_check --pipeline cloudformation --cfn-template infra/

All other flags (--output, --severity-threshold, --checks, --standard, …) behave the same as with the AWS provider.

Intrinsic handling

CFN values may be literals ("ap-southeast-2"), booleans, unresolved intrinsics ({"Ref": "Region"}, {"Fn::Sub": "..."}), or condition references. The scanner follows two conventions:

  1. Anything not provably safe is treated as a potential offender unless the rule explicitly skips unresolved values. For instance, is_true(value) returns True only for True or "true", so a template that hides a Ref behind a boolean-typed property is scored as if the flag were disabled.
  2. Statically-reducible intrinsics are reduced before matching. resolve_literal(value, parameters) in cloudformation/base.py evaluates {"Ref": "ParamName"} against the template's Parameters.<Name>.Default, {"Fn::Sub": "literal"} / {"Fn::Sub": "...${Var}..."} including the [template, {var-map}] form, and {"Fn::Join": [delim, [list]]} when every list item resolves. Rules that benefit (EB-002 target ARNs, CF-003 VPC IDs) call the resolver first and fall back to the old "skip unresolved" path only when the intrinsic references a pseudo-parameter (AWS::Region) or a runtime-dependent intrinsic (Fn::GetAtt, Fn::ImportValue, Fn::If).

Rule helpers (importable from cloudformation/base.py):

  • as_str(value) — literal-only accessor, returns "" for intrinsics.
  • resolve_literal(value, parameters) — tries to reduce every statically-resolvable intrinsic; returns None when it can't.
  • is_true(value) — strict boolean gate.
  • is_intrinsic(value) — predicate used by CF-002 to skip intrinsic dicts entirely when walking for hard-coded secrets.

This matches cfn-lint and cfn-nag conventions and keeps findings useful under the common case where templates are parameterized.

Resource-type coverage

Service IDs CloudFormation resources consumed
CodeBuild CB-001…011 AWS::CodeBuild::Project, AWS::CodeBuild::SourceCredential
CodePipeline CP-001…005, CP-007 AWS::CodePipeline::Pipeline
CodeDeploy CD-001…003 AWS::CodeDeploy::DeploymentGroup
ECR ECR-001…006 AWS::ECR::Repository, AWS::ECR::PullThroughCacheRule
IAM IAM-001…006, IAM-008 AWS::IAM::Role, AWS::IAM::Policy, AWS::IAM::ManagedPolicy
PBAC PBAC-001…003, PBAC-005 AWS::CodeBuild::Project, AWS::EC2::SecurityGroup
S3 S3-001…005 AWS::S3::Bucket (artifact buckets discovered from pipelines)
CloudTrail CT-001…003 AWS::CloudTrail::Trail
CloudWatch Logs CWL-001…002 AWS::Logs::LogGroup
Secrets Manager SM-001…002 AWS::SecretsManager::Secret, AWS::SecretsManager::RotationSchedule
CodeArtifact CA-001…004 AWS::CodeArtifact::Domain, AWS::CodeArtifact::Repository
CodeCommit CCM-002…003 AWS::CodeCommit::Repository (CCM-001 omitted, no equivalent CFN resource)
Lambda LMB-001…004 AWS::Lambda::Function, AWS::Lambda::Url, AWS::Lambda::Permission
KMS KMS-001…002 AWS::KMS::Key
SSM SSM-001…002 AWS::SSM::Parameter
EventBridge EB-001…002 AWS::Events::Rule (targets are inline)
Signer SIGN-001 AWS::Lambda::Function.CodeSigningConfigArn, AWS::Signer::SigningProfile
CloudWatch CW-001 AWS::CloudWatch::Alarm (namespace=AWS/CodeBuild, metric=FailedBuilds)
CFN-native CF-001…003 AWS::IAM::AccessKey, stateful data-store types, AWS::EC2::Subnet

Limitations

  • Template-level only. Resources provisioned outside the template (console, SDK, sister stacks) are not scanned — use --pipeline aws alongside for a live view.
  • Intrinsics are not evaluated beyond the statically-reducible forms above. A Fn::GetAtt, Fn::ImportValue, or Fn::If passes through as an opaque dict; rules that require a literal value silently skip the finding rather than guess at the resolved shape.
  • No cross-stack resolution. Fn::ImportValue references are preserved as-is; the exporting stack is not fetched.
  • Transform macros are ignored. AWS::Serverless-2016-10-31 (SAM) and custom transforms run server-side; the pre-transform resources are what the scanner sees.

What it covers

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

Check Title Severity Fix
CA-001 CodeArtifact domain not encrypted with customer KMS CMK 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 No build timeout configured 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-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
CF-001 Template declares AWS::IAM::AccessKey (long-lived credential) CRITICAL
CF-002 Stateful data-store resource carries a plaintext secret CRITICAL
CF-003 CodeBuild project's VPC contains a public subnet HIGH
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
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-008 OIDC-federated role trust policy missing audience or subject pin HIGH
KMS-001 Customer-managed symmetric KMS key has rotation disabled MEDIUM
KMS-002 KMS key policy grants kms:* to an IAM principal HIGH
LMB-001 Lambda function has no code-signing config HIGH
LMB-002 Lambda Function URL configured with AuthType = NONE HIGH
LMB-003 Lambda environment variables contain plaintext secrets HIGH
LMB-004 Lambda resource policy grants wildcard principal CRITICAL
PBAC-001 CodeBuild project has no VPC configuration HIGH
PBAC-002 CodeBuild service role shared across multiple projects MEDIUM
PBAC-003 Security group allows 0.0.0.0/0 all-port egress MEDIUM
PBAC-005 Pipeline action roles all equal the pipeline-level 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 active AWS Signer profile exists for the Lambda platform MEDIUM
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 stored as String, not SecureString HIGH
SSM-002 SecureString uses alias/aws/ssm rather than a customer CMK MEDIUM

CA-001: CodeArtifact domain not encrypted with customer KMS CMK

MEDIUM CICD-SEC-9 CWE-311

Reads AWS::CodeArtifact::Domain.Properties.EncryptionKey. An empty value means anyone with codeartifact:Read* can read packages — the encryption key isn't a separate authorization boundary.

Recommended action

Set EncryptionKey on every AWS::CodeArtifact::Domain to a customer-managed CMK ARN. The default AWS-owned key can't be rotated or scoped by IAM.

CA-002: CodeArtifact repository has a public external connection

HIGH CICD-SEC-3 CWE-829

Reads AWS::CodeArtifact::Repository.Properties.ExternalConnections. Any value beginning with public: (e.g. public:npmjs) fetches packages directly from the public ecosystem with no intermediate scrub.

Recommended action

Route every ExternalConnections entry through a private mirror that caches and vets public packages, or scope with upstream allow-lists. Direct public:npmjs / public:pypi is dependency-confusion fuel.

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

CRITICAL CICD-SEC-8 CWE-732

Parses AWS::CodeArtifact::Domain.Properties.PermissionsPolicyDocument. Fires on any Allow statement that names a wildcard principal — wildcard at the domain level grants access to every repo in the domain.

Recommended action

Remove Principal: "*" (or Principal.AWS: "*") from every Allow statement in PermissionsPolicyDocument. Name the specific accounts and add an aws:PrincipalOrgID condition.

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

HIGH CICD-SEC-2 CWE-269

Parses AWS::CodeArtifact::Repository.Properties.PermissionsPolicyDocument. Fires when an Allow statement pairs codeartifact:* (or *) with Resource: "*". That combination lets the principal publish, delete, and rewrite every package version in the repo.

Recommended action

Enumerate specific actions and resources instead of codeartifact:* with Resource: "*".

CB-001: Secrets in plaintext environment variables

CRITICAL CICD-SEC-6 CWE-798

Walks every AWS::CodeBuild::Project's Properties.Environment.EnvironmentVariables list. Flags any entry whose Type is PLAINTEXT (or absent — the CFN default) when (a) the Name matches a secret-like pattern (PASSWORD, TOKEN, API_KEY, …) or (b) the Value matches one of pipeline-check's known credential shapes (cloud access keys, VCS / registry / CI / cloud-service tokens, JWTs — the same shared detector catalog GHA-008 uses).

Recommended action

Move secrets to AWS Secrets Manager or SSM Parameter Store and reference them via Type: SECRETS_MANAGER or Type: PARAMETER_STORE on the corresponding Environment.EnvironmentVariables entry.

CB-002: Privileged mode enabled

HIGH CICD-SEC-7 CWE-250

Reads AWS::CodeBuild::Project.Properties.Environment.PrivilegedMode. Privileged mode grants the build container root-level access to the Docker daemon on the host.

Recommended action

Set Environment.PrivilegedMode: false (or omit it; the CFN default is false). Where Docker-in-Docker is unavoidable, consider Kaniko or BuildKit and keep the buildspec under branch protection.

CB-003: Build logging not enabled

MEDIUM CICD-SEC-10 CWE-778

Reads both LogsConfig.CloudWatchLogs.Status and LogsConfig.S3Logs.Status. Without either, the build's stdout/stderr is captured only in the in-flight console view — audit and incident review have no record.

Recommended action

Enable at least one of LogsConfig.CloudWatchLogs.Status: ENABLED or LogsConfig.S3Logs.Status: ENABLED. CloudWatch is the easier default; pair S3 with an object-lock bucket for tamper-evident retention.

CB-004: No build timeout configured

LOW CICD-SEC-7 CWE-400

Reads AWS::CodeBuild::Project.Properties.TimeoutInMinutes. Projects left at the AWS maximum of 480 minutes let a runaway or hijacked build consume compute and delay detection of a compromised pipeline stage.

Recommended action

Set TimeoutInMinutes to a value matched to your real build duration (15–60 minutes is typical). Pair with a CloudWatch alarm on AWS/CodeBuild BuildDuration.

CB-005: Outdated managed build image

MEDIUM CICD-SEC-7 CWE-1104

Matches Environment.Image against aws/codebuild/standard:<major>.<minor>. Older managed images carry unpatched OS packages, runtimes, and build tools — every artifact they produce inherits those gaps.

Recommended action

Update Environment.Image to the latest aws/codebuild/standard:<major>.0 release. For custom or third-party images, pin by @sha256:<digest> rather than a mutable tag.

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

HIGH CICD-SEC-6 CWE-798

Reads Source.Type and Source.Auth.Type plus any AWS::CodeBuild::SourceCredential.{ServerType,AuthType} side resource. Fires when an external VCS source (GITHUB, GITHUB_ENTERPRISE, BITBUCKET) is authenticated with a long-lived credential.

Recommended action

Replace OAUTH / PERSONAL_ACCESS_TOKEN / BASIC_AUTH with an AWS CodeConnections (CodeStar) connection. Tokens stored via AWS::CodeBuild::SourceCredential or inline Source.Auth don't rotate.

CB-007: CodeBuild webhook has no filter_group

MEDIUM CICD-SEC-1 CWE-732

Unlike Terraform (where webhooks are a separate resource), CFN models the webhook as a property of AWS::CodeBuild::Project.Triggers. Reads Triggers.{Webhook,FilterGroups} and fires when a webhook is enabled with no filter groups.

Recommended action

Define Triggers.FilterGroups entries that restrict triggers to specific branches, actors, and event types. At minimum include an ACTOR_ACCOUNT_ID filter to keep fork PRs from triggering builds.

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

HIGH CICD-SEC-4 CWE-1357

Inspects AWS::CodeBuild::Project.Properties.Source.BuildSpec. Flags multi-line literal values or values that begin with YAML preamble (version:, phases:) — those indicate an inline spec that any principal with codebuild:UpdateProject can rewrite without going through code review.

Recommended action

Move buildspec content into a buildspec.yml inside the source repository, under branch protection. Reference it from Source.BuildSpec only by relative path.

CB-009: CodeBuild image not pinned by digest

MEDIUM CICD-SEC-3 CWE-1357

Classifies Environment.Image using the same shared image classifier the workflow providers use. Mutable tags let an upstream image swap execute on the next build with no template change.

Recommended action

Pin Environment.Image by @sha256:<digest> rather than a mutable tag. AWS-managed aws/codebuild/standard:N images are exempted.

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

HIGH CICD-SEC-4 CWE-863

Reads Triggers.FilterGroups[*]. For each group that covers a PULL_REQUEST_* event, fires when no sibling ACTOR_ACCOUNT_ID filter constrains the PR author.

Recommended action

Add an ACTOR_ACCOUNT_ID filter to every Triggers.FilterGroups entry whose EVENT filter covers a PULL_REQUEST_* event. Without it, a fork-PR build runs with the project's service role.

CB-011: CodeBuild buildspec contains indicators of malicious activity

CRITICAL CICD-SEC-4 CWE-506

Runs the shared buildspec-IOC matcher against any inline Source.BuildSpec. The matcher looks for reverse-shell payloads, miner CLIs, secret-exfil patterns, and credential-grabbing one-liners.

Recommended action

Treat any hit on this rule as a potential pipeline compromise. Identify the commit that introduced the buildspec, rotate every credential reachable by the project's service role, and move the buildspec to a repo-sourced file under branch protection (see CB-008).

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

MEDIUM CICD-SEC-9 CWE-311

Reads AWS::CodeCommit::Repository.Properties.KmsKeyId. Empty values fall back to AWS-owned encryption, which can't be audited or scoped to a specific role via key policy.

Recommended action

Set KmsKeyId on every AWS::CodeCommit::Repository to a customer-managed CMK ARN. Source code carries IP, credentials, and customer data — the encryption boundary matters.

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

MEDIUM CICD-SEC-8 CWE-942

Compares Triggers[*].DestinationArn against the account id of the current stack (extracted from sibling resource ARNs when possible). A trigger whose destination lives in another account leaks repository activity outside the trust boundary.

Recommended action

Point Triggers[*].DestinationArn at an SNS topic or Lambda function in the same account. If cross-account is intentional, document the receiving account in your threat model and baseline this finding.

CD-001: Automatic rollback on failure not enabled

MEDIUM CICD-SEC-1 CWE-754

Reads AWS::CodeDeploy::DeploymentGroup.Properties.AutoRollbackConfiguration. The block needs Enabled: true AND "DEPLOYMENT_FAILURE" present in Events for the deployment group to self-heal.

Recommended action

Enable AutoRollbackConfiguration with at least the DEPLOYMENT_FAILURE event so a failed release returns the environment to its prior state without manual intervention.

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

HIGH CICD-SEC-1 CWE-754

Reads AWS::CodeDeploy::DeploymentGroup.Properties.DeploymentConfigName. Fires when the value is CodeDeployDefault.AllAtOnce, LambdaAllAtOnce, or ECSAllAtOnce — these route every request to the new revision simultaneously.

Recommended action

Switch DeploymentConfigName to a canary or linear config (e.g. CodeDeployDefault.LambdaCanary10Percent5Minutes). A staged rollout gives alarm-based rollback a window to catch regressions before they hit 100% of traffic.

CD-003: No CloudWatch alarm monitoring on deployment group

MEDIUM CICD-SEC-10 CWE-778

Reads AWS::CodeDeploy::DeploymentGroup.Properties.AlarmConfiguration.{Enabled,Alarms}. Without an alarm list, error spikes or latency regressions from a release won't auto-halt the deployment.

Recommended action

Add CloudWatch alarms to AlarmConfiguration.Alarms and set AlarmConfiguration.Enabled: true. Pair with CD-001 — alarm-triggered rollback only fires when at least one alarm exists to monitor.

CF-001: Template declares AWS::IAM::AccessKey (long-lived credential)

CRITICAL CICD-SEC-6 CWE-798

Fires on every AWS::IAM::AccessKey in the template. CloudFormation writes the resulting SecretAccessKey to stack outputs — the secret is now in every stack update log and every DescribeStacks response.

Recommended action

Replace static keys with role-based access: an AWS::IAM::Role plus an AWS::IAM::OIDCProvider for CI, or an IAM role for service-to-service auth. Static keys live forever in stack outputs and any tool that ever read them.

CF-002: Stateful data-store resource carries a plaintext secret

CRITICAL CICD-SEC-6 CWE-312

Walks every string value of the stateful data-store resources (AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::Redshift::Cluster, AWS::ElastiCache::ReplicationGroup, AWS::DocDB::DBCluster, AWS::Neptune::DBCluster, AWS::OpenSearchService::Domain, AWS::MemoryDB::Cluster). Fires when a string leaf matches a credential shape OR when a secret-named attribute (*Password, *Token, …) carries a non-placeholder literal.

Recommended action

Move the secret into Secrets Manager (or SSM Parameter Store SecureString) and reference it via '{{resolve:secretsmanager:…}}' at deploy time. Never literal-string a credential into a stateful resource — the value lives in the template, the stack history, and any drift detection report.

CF-003: CodeBuild project's VPC contains a public subnet

HIGH CICD-SEC-7 CWE-1327

When AWS::CodeBuild::Project.Properties.VpcConfig.VpcId resolves to a concrete reference, walks every AWS::EC2::Subnet in the same VPC and fires if any has MapPublicIpOnLaunch: true.

Recommended action

Place CodeBuild projects in private subnets (MapPublicIpOnLaunch: false) with egress routed through a NAT gateway or VPC interface endpoints. Public subnets put the build host on a public IP for the duration of the build.

CP-001: No approval action before deploy stages

HIGH CICD-SEC-1 CWE-862

Walks AWS::CodePipeline::Pipeline.Stages[*].Actions[*].ActionTypeId.Category. Fires when any Deploy action is reachable from the source without an intervening Approval action upstream.

Recommended action

Add a Manual approval action to a stage that precedes every Deploy-category action. Pipelines that auto-promote to production trust every prior stage's findings absolutely.

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

MEDIUM CICD-SEC-9 CWE-311

Reads ArtifactStore.EncryptionKey (or ArtifactStores for cross-region pipelines). An empty value means the store falls back to AWS-owned-key S3 SSE; with a CMK you control key policy and rotation independently.

Recommended action

Set ArtifactStore.EncryptionKey (or every entry in ArtifactStores.EncryptionKey) to a customer-managed KMS CMK. Default S3 SSE is encrypted by an AWS-owned key you can't rotate or scope by IAM.

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

LOW CICD-SEC-4 CWE-1188

Reads Stages[*].Actions[*] where ActionTypeId.Category == "Source" and Configuration.PollForSourceChanges is the string "true".

Recommended action

Set Configuration.PollForSourceChanges: false on every Source action and create an EventBridge rule (or CodeStar connection) to drive change detection on commit.

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

HIGH CICD-SEC-6 CWE-798

Fires on Stages[*].Actions[*] whose ActionTypeId.Owner == "ThirdParty" AND ActionTypeId.Provider == "GitHub". The v1 GitHub action stores a long-lived OAuth token literally in the pipeline configuration.

Recommended action

Switch the source action to ActionTypeId.Owner: AWS + ActionTypeId.Provider: CodeStarSourceConnection and point Configuration.ConnectionArn at an AWS::CodeStarConnections::Connection.

CP-005: Production Deploy stage has no preceding ManualApproval

MEDIUM CICD-SEC-1 CWE-862

A stricter version of CP-001 scoped to production-named stages. Walks Stages[*].Name for prod / production / live substrings and requires a preceding Approval action.

Recommended action

Add a Manual approval action in the stage that precedes any stage whose name contains prod / production / live and contains a Deploy action.

CP-007: CodePipeline v2 PR trigger accepts all branches

HIGH CICD-SEC-4 CWE-863

Inspects v2 pipelines (PipelineType: V2) whose Triggers[*].GitConfiguration declares a PullRequest block without Branches.Includes.

Recommended action

Set Triggers[*].GitConfiguration.PullRequest[*].Branches.Includes to the specific branches the pipeline expects. An empty include list runs on every branch event, including fork-PR rebases.

CT-001: No active CloudTrail trail in region

HIGH CICD-SEC-10 CWE-778

Counts AWS::CloudTrail::Trail resources. Without a trail (declared here or out-of-band), management-plane activity has no durable audit record.

Recommended action

Declare at least one AWS::CloudTrail::Trail — typically a single IsMultiRegionTrail: true trail sending events to a write-protected S3 bucket. If trails are managed out-of-band, baseline this rule's INFO emission.

CT-002: CloudTrail log-file validation disabled

MEDIUM CICD-SEC-10 CWE-353

Reads AWS::CloudTrail::Trail.Properties.EnableLogFileValidation. Without it, an attacker with s3:PutObject on the trail's bucket can rewrite event records and there's no cryptographic record of the original.

Recommended action

Set EnableLogFileValidation: true on every AWS::CloudTrail::Trail. CloudTrail then writes hash digests S3 cannot tamper with, post-incident validation can detect log forgery.

CT-003: CloudTrail trail is not multi-region

MEDIUM CICD-SEC-10 CWE-778

Reads AWS::CloudTrail::Trail.Properties.IsMultiRegionTrail. Multi-region is the only configuration that guarantees you'll see CreateAccessKey in ap-south-1 from your us-east-1 trail.

Recommended action

Set IsMultiRegionTrail: true so a single trail captures activity from every region. A region-scoped trail misses anything an attacker does in another region.

CW-001: No CloudWatch alarm on CodeBuild FailedBuilds metric

LOW CICD-SEC-10 CWE-778

Gated check: fires only when the template declares AWS::CodeBuild::Project. Passes when at least one AWS::CloudWatch::Alarm is configured for Namespace: AWS/CodeBuild + MetricName: FailedBuilds.

Recommended action

Declare an AWS::CloudWatch::Alarm with Namespace: AWS/CodeBuild and MetricName: FailedBuilds and route it to an actionable destination (PagerDuty, Slack via Chatbot, SNS topic with a human responder).

CWL-001: CodeBuild log group has no retention policy

LOW CICD-SEC-10 CWE-1188

Filters AWS::Logs::LogGroup by LogGroupName prefix /aws/codebuild/ and reads RetentionInDays. Unbounded retention isn't free; it also makes incident response harder when there are years of irrelevant logs to grep.

Recommended action

Set RetentionInDays on every AWS::Logs::LogGroup whose name starts with /aws/codebuild/. 30 / 90 / 365 days are typical.

CWL-002: CodeBuild log group not KMS-encrypted

MEDIUM CICD-SEC-9 CWE-311

Reads AWS::Logs::LogGroup.Properties.KmsKeyId on log groups whose name starts with /aws/codebuild/. Without a CMK, logs are encrypted with an AWS-owned key, which can't be audited or scoped by IAM.

Recommended action

Set KmsKeyId on every AWS::Logs::LogGroup whose name starts with /aws/codebuild/ to a customer-managed CMK ARN. Build logs commonly carry secret fragments and environment dumps.

EB-001: No EventBridge rule for CodePipeline failure notifications

MEDIUM CICD-SEC-10 CWE-778

Looks for at least one AWS::Events::Rule whose EventPattern JSON matches aws.codepipeline Pipeline Execution State Change events filtered to FAILED.

Recommended action

Declare an AWS::Events::Rule whose EventPattern matches the CodePipeline Pipeline Execution State Change detail-type with detail.state: FAILED, and target it at the notification destination of your choice (SNS, Slack via Chatbot, PagerDuty).

EB-002: EventBridge rule has a wildcard target ARN

HIGH CICD-SEC-8 CWE-441

Reads AWS::Events::Rule.Properties.Targets[*].Arn. A literal * in the ARN is the offending shape — it makes the target opaque to any reviewer tracing event flow.

Recommended action

Pin AWS::Events::Rule.Targets[*].Arn to a specific function or queue ARN. Wildcards in target ARNs defeat the per-target audit trail and let any resource matching the pattern receive the event.

ECR-001: Image scanning on push not enabled

HIGH CICD-SEC-3 CWE-1104

Reads AWS::ECR::Repository.Properties.ImageScanningConfiguration.ScanOnPush. Without it, a freshly-pushed image goes straight into deployable storage with no known-CVE pass.

Recommended action

Set ImageScanningConfiguration.ScanOnPush: true on every AWS::ECR::Repository. For deeper coverage, also enable Inspector v2 enhanced scanning at the registry level.

ECR-002: Image tags are mutable

HIGH CICD-SEC-9 CWE-1357

Reads AWS::ECR::Repository.Properties.ImageTagMutability. Default is MUTABLE — anyone with ecr:PutImage on the repo can overwrite release tags consumed by production deployments.

Recommended action

Set ImageTagMutability: IMMUTABLE on every AWS::ECR::Repository. Immutable tags point at exactly one digest forever — an attacker can't swap :latest mid-deploy.

ECR-003: Repository policy allows public access

CRITICAL CICD-SEC-8 CWE-732

Parses AWS::ECR::Repository.Properties.RepositoryPolicyText (or the standalone resource if used). Flags any Allow statement that names a wildcard principal — wildcard there lets every AWS account in the world pull the image.

Recommended action

Drop any Statement with Effect: Allow plus Principal: "*" (or Principal.AWS: "*" / Principal.Service: "*"). Use specific account IDs.

ECR-004: No lifecycle policy configured

LOW CICD-SEC-7 CWE-400

Reads AWS::ECR::Repository.Properties.LifecyclePolicy. Without one, images and untagged digests accumulate indefinitely — old vulnerable images stay deployable.

Recommended action

Configure LifecyclePolicy.LifecyclePolicyText with rules that expire untagged and old tagged images. Bounded image age and bounded image count are reasonable starting points.

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

MEDIUM CICD-SEC-9 CWE-311

Reads AWS::ECR::Repository.Properties.EncryptionConfiguration.{EncryptionType,KmsKey}. The AES256 default uses an AWS-owned key — you can't audit who used it or revoke access with a key policy.

Recommended action

Set EncryptionConfiguration.EncryptionType: KMS and EncryptionConfiguration.KmsKey: <CMK ARN> referencing a customer-managed CMK with a key policy scoped to the principals that legitimately pull.

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

HIGH CICD-SEC-3 CWE-829

Reads AWS::ECR::PullThroughCacheRule.Properties.{UpstreamRegistryUrl,CredentialArn}. Fires when the upstream is not on the trusted allow-list AND no credential ARN is configured.

Recommended action

Either scope UpstreamRegistryUrl to a trusted registry (public.ecr.aws, registry.k8s.io, ghcr.io, gcr.io) or set CredentialArn so the upstream authenticates the pull.

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

CRITICAL CICD-SEC-2 CWE-269

Considers a role CI/CD-scoped when its AssumeRolePolicyDocument trusts codebuild.amazonaws.com, codepipeline.amazonaws.com, or codedeploy.amazonaws.com. Reads ManagedPolicyArns literal entries; fires when arn:aws:iam::aws:policy/AdministratorAccess appears.

Recommended action

Replace AdministratorAccess with least-privilege policies that grant only the specific actions and resources the build needs. Pair with IAM-003 (permissions boundary) so a future policy edit can't quietly re-broaden the role.

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

HIGH CICD-SEC-2 CWE-269

Walks every policy document attached to a CI/CD role: inline Role.Policies plus the resolved AWS::IAM::ManagedPolicy referenced via ManagedPolicyArns: { Ref: … }. Fires when any Allow statement names "*" in Action.

Recommended action

Enumerate the specific IAM actions the role needs and drop Action: "*" entirely. Access Analyzer or CloudTrail-based policy generation can suggest a minimum set.

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

MEDIUM CICD-SEC-2 CWE-732

Reads AWS::IAM::Role.Properties.PermissionsBoundary. Without a boundary, every additive policy attached to the role takes immediate effect — there's no second layer constraining the maximum reach.

Recommended action

Set PermissionsBoundary on every CI/CD role to a managed policy ARN (or { Ref: <ManagedPolicy> }). Boundaries cap effective permissions even if an admin later attaches a broader policy.

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

HIGH CICD-SEC-2 CWE-269

Inspects every policy reachable from a CI/CD role. Fires on any Allow statement granting iam:PassRole (or iam:* / *) with Resource: "*". PassRole on a wildcard resource is the canonical AWS privilege-escalation primitive.

Recommended action

Scope iam:PassRole to the specific role ARNs the pipeline must hand off to. Add an iam:PassedToService condition so the role can only be passed to the service that actually consumes it.

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

HIGH CICD-SEC-2 CWE-441

Parses AssumeRolePolicyDocument. Walks every Allow statement whose Principal.AWS is an external account, and fires when no Condition carries sts:ExternalId. Without it, the role is vulnerable to the confused-deputy pattern.

Recommended action

Add a Condition block with StringEquals.sts:ExternalId to every trust-policy statement that allows an external AWS account to assume the role. Generate a high-entropy ExternalId once and store it in the relying party's configuration.

IAM-006: Sensitive actions granted with wildcard Resource

MEDIUM CICD-SEC-2 CWE-732

Inspects every policy reachable from a CI/CD role. Fires on any Allow statement that pairs a sensitive service action (s3:*, kms:*, secretsmanager:*, ssm:*, iam:*, sts:*, dynamodb:*, lambda:*, ec2:*) with Resource: "*".

Recommended action

Scope Resource to specific ARNs (bucket ARNs, key ARNs, secret ARNs, role ARNs). Reserve Resource: "*" for actions that genuinely require it (ec2:Describe*, cloudwatch:DescribeAlarms).

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

HIGH CICD-SEC-2 CWE-287

Inspects every AWS::IAM::Role.Properties.AssumeRolePolicyDocument that carries an OIDC trust statement (provider URL like token.actions.githubusercontent.com). Fires when Condition omits the audience or subject claim, or when a GitHub repo: subject wildcards the repo or ref segment (repo:org/*, repo:org/repo:*) or trusts the pull_request context. Without a specific repo + ref pin, an untrusted workflow (including a fork PR) can assume the role.

Recommended action

Add Condition.StringEquals (or StringLike) entries pinning both <host>:aud and <host>:sub to specific values. For GitHub Actions: pin aud to sts.amazonaws.com and sub to repo:<org>/<repo>:ref:refs/heads/main (or the env / branch combination the role expects).

KMS-001: Customer-managed symmetric KMS key has rotation disabled

MEDIUM CICD-SEC-6 CWE-322

Reads AWS::KMS::Key.Properties.EnableKeyRotation on symmetric keys (KeySpec = SYMMETRIC_DEFAULT or absent). Asymmetric keys are skipped — KMS doesn't rotate them, key replacement is the only path.

Recommended action

Set EnableKeyRotation: true on every symmetric AWS::KMS::Key. KMS rotates the underlying key material once per year transparently, no downstream change is needed.

KMS-002: KMS key policy grants kms:* to an IAM principal

HIGH CICD-SEC-2 CWE-732

Parses AWS::KMS::Key.Properties.KeyPolicy. Fires on any Allow statement that pairs kms:* with a non-root IAM principal — that's the canonical key-compromise primitive.

Recommended action

Enumerate the specific KMS actions each principal needs (kms:Encrypt, kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey). Reserve kms:* for the root principal that owns the key.

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

HIGH CICD-SEC-9 CWE-345

Reads AWS::Lambda::Function.Properties.CodeSigningConfigArn. Without it, Lambda accepts any zip the deployer can upload — there's no cryptographic check that the artifact came from the expected pipeline.

Recommended action

Set CodeSigningConfigArn on every AWS::Lambda::Function to an AWS::Lambda::CodeSigningConfig whose allowed publishers list signing profiles your release pipeline uses.

LMB-002: Lambda Function URL configured with AuthType = NONE

HIGH CICD-SEC-8 CWE-862

Reads AWS::Lambda::Url.Properties.AuthType. The NONE setting exposes the function over a public HTTPS endpoint with no authentication.

Recommended action

Set AuthType: AWS_IAM on every AWS::Lambda::Url and grant invoke via explicit AWS::Lambda::Permission resources rather than leaving the URL public.

LMB-003: Lambda environment variables contain plaintext secrets

HIGH CICD-SEC-6 CWE-798

Walks AWS::Lambda::Function.Properties.Environment.Variables for (a) secret-like names (PASSWORD, TOKEN, API_KEY) and (b) credential-shaped values (AKIA…, ghp_…, xox*, JWTs).

Recommended action

Move secrets to Secrets Manager or SSM Parameter Store and read them at function init time. For static values that must live in the env, encrypt them at rest with a customer CMK via KmsKeyArn.

LMB-004: Lambda resource policy grants wildcard principal

CRITICAL CICD-SEC-8 CWE-732

Inspects every AWS::Lambda::Permission resource. Fires when Principal is "*" or any other wildcard form. A wildcard invoker exposes the function — and the role it executes with — to the whole internet.

Recommended action

Drop any AWS::Lambda::Permission with Principal: "*". Name the specific service principal or account that needs invoke, and scope further with SourceAccount / SourceArn conditions.

PBAC-001: CodeBuild project has no VPC configuration

HIGH CICD-SEC-5 CWE-1327

Reads AWS::CodeBuild::Project.Properties.VpcConfig.{VpcId,Subnets,SecurityGroupIds}. All three must be set. Without VPC config, build nodes run in AWS-managed infrastructure with unrestricted outbound internet.

Recommended action

Set VpcConfig.VpcId, VpcConfig.Subnets, and VpcConfig.SecurityGroupIds on every AWS::CodeBuild::Project. Use private subnets with egress scoped to the package mirrors and AWS endpoints the build actually needs.

PBAC-002: CodeBuild service role shared across multiple projects

MEDIUM CICD-SEC-5 CWE-269

Counts AWS::CodeBuild::Project.ServiceRole collisions (Ref / Fn::GetAtt references are resolved to the target logical id so identical-target references coalesce). When two or more projects point at the same role, a build compromise in any one inherits the others' permissions.

Recommended action

Create one AWS::IAM::Role per AWS::CodeBuild::Project and reference it via ServiceRole. Per-project roles cap the blast radius of a hijacked build.

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

MEDIUM CICD-SEC-5 CWE-1327

Walks AWS::EC2::SecurityGroup.Properties.SecurityGroupEgress for every AWS::EC2::SecurityGroup in the template (not only CodeBuild-attached ones). Fires on any rule that allows 0.0.0.0/0 (or ::/0) on the full port range, a completely open exfiltration channel.

Recommended action

Scope egress to the specific destinations the build needs. Drop the catch-all SecurityGroupEgress: { CidrIp: 0.0.0.0/0, IpProtocol: -1 }.

PBAC-005: Pipeline action roles all equal the pipeline-level role

HIGH CICD-SEC-5 CWE-250

Compares each Stages[*].Actions[*].RoleArn against the pipeline's top-level RoleArn. When all action-level values are empty or identical to the pipeline role, every stage runs with the same blast-radius.

Recommended action

Assign a least-privilege RoleArn to every Stages[*].Actions[*] that needs cross-account or cross-service permissions, instead of falling back to the pipeline's top-level RoleArn.

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

CRITICAL CICD-SEC-9 CWE-732

Discovers pipeline artifact buckets via ArtifactStore.Location / ArtifactStores[*].Location and reads AWS::S3::Bucket.Properties.PublicAccessBlockConfiguration. Any of the four PAB flags left false (or missing) lets an ACL or bucket policy expose build artifacts.

Recommended action

Set PublicAccessBlockConfiguration.{BlockPublicAcls,IgnorePublicAcls,BlockPublicPolicy,RestrictPublicBuckets} all to true on every artifact bucket.

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

HIGH CICD-SEC-9 CWE-311

Reads AWS::S3::Bucket.Properties.BucketEncryption.ServerSideEncryptionConfiguration[0].ServerSideEncryptionByDefault.SSEAlgorithm.

Recommended action

Configure BucketEncryption.ServerSideEncryptionConfiguration with ServerSideEncryptionByDefault.SSEAlgorithm: aws:kms and KMSMasterKeyID set to a customer-managed CMK.

S3-003: Artifact bucket versioning not enabled

MEDIUM CICD-SEC-9 CWE-353

Reads AWS::S3::Bucket.Properties.VersioningConfiguration.Status — must be Enabled.

Recommended action

Set VersioningConfiguration.Status: Enabled on every artifact bucket. Versioning lets you recover from accidental or malicious overwrites without restoring from external backups.

S3-004: Artifact bucket access logging not enabled

LOW CICD-SEC-10 CWE-778

Reads AWS::S3::Bucket.Properties.LoggingConfiguration.DestinationBucketName.

Recommended action

Set LoggingConfiguration.DestinationBucketName to a central, write-protected logging bucket. Access logs are what forensics use to reconstruct who pulled which artifact during an incident.

S3-005: Artifact bucket missing aws:SecureTransport deny

MEDIUM CICD-SEC-9 CWE-319

Looks for an AWS::S3::BucketPolicy joined to the artifact bucket by Bucket (literal name or { Ref: <BucketLogicalId> }). Parses the policy and scans for any Deny statement whose Condition matches aws:SecureTransport = false.

Recommended action

Attach an AWS::S3::BucketPolicy carrying a Deny statement on Action: "s3:*" when Bool aws:SecureTransport = false.

SIGN-001: No active AWS Signer profile exists for the Lambda platform

MEDIUM CICD-SEC-9 CWE-345

Gated check: fires only when an AWS::Lambda::Function references CodeSigningConfigArn. Passes when at least one AWS::Signer::SigningProfile with PlatformId containing AWSLambda exists in the template.

Recommended action

Declare an AWS::Signer::SigningProfile with PlatformId: AWSLambda-SHA384-ECDSA and reference it from an AWS::Lambda::CodeSigningConfig. Without one, Lambda code signing has no signer to validate against (see LMB-001).

SM-001: Secrets Manager secret has no rotation configured

HIGH CICD-SEC-6 CWE-262

Joins AWS::SecretsManager::RotationSchedule to AWS::SecretsManager::Secret by SecretId. Fires when a secret has no matching rotation resource — a static secret lives forever in any backup or snapshot taken since the leak.

Recommended action

Declare an AWS::SecretsManager::RotationSchedule that targets the secret via SecretId (literal ARN or { Ref: <SecretLogicalId> }), with HostedRotationLambda or a RotationLambdaARN plus RotationRules.AutomaticallyAfterDays.

SM-002: Secrets Manager resource policy allows wildcard principal

CRITICAL CICD-SEC-8 CWE-732

Parses AWS::SecretsManager::ResourcePolicy.Properties.ResourcePolicy. Fires on any Allow statement that names a wildcard principal — the secret content is readable by every AWS account in the world until the policy is fixed.

Recommended action

Remove Principal: "*" (or Principal.AWS: "*") from every Allow statement on AWS::SecretsManager::ResourcePolicy. If cross-account access is intentional, name the specific accounts and add an aws:PrincipalOrgID condition.

SSM-001: SSM parameter with secret-like name stored as String, not SecureString

HIGH CICD-SEC-6 CWE-312

Checks AWS::SSM::Parameter.Properties.Name against the standard secret-name regex. If the name matches and Type is String (the CFN-only default — SecureString is not creatable via CFN, see AWS docs), the value is in plaintext.

Recommended action

Set Type: SecureString on every AWS::SSM::Parameter whose name or value looks secret-like. SecureString parameters are encrypted with KMS and audited separately from plain GetParameter access.

SSM-002: SecureString uses alias/aws/ssm rather than a customer CMK

MEDIUM CICD-SEC-9 CWE-311

Reads AWS::SSM::Parameter.Properties.{Type,KeyId}. Fires on a SecureString whose KeyId is empty or set to alias/aws/ssm.

Recommended action

Set KeyId on every SecureString AWS::SSM::Parameter to a customer-managed KMS CMK ARN. Default alias/aws/ssm is an AWS-owned key that can't be scoped or rotated by your key policy.


Adding a new CloudFormation check

  1. Drop a single module at pipeline_check/core/checks/cloudformation/rules/<id>_<slug>.py exporting a RULE (metadata) and a check(ctx: CloudFormationContext) -> list[Finding] callable. The orchestrator (CloudFormationRuleChecks) auto-discovers it and this doc's table picks it up on the next regen.
  2. If the rule needs side resources (managed-policy dereferencing, artifact-bucket discovery, policy documents joined on Bucket / RoleName), add a private helper to pipeline_check/core/checks/cloudformation/rules/_<service>_context.py following the _iam_context.py / _s3_context.py pattern.
  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/cloudformation/test_<service>.py using make_cfn_ctx or one of the existing template fixtures.
  5. (Recommended) Add an AWS-runtime parity rule under pipeline_check/core/checks/aws/rules/ and a Terraform parity rule under pipeline_check/core/checks/terraform/rules/ so the three IaC entry points stay aligned.
  6. Regenerate this doc:
python scripts/gen_provider_docs.py cloudformation