CodeBuild Project Uses User-Controlled Buildspec
Overview
This check identifies AWS CodeBuild projects that use buildspec files stored in the source repository instead of inline (centrally managed) build specifications. When a buildspec file lives in the repository, anyone with commit access can modify the build instructions--potentially without proper review.
Risk
If this check fails, your CI/CD pipeline could execute malicious code through unreviewed pull requests. Attackers can weaponize repository-controlled buildspecs to:
- Steal secrets: Access environment variables, AWS credentials, and other sensitive data
- Tamper with artifacts: Inject malicious code into your build outputs
- Pivot to other resources: Use the CodeBuild service role to access other AWS services
- Mine cryptocurrency: Abuse your compute resources for unauthorized workloads
Severity: Medium
Remediation Steps
Prerequisites
- Access to the AWS Console with permissions to modify CodeBuild projects, or
- AWS CLI installed and configured with appropriate credentials
Required IAM permissions
Your IAM user or role needs the following permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codebuild:UpdateProject",
"codebuild:BatchGetProjects"
],
"Resource": "*"
}
]
}
For production, scope the Resource to specific CodeBuild project ARNs.
AWS Console Method
- Open the AWS CodeBuild console
- Click on the project name identified in the Prowler finding
- Click Edit in the top right, then select Buildspec
- Under "Build specifications", select Insert build commands
- Click Switch to editor to enter your buildspec YAML
- Enter your build specification inline. At minimum:
version: 0.2
phases:
build:
commands:
- echo "Build started" - Click Update project
Repeat for each project flagged by Prowler.
AWS CLI method
Use the update-project command to set an inline buildspec:
aws codebuild update-project \
--name <your-project-name> \
--source '{
"type": "CODECOMMIT",
"location": "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/your-repo",
"buildspec": "version: 0.2\nphases:\n build:\n commands:\n - echo Build started"
}' \
--region us-east-1
Parameters:
--name: Your CodeBuild project name--source: The source configuration with inline buildspec
Important: You must specify all required source fields (type, location) when updating. The buildspec value should be the complete inline YAML as a single string with \n for newlines.
Example with a more complete buildspec:
aws codebuild update-project \
--name my-application-build \
--source '{
"type": "GITHUB",
"location": "https://github.com/myorg/myrepo.git",
"buildspec": "version: 0.2\nphases:\n install:\n runtime-versions:\n nodejs: 18\n build:\n commands:\n - npm ci\n - npm run build\nartifacts:\n files:\n - \"**/*\"\n base-directory: dist"
}' \
--region us-east-1
Getting current project configuration first:
Before updating, retrieve the current source settings to preserve other fields:
aws codebuild batch-get-projects \
--names <your-project-name> \
--query 'projects[0].source' \
--region us-east-1
CloudFormation
Update your AWS::CodeBuild::Project resource to use an inline buildspec instead of a file reference.
Before (vulnerable - uses file in repository):
Resources:
MyCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: my-application-build
Source:
Type: GITHUB
Location: https://github.com/myorg/myrepo.git
# No BuildSpec specified = uses buildspec.yml from repo
After (secure - inline buildspec):
Resources:
MyCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: my-application-build
Source:
Type: GITHUB
Location: https://github.com/myorg/myrepo.git
BuildSpec: |
version: 0.2
phases:
install:
runtime-versions:
nodejs: 18
build:
commands:
- npm ci
- npm run build
- npm test
artifacts:
files:
- '**/*'
base-directory: dist
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
Artifacts:
Type: S3
Location: !Ref ArtifactBucket
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
Complete example with all common settings:
AWSTemplateFormatVersion: '2010-09-09'
Description: CodeBuild project with inline buildspec (secure configuration)
Parameters:
GitHubRepoUrl:
Type: String
Description: HTTPS URL of the GitHub repository
Resources:
ArtifactBucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
Policies:
- PolicyName: CodeBuildLogs
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
SecureCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: secure-build-project
Description: Build project with centrally managed buildspec
Source:
Type: GITHUB
Location: !Ref GitHubRepoUrl
BuildSpec: |
version: 0.2
phases:
install:
runtime-versions:
nodejs: 18
pre_build:
commands:
- echo "Installing dependencies..."
- npm ci
build:
commands:
- echo "Building application..."
- npm run build
post_build:
commands:
- echo "Running tests..."
- npm test
artifacts:
files:
- '**/*'
base-directory: dist
cache:
paths:
- 'node_modules/**/*'
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
Artifacts:
Type: S3
Location: !Ref ArtifactBucket
Packaging: ZIP
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
PrivilegedMode: false
LogsConfig:
CloudWatchLogs:
Status: ENABLED
Outputs:
ProjectName:
Value: !Ref SecureCodeBuildProject
Description: Name of the CodeBuild project
Terraform
Update your aws_codebuild_project resource to use an inline buildspec.
Before (vulnerable - uses file in repository):
resource "aws_codebuild_project" "my_project" {
name = "my-application-build"
service_role = aws_iam_role.codebuild_role.arn
source {
type = "GITHUB"
location = "https://github.com/myorg/myrepo.git"
# No buildspec specified = uses buildspec.yml from repo
}
# ... other configuration
}
After (secure - inline buildspec):
resource "aws_codebuild_project" "my_project" {
name = "my-application-build"
service_role = aws_iam_role.codebuild_role.arn
source {
type = "GITHUB"
location = "https://github.com/myorg/myrepo.git"
buildspec = <<-BUILDSPEC
version: 0.2
phases:
install:
runtime-versions:
nodejs: 18
build:
commands:
- npm ci
- npm run build
- npm test
artifacts:
files:
- '**/*'
base-directory: dist
BUILDSPEC
}
artifacts {
type = "S3"
location = aws_s3_bucket.artifacts.bucket
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
privileged_mode = false
}
}
Complete module example:
variable "project_name" {
description = "Name of the CodeBuild project"
type = string
}
variable "github_repo_url" {
description = "HTTPS URL of the GitHub repository"
type = string
}
variable "buildspec_content" {
description = "Inline buildspec YAML content"
type = string
default = <<-BUILDSPEC
version: 0.2
phases:
build:
commands:
- echo "Build started"
BUILDSPEC
}
resource "aws_s3_bucket" "artifacts" {
bucket_prefix = "${var.project_name}-artifacts-"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_iam_role" "codebuild_role" {
name = "${var.project_name}-codebuild-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy" "codebuild_policy" {
name = "${var.project_name}-codebuild-policy"
role = aws_iam_role.codebuild_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject"
]
Resource = "${aws_s3_bucket.artifacts.arn}/*"
}
]
})
}
resource "aws_codebuild_project" "secure_project" {
name = var.project_name
description = "Secure CodeBuild project with inline buildspec"
service_role = aws_iam_role.codebuild_role.arn
source {
type = "GITHUB"
location = var.github_repo_url
buildspec = var.buildspec_content
}
artifacts {
type = "S3"
location = aws_s3_bucket.artifacts.bucket
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
privileged_mode = false
}
logs_config {
cloudwatch_logs {
status = "ENABLED"
}
}
tags = {
SecurityCompliance = "inline-buildspec"
}
}
output "project_name" {
value = aws_codebuild_project.secure_project.name
description = "Name of the CodeBuild project"
}
Verification
After making changes, verify the configuration is correct:
- In the AWS Console, navigate to your CodeBuild project and confirm the Buildspec section shows "Insert build commands" rather than "Use a buildspec file"
- Re-run the Prowler check to confirm remediation:
prowler aws --check codebuild_project_user_controlled_buildspec -r us-east-1
CLI verification
Use the batch-get-projects command to verify the buildspec configuration:
aws codebuild batch-get-projects \
--names <your-project-name> \
--query 'projects[0].source.buildspec' \
--region us-east-1
Expected result: The output should show your inline buildspec YAML content (starting with "version: 0.2"), NOT a file path like "buildspec.yml".
Check if buildspec references a file:
aws codebuild batch-get-projects \
--names <your-project-name> \
--query 'projects[0].source.buildspec' \
--output text \
--region us-east-1 | grep -E "\.ya?ml$"
If this returns any output, the project still uses a file-based buildspec.
Additional Resources
- AWS CodeBuild Build Specification Reference
- AWS CodeBuild Security Best Practices
- Poisoned Pipeline Execution (PPE) Attacks
- OWASP CI/CD Security Risks
Notes
-
Trade-off with developer experience: Inline buildspecs are more secure but require infrastructure changes to update build steps. Consider storing buildspecs in a separate, tightly controlled repository and referencing them via S3 instead.
-
S3-hosted buildspec alternative: You can store your buildspec in an S3 bucket with strict access controls instead of inline. Use the S3 ARN format:
arn:aws:s3:::my-secure-bucket/buildspec.yml. This provides central control while keeping buildspecs version-controlled separately. -
Pull request builds: If you use CodeBuild for pull request validation, inline buildspecs prevent contributors from modifying the build to skip tests or introduce malicious code.
-
Webhook filtering: Even with inline buildspecs, consider using webhook filters to control which events trigger builds.
-
No immediate disruption: Updating the buildspec configuration takes effect on the next build. Running builds are not affected.