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.