Last week I completed all 12 challenges of the Wiz Cloud Security Championship — a year-long CTF covering cloud misconfigurations, identity attacks, and infrastructure exploitation. The finale, Challenge 12 "Glass House", was the most architecturally interesting of the twelve. This post isn't a step-by-step walkthrough. It's a reflection on what the challenge revealed about how we design CI/CD trust boundaries — and where those designs quietly fail.
The Setup: Everything Is Visible
The challenge name was deliberate. The entire attack surface was in plain sight: an open GitLab repository, public build statuses, visible API endpoints, and a published research paper describing the exact vulnerability class. The Wiz Research team called it CodeBreach — a class of flaws where CI/CD systems make implicit identity assumptions that can be subverted by anyone who understands the underlying mechanics.
The specific flaw: an AWS CodeBuild webhook filter was configured to restrict builds to a trusted actor ID using a regex match. The regex was unanchored — instead of requiring an exact match, it checked whether the actor's ID contained the target string as a substring. A filter intended to allow only one specific identity ended up allowing any identity whose numeric ID happened to contain that sequence of digits.
The Architectural Problem
This is not primarily a coding mistake. It is an architectural trust boundary failure. The design assumed that identity validation at the pipeline trigger layer was sufficient to control what code gets executed in a privileged environment. But the validation logic had a precision gap — and that gap was wide enough to drive a full privilege escalation through.
From an architect's perspective, three things went wrong simultaneously:
1. The trust anchor was too shallow. The system trusted the actor ID as a proxy for authorization, without verifying that the actor had legitimate access to the target environment. Identity and authorization are not the same thing. Passing an identity check does not mean a principal should have access to a privileged SSM parameter or a production IAM role.
2. The blast radius was too large. The privileged build had access to an SSM SecureString containing a signing key. That key was reachable by any build that passed the actor filter. Least privilege at the SSM layer — scoping access to specific build project ARNs, or requiring additional context conditions — would have contained the impact even if the filter was bypassed.
3. Fork contributions were trusted like direct commits. The build system treated code from fork branches with the same level of trust as code from the main repository. Poisoned Pipeline Execution (PPE) exploits this gap: an attacker contributes to a fork, opens a merge request, and if the CI pipeline runs their code in a privileged context, the fork becomes a foothold into the build environment.
The Real-World Parallel
These patterns are not theoretical. Enterprise CI/CD environments — GitHub Actions, Jenkins, GitLab CI, CodeBuild — routinely run privileged workloads triggered by external contributions. The combination of fork-triggered pipelines, broad IAM roles attached to build runners, and identity-based filters that haven't been audited for precision creates exactly the conditions this challenge modelled.
The question worth asking about your own pipeline architecture: if an attacker could control the actor identity triggering a build, what would they be able to reach?
Defensive Architecture Takeaways
Anchor your regex filters. Any string match used for access control should be exact. ^17531$ is not equivalent to 17531. This applies to commit message filters, branch name filters, actor ID filters, and any other pattern-based gate in your pipeline configuration.
Scope IAM roles per pipeline. Each build project should have its own dedicated service role with access only to the secrets it legitimately needs. A shared "build role" with broad SSM or Secrets Manager access means any compromised pipeline can reach secrets intended for another.
Treat fork contributions as untrusted code. Pipelines triggered by fork merge requests should run in an isolated, unprivileged context. Privileged steps — deployments, secret access, signed artifact production — should require a human approval gate or be restricted to builds originating from protected branches in the main repository.
Audit your trust chain end-to-end. Map each privileged action in your CI/CD pipeline back to its trigger condition. For each one, ask: who can satisfy this condition, and could that set be larger than intended?
Result
I finished #28 globally with 12/12 challenges complete — one place above the solver whose public writeup I used as a reference during the final challenge. The championship ran for twelve months across twelve challenges covering the full cloud security stack: misconfigured storage, broken IAM, container escapes, Kubernetes privilege escalation, and CI/CD exploitation.
The Glass House challenge was well-named. Everything was visible. The lesson it reinforced is that visibility alone does not imply security — the gaps that matter are in the assumptions we don't examine.