fix: harden bitbucket-pipelines.yml with security and correctness improvements

This commit is contained in:
Siddhant Medar
2026-04-14 16:24:40 -05:00
parent fb1266ba33
commit f3a36eea1e
+46 -15
View File
@@ -18,6 +18,12 @@
# Only feature/*, bugfix/*, hotfix/*, dev, stg have PR pipelines defined. # Only feature/*, bugfix/*, hotfix/*, dev, stg have PR pipelines defined.
# Any other branch name simply gets NO PR pipeline → checks won't pass # Any other branch name simply gets NO PR pipeline → checks won't pass
# → PR cannot be merged. No catch-all '**' pattern (that caused double runs). # → PR cannot be merged. No catch-all '**' pattern (that caused double runs).
#
# SETUP REQUIRED: enforce these gates by enabling "Require a successful
# build to merge" for dev, stg, and main in:
# Bitbucket → Repository settings → Branch permissions
# Without this, the gates are advisory only — PRs can be merged regardless
# of whether the gate, lint, tests, or AI review pass or fail.
# ============================================================================= # =============================================================================
@@ -43,7 +49,7 @@ definitions:
# then syncs both dev and test dependency groups from pyproject.toml # then syncs both dev and test dependency groups from pyproject.toml
- script: &install - script: &install
pip install uv==0.11.5; pip install uv==0.11.5;
uv sync --group dev --group test; uv sync --frozen --group dev --group test;
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Reusable step definitions # Reusable step definitions
@@ -89,12 +95,17 @@ definitions:
# and posts review comments directly on the pull request. # and posts review comments directly on the pull request.
- step: &ai-code-review - step: &ai-code-review
name: AI Code Review name: AI Code Review
image: python:3.12-slim image: python:3.12.7-slim # slim is fine here — no uv sync
# SETUP REQUIRED: configure OIDC provider in
# Bitbucket → Workspace settings → Security → OpenID Connect
# and add the matching trust policy to the IAM role below.
# Without it, the step fails at startup before any script runs.
oidc: true # enables OIDC token injection oidc: true # enables OIDC token injection
script: script:
# Slim image doesn't include git — install it + AWS/HTTP libs # Slim image doesn't include git — install it + AWS/HTTP libs
- apt-get update && apt-get install -y git # --no-install-recommends skips git-man/less/openssh-client etc.
- pip install boto3 requests - apt-get update && apt-get install -y --no-install-recommends git
- pip install "boto3==1.35.*" "requests==2.32.*"
# --- AWS OIDC authentication setup --- # --- AWS OIDC authentication setup ---
# How it works: # How it works:
@@ -105,16 +116,30 @@ definitions:
- export AWS_REGION=us-east-1 - export AWS_REGION=us-east-1
- export AWS_ROLE_ARN="arn:aws:iam::975049960860:role/DoczyAI-Bitbucket-OIDC" - export AWS_ROLE_ARN="arn:aws:iam::975049960860:role/DoczyAI-Bitbucket-OIDC"
- export AWS_WEB_IDENTITY_TOKEN_FILE="$(pwd)/web-identity-token" - export AWS_WEB_IDENTITY_TOKEN_FILE="$(pwd)/web-identity-token"
- echo "$BITBUCKET_STEP_OIDC_TOKEN" > "$(pwd)/web-identity-token" - printf '%s' "$BITBUCKET_STEP_OIDC_TOKEN" > "$(pwd)/web-identity-token"
# ↑ Quoted to prevent word-splitting if token has special chars # printf (not echo) avoids appending a trailing newline to the JWT
# Clone the review agent repo (private — needs BITBUCKET_CLONE_TOKEN) # Clone the review agent repo (private — needs BITBUCKET_CLONE_TOKEN)
# SETUP REQUIRED: set BITBUCKET_CLONE_TOKEN (marked "Secured") in
# Bitbucket → Repository settings → Repository variables
# Guard against missing token so failure is self-diagnosing
- |
if [ -z "${BITBUCKET_CLONE_TOKEN}" ]; then
echo "BLOCKED: BITBUCKET_CLONE_TOKEN not set."
echo "Set it in Repository settings → Repository variables (Secured)."
exit 1
fi
# -q flag suppresses URL output to avoid leaking the token in logs # -q flag suppresses URL output to avoid leaking the token in logs
- git clone -q "https://x-token-auth:${BITBUCKET_CLONE_TOKEN}@bitbucket.org/${BITBUCKET_WORKSPACE}/code-review-agent.git" - git clone -q "https://x-token-auth:${BITBUCKET_CLONE_TOKEN}@bitbucket.org/${BITBUCKET_WORKSPACE}/code-review-agent.git"
# Build the PR URL and run the agent # Build the PR URL and run the agent.
# The || echo below only covers the python main.py call:
# agent crashes, AWS STS errors, and runtime exceptions become
# warnings instead of hard failures. A failure in git clone or
# apt-get above is still a hard failure (rare in practice).
# AI review is advisory, not a gate.
- export PR_URL="https://bitbucket.org/${BITBUCKET_WORKSPACE}/${BITBUCKET_REPO_SLUG}/pull-requests/${BITBUCKET_PR_ID}" - export PR_URL="https://bitbucket.org/${BITBUCKET_WORKSPACE}/${BITBUCKET_REPO_SLUG}/pull-requests/${BITBUCKET_PR_ID}"
- cd code-review-agent && python main.py "$PR_URL" - cd code-review-agent && python main.py "$PR_URL" || echo "WARNING: AI review step failed (non-blocking)"
# ============================================================================= # =============================================================================
@@ -139,7 +164,7 @@ pipelines:
# =========================================================================== # ===========================================================================
# Each entry matches a SOURCE branch pattern (the branch the PR comes # Each entry matches a SOURCE branch pattern (the branch the PR comes
# FROM, not the branch it targets). The gate step inside validates # FROM, not the branch it targets). The gate step inside validates
# the DESTINATION branch using ALLOWED_TARGETS. # the DESTINATION branch.
# #
# Flow for every PR: # Flow for every PR:
# Step 1: Gate → is this PR targeting the correct branch? # Step 1: Gate → is this PR targeting the correct branch?
@@ -356,7 +381,10 @@ pipelines:
patch=$((patch + 1)) patch=$((patch + 1))
fi fi
# Create the annotated tag and push it to the remote # Create the annotated tag and push it to the remote.
# SETUP REQUIRED: enable "Repository write access" in
# Bitbucket → Repository settings → Pipelines
# Without it, git push fails with 403 and releases don't work.
- | - |
new_tag="v${major}.${minor}.${patch}" new_tag="v${major}.${minor}.${patch}"
echo "Creating tag: ${new_tag}" echo "Creating tag: ${new_tag}"
@@ -375,7 +403,7 @@ pipelines:
image: atlassian/default-image:4 image: atlassian/default-image:4
script: script:
# Read the tag that was created in step 1 # Read the tag that was created in step 1
- export RELEASE_TAG=$(cat release_tag.txt) - export RELEASE_TAG="$(cat release_tag.txt)"
- echo "Deploying ${RELEASE_TAG}" - echo "Deploying ${RELEASE_TAG}"
# DEPLOY_COMMAND is a repository variable configured in # DEPLOY_COMMAND is a repository variable configured in
@@ -400,7 +428,7 @@ pipelines:
rollback-prod: rollback-prod:
- variables: - variables:
- name: ROLLBACK_TAG - name: ROLLBACK_TAG
default: v0.0.0 # user must change this to a real tag default: "" # user MUST provide a real tag (e.g. v1.2.0)
# Step 1: Validate the tag exists # Step 1: Validate the tag exists
- step: - step:
@@ -422,11 +450,14 @@ pipelines:
exit 1 exit 1
fi fi
# Verify the tag exists in the remote # Verify the ref exists AND is specifically a tag (not a branch,
# HEAD, or commit hash). git rev-parse would accept any ref;
# `git tag -l` + exact-match grep ensures we only accept tags.
- git fetch --tags --force - git fetch --tags --force
- | - |
if ! git rev-parse "${ROLLBACK_TAG}" >/dev/null 2>&1; then if ! git tag -l "${ROLLBACK_TAG}" | grep -qxF "${ROLLBACK_TAG}"; then
echo "BLOCKED: tag ${ROLLBACK_TAG} does not exist." echo "BLOCKED: ${ROLLBACK_TAG} is not a tag."
echo "Rollback requires a release tag (e.g. v1.2.0), not a branch/commit."
exit 1 exit 1
fi fi
- echo "Tag ${ROLLBACK_TAG} found — proceeding with rollback." - echo "Tag ${ROLLBACK_TAG} found — proceeding with rollback."