(Works / Security lab · DevSecOps)

Azure CI/CD Breach Simulation

Attacker + Defender, written side-by-side in Terraform and GitHub Actions

Year

2025

Role

DevSecOps + Red Team

Stack

Azure, Terraform, GitHub Actions

Angle

Attacker + Defender

High-level design

Two live Azure stacks - one deliberately vulnerable, one hardened. A malicious app exfiltrates CLIENT_SECRET through the insecure pipeline. Forensic evidence and an incident response playbook sit next to the fix.

OIDC Federation Terraform GitHub Actions Bandit Forensics Incident Response github.com/gocko1004/azure-ci-pipeline-breach-sim →

The infrastructure, the data flow,

and where each security control lives.

Numbered steps trace the hardened secure-deploy.yml flow, 1 through 7. R1 and R2 are the insecure-deploy.yml shortcuts. Full step names in the legend below.

Actor

Developer

commits to main

Attacker

reads pipeline logs

External - github.com

GitHub repo

source of truth

Actions runner

insecure + secure deploy.yml

continue-on-error PR approval

Azure Subscription - sub-prod / Switzerland North

Entra ID

identity + federation

OIDC

Managed Identity

short-lived tokens

RBAC

Resource Group rg-prod - VNet 10.0.0.0/16 / Subnet [NSG]

Key Vault

secrets store

RBAC Soft-delete

App Service

webapp runtime

TLS 1.3 NSG

Log Analytics

platform logs

SP secret visible

Storage Account

blob + table

Private EP

1 git push

2 trigger

3 OIDC

4 federated token

5 secret-get (RBAC)

6 inject secret

7 HTTPS

R1 static SP secret (insecure)

R2 stdout leak to logs

Red path - insecure-deploy.yml

Runner uses long-lived CLIENT_SECRET → app.py prints it to stdout → lands in Log Analytics → any reader with Log Analytics access reads the secret.

Green path - secure-deploy.yml

Runner requests OIDC federated token → Entra ID exchanges for Managed Identity → pulls secret from Key Vault over RBAC → App Service runs under Managed Identity. No static secret exists.

Same Azure subscription, same services. Two credential paths, one with a standing secret and one without.

Why it matters

Pipeline breaches are not exotic. Long-lived secrets in CI, silenced scanners, and unrestricted deploy paths are the attack surface most teams actually carry.

The point is not to prove Azure can be hardened. The point is the two stacks next to each other, so the cost of the shortcut is obvious.

What this shows about me

I read attack paths, not compliance checklists.

The insecure stack works. It is not a strawman.

I write Terraform and GitHub Actions to a review bar.

Input validation, OIDC, environment gates, compliance constraints.

I capture evidence.

Forensics folder + IR playbook alongside the fix, not a commit message.

I can walk a CISO or a CTO through this in ten minutes.

Both sides on one page, no jargon, with a demo to run.

Outcome

A working demonstration of a CI/CD breach and the exact Terraform + workflow changes that stop it. Evidence lives in the repo. The incident response doc reads like a real writeup, not a checklist.

Vulnerabilities demonstrated 20+

Stacks deployed 2 (insecure + secure)

Primary fix OIDC federation

Forensic artifacts Bandit JSON + screenshots

Docs shipped COMPARISON.md + IR

Verifiable Live Azure, not localhost

Create a free website with Framer, the website builder loved by startups, designers and agencies.