✓ Human-authored analysis; AI used for formatting and proofreading.
Your CI pipeline probably runs SAST. It probably runs SCA. It might scan container images. It might check for secrets in commits. If you're thorough, it runs Checkov or tfsec against your Terraform plans.
All of these check the artifact. None of them check what the artifact deploys into.
A CI pipeline that passes every security gate can deploy perfectly secure code into a catastrophically misconfigured environment. The code has no vulnerabilities. The dependencies are patched. The container image is clean. The IAM role the pipeline uses to deploy has AdministratorAccess, trusts any GitHub repo without subject restrictions, and can reach every S3 bucket in the account.
That's not a vulnerability. No CVE will be assigned. No scanner will flag it. No SAST tool will find it. It's a configuration state. It's the root cause of 19% of documented cloud security breaches.
The gap shift-left created
Shift-left moved vulnerability detection earlier in the development lifecycle. That was the right idea. Catching a SQL injection in a pull request is cheaper than catching it in production. Catching a vulnerable dependency before merge is cheaper than patching it after deployment.
But shift-left moved one kind of detection left code vulnerabilities and left another kind entirely unshifted: the configuration of the deployment target. The CI pipeline knows everything about the artifact it's building. It knows nothing about the environment it's deploying into.
This creates a specific blind spot. The pipeline checks whether the Terraform plan creates a public S3 bucket (good). It does not check whether the IAM role executing the Terraform plan can create resources in any account in the organization (bad). The pipeline checks whether the Docker image has a known CVE (good). It does not check whether the ECS task role the container will assume has access to production databases (bad). The pipeline checks whether secrets are committed to the repository (good). It does not check whether the OIDC federation that authenticates the pipeline to AWS allows any repository in the GitHub organization or any repository on GitHub to assume the deployment role (bad).
The artifact is secure. The pipeline is not. The pipeline's misconfiguration is invisible to every tool in the pipeline.
Three breaches, one pattern
In the breaches.cloud incident database maintained by Chris Farris, three of sixteen documented cloud breaches share the same root cause: CI/CD systems with long-term credentials or misconfigured trust relationships that gave attackers a direct path to production data.
CommuteAir (2023): a Jenkins server with AWS access keys in the build workspace. An attacker accessed the Jenkins instance, found the keys, and used them to access S3 buckets containing the TSA No Fly List. The CI/CD system was the entry point. The access keys were the bridge. The data was the target.
Uber (2014 and 2016): AWS access keys committed to GitHub repositories. In 2014, the repository was public. In 2016, it was private but accessible to an attacker who compromised a developer's GitHub credentials. Both times, the keys in the repository provided direct access to production S3 data. The CI/CD artifact (the repository) contained the credential. The credential was the bridge.
Drizly (2020): an executive's GitHub account was compromised via credential stuffing. The attacker found AWS access keys in a private repository and used them to access customer data in RDS. The FTC ordered Drizly to destroy the data and implement a security program.
Three incidents, different companies and different years. The same pattern: a CI/CD system held credentials that provided direct access to production data, and no configuration check verified that the CI/CD system's access was appropriately scoped.
The five configuration states CI/CD security doesn't check
The gap isn't one missing check. It's five categories of configuration that exist at the boundary between the CI pipeline and the AWS environment, and that no tool in the pipeline evaluates.
1. OIDC trust without subject restrictions
GitHub Actions, GitLab CI, and Terraform Cloud all support OIDC federation for AWS authentication. Instead of storing long-term access keys, the CI system authenticates via a short-lived token that AWS verifies against the OIDC provider. This is the recommended approach. It eliminates stored credentials.
But the trust relationship has a scope property that most teams don't configure. An IAM role that trusts GitHub Actions OIDC must specify WHICH repositories and branches can assume it. Without a subject restriction, the trust policy says "I trust GitHub Actions" not "I trust my organization's repository on the main branch." Any GitHub repository, including repositories the attacker controls, can assume the role.
The hackingthe.cloud wiki documents this for GitHub, GitLab, and Terraform Cloud. AWS's own console creates the GitLab misconfiguration by default. The OIDC provider is configured correctly. The trust policy is configured incorrectly. Nothing in the CI pipeline checks the trust policy, because the trust policy is an AWS configuration, not a CI artifact.
2. Overpermissioned deployment roles
The IAM role a CI pipeline uses to deploy infrastructure determines what the pipeline can create, modify, and access. In practice, these roles frequently have AdministratorAccess or broadly scoped permissions, because restricting the deployment role requires knowing which AWS actions the deployment needs. That set changes with every Terraform change.
The result: the deployment role can do anything. If the CI pipeline is compromised through a dependency confusion attack, a malicious pull request, a compromised runner, or a stolen token — the attacker inherits the deployment role's permissions. An AdministratorAccess deployment role means a compromised pipeline is equivalent to compromised root.
The pipeline checks whether the Terraform plan is safe. It does not check whether its own role is safe. The role that executes the plan is invisible to the plan.
3. Cross-account deployment without boundaries
Organizations with multiple AWS accounts often grant CI/CD pipelines the ability to deploy across accounts. A pipeline in the tooling account assumes a role in the production account to deploy. The trust relationship allows this, the SCP permits it, and no guardrail limits which accounts the pipeline can reach.
If the tooling account is compromised, every account the pipeline can deploy to is compromised. The blast radius of the CI/CD system equals the number of accounts it can access. In organizations with hundreds of accounts and a centralized deployment pipeline, that blast radius is total.
No tool in the pipeline evaluates how many accounts the deployment role can reach. That's a cross-account IAM reachability question. It requires joining the trust policy on the target role with the permissions on the source role and the SCP chain between the two accounts. It's a graph traversal, not a per-resource check. CI/CD security tools don't do graph traversals.
4. No SCP constraining CI/CD actions
SCPs restrict what any principal in a member account can do, regardless of their IAM permissions. An SCP that denies iam:CreateUser prevents any pipeline from creating IAM users, even if the pipeline's role permits it. An SCP that denies kms:ImportKeyMaterial prevents a compromised pipeline from executing the BYOKM ransomware technique, even with full KMS access.
But SCPs are organizational configurations. They're set by the cloud platform team, not by the application team that owns the pipeline. The CI pipeline doesn't know whether SCPs exist, what they restrict, or whether its own actions are constrained by them. The platform team that manages SCPs doesn't know which actions the pipeline needs, because the pipeline is managed by the application team.
The gap is organizational. Two teams own different halves of the security boundary, and neither verifies the other's half.
5. Secrets in CI/CD that aren't in code
Secret scanning in CI pipelines catches credentials committed to source code. It doesn't catch credentials stored in CI environment variables, secret managers, or build system configurations. Jenkins credentials, GitHub Actions secrets, GitLab CI variables, CircleCI contexts. These are stored outside the repository and injected at runtime. They're invisible to repository-level secret scanning.
The CommuteAir breach happened because access keys were in the Jenkins workspace — not in the code repository, but in the build environment's configuration. Repository secret scanning wouldn't have found them. The keys existed in the CI system's configuration layer, which no CI security tool evaluates.
Why this gap persists
CI/CD security tools are designed to run inside the pipeline. They evaluate artifacts the pipeline produces: source code, dependency manifests, container images, infrastructure-as-code plans. They run with the pipeline's permissions, see the pipeline's inputs, and produce findings about the pipeline's outputs.
The configuration gap lives outside the pipeline. The IAM trust policy is an AWS configuration. The deployment role's permissions are an AWS configuration. The SCP is an AWS organization configuration. The cross-account trust is a relationship between two AWS accounts. None of these are artifacts the pipeline produces. They're the environment the pipeline operates in.
A tool that evaluates these configurations needs to see the AWS environment, not the CI artifacts. It needs to read trust policies, resolve OIDC provider configurations, check subject restrictions, evaluate deployment role permissions, trace cross-account reachability, and verify SCP coverage. That's a configuration verification task. The same task performed on any other AWS resource.
The CI/CD security gap is a gap in configuration verification at the CI/CD boundary. The tools that check the code are doing their job. The tools that should check the deployment target configuration either don't exist or aren't run.
Closing the gap
Five invariants, each verifiable from a configuration snapshot:
Every OIDC trust restricts the subject claim. For GitHub Actions: the trust policy contains a condition on token.actions.githubusercontent.com:sub with a specific repo:org/repo:ref:refs/heads/branch pattern. For GitLab: same pattern on gitlab.com:sub. For Terraform Cloud: same on app.terraform.io:sub. Any OIDC trust without a subject restriction is a finding regardless of what the pipeline deploys.
Deployment roles follow least privilege. The IAM role assumed by the CI pipeline has permissions scoped to the specific AWS actions the deployment requires — not AdministratorAccess, PowerUserAccess or a managed policy that covers more than the pipeline needs. This is the hardest invariant to specify because the required permissions change with every deployment change. But the upper bound is specifiable: no deployment role should have iam:*, organizations:*, or sts:AssumeRole without resource restrictions.
Cross-account deployment is bounded. The set of accounts a CI/CD pipeline can reach is explicitly defined and reviewed. A deployment role that can assume roles in 200 accounts should be a critical finding, regardless of what it deploys to any one of them.
SCPs constrain CI/CD actions. Dangerous actions — kms:ImportKeyMaterial, iam:CreateUser, organizations:LeaveOrganization are SCP-denied in accounts where CI/CD pipelines operate. The pipeline doesn't need to know about the SCP. The SCP needs to exist.
CI/CD credentials are session-based, not stored. OIDC federation, not access keys. Session tokens, not long-term credentials. No access keys in environment variables, build configurations, or workspace files. This is the invariant CommuteAir, Uber, and Drizly violated. It's verifiable from a snapshot that shows IAM users with access keys whose names or paths match CI/CD patterns.
None of these invariants require running inside the pipeline. They require evaluating the AWS configuration that the pipeline connects to. That's configuration verification. The same discipline applied to any other AWS resource.
The pipeline doesn't need another scanner. The environment needs a verifier.
The CI/CD security tooling market is mature and effective at its job. SAST finds code vulnerabilities. SCA finds dependency vulnerabilities. Container scanning finds image vulnerabilities. Secret scanning finds committed credentials. IaC scanning finds misconfigured plans.
The gap that caused 19% of documented cloud breaches isn't in any of these tools. It's in the space between the pipeline and the environment — the trust policies, the deployment roles, the cross-account relationships, the missing SCPs. That space isn't checked by adding another scanner to the pipeline. It's checked by verifying the AWS configuration the pipeline connects to.
Your CI pipeline has a security gap. It isn't a vulnerability. It's a configuration.
Top comments (0)