In the previous nine sessions Patrick Steger and I built a GitHub DevSecOps pipeline with build, SCA, License Compliance, SAST, Container Scanning, Secret Detection and DAST. All useful — but only if it actually runs before code lands in main, and only if the merge is blocked when something serious shows up. In Part 10 we wire that gate together with Pull Requests and Branch Protection rules.
Pull Requests Are a Discussion, Not a Submit Button#
To understand a Pull Request you first have to understand a branch. The main branch holds the project. When I want to make a change, I create a feature branch, commit on it, and when I am ready I open a Pull Request to pull those commits back into main.
Patrick asked the obvious question: do I only open the PR when I am completely done? Absolutely not. The whole point of a modern Pull Request is the discussion. Open it early, discuss with the team, push more commits as the conversation evolves. The “pull” in the name is historical. Today the PR is where reviews happen, where workflows report results, and where the eventual merge decision is made.
When everything is validated — both human reviews and automated workflows — the last step of a Pull Request is merging it back to main.
Branch Protection Is What Enforces It#
Branch Protection is the means to enforce those steps before anything reaches the target branch. The rules can require workflows to run, require approvals from a security expert or architect, and require specific status checks to pass.
You configure it under Settings → Branches → Branch Protection rules. There are many options, and we walk through the ones that matter for DevSecOps in the demo.
The Best Practices#
Before the demo we line up the practices we recommend whenever Pull Requests are used:
- Open Pull Requests early and often, and keep them small. The smaller the batch size, the faster the feedback loop.
- A Pull Request does not have to be merged. It can be rejected, or used to explore alternatives in the team.
- Use Pull Request templates so reviewers always get the context they need.
- Define Branch Protection rules — that is the whole point of this video.
- Take advantage of the different merge methods (merge, squash, rebase) and choose deliberately.
The Demo: PR Without Protection#
I edit the POM file to bump Spring Boot from 2.0.1 to 2.0.9, and I add a new MD5 hash on a message — a deliberate insecure crypto algorithm to trigger SAST. Patrick reminds me I forgot to create a branch first. No problem in GitHub: you create a new branch and move the changes onto it directly.
We open a Pull Request from fixes into main. We see space for conversation, the commits, the file changes — but only one security check is running, not the full pipeline. The reason: when we built the pipeline workflow, we restricted it with on: push: branches: [main]. The pipeline only runs on main.
The other thing missing: nothing forces an approval, nothing blocks the merge.
Configuring Branch Protection#
Settings → Branches → Add Branch Protection rule. We use * so the rule applies to every branch. Then we enable:
- Require a Pull Request before merging. No more pushing straight to main.
- Require approvals. With just Patrick and me on the repo we set the count to 1.
- Require status checks before merging, and require branches to be up to date before merging.
For status checks you have to add each job by name: Build, SCA, License Compliance, SAST, Docker Image, Container Image Scan, DAST, License Compliance, Test Results. Patrick called the usability of this part not great, and he is right — it is detailed work, one job at a time. We also enable “Do not allow bypassing the above settings”, which is exactly what you want on a production branch.
Back on the Pull Request the required checks are now listed but still not executing — because the workflow itself is still pinned to main. So I edit the workflow, remove the branch filter so the pipeline runs on every branch, and commit it on main. Because that change happened on main, the existing PR has to be closed and reopened (and the branch updated) before the new workflow definition picks it up.
Reading the Results on the PR#
Now the pipeline runs against the PR. Build runs, SCA runs, test results show up directly on the Pull Request, and the GitHub code scanning report surfaces our new findings — including the MD5 issue. It even shows up twice, because both Semgrep and CodeQL detect it. Each finding has “show paths” and “dismiss this alert” actions, plus a per-finding conversation thread.
One honest gap: GitHub does not show what findings a commit resolved, only what it introduced. DAST also failed, but in our case because of a rate limit on the open-source service we are using, not because of a real issue.
The merge is still blocked because we have not approved yet. Patrick approves with a note that the MD5 hash is just an internal hash function. The merge button unlocks and we confirm the merge — the Pull Request is closed, the changes are in main, and the workflow runs again on main as expected.
Recap#
Pull Requests are the discussion surface; Branch Protection is the enforcement. Without the protection rules the PR is just a UI. With protection rules requiring PRs, approvals, and the full set of pipeline status checks, the PR becomes a real DevSecOps gate. New findings are visible per PR, conversations happen on findings, and merges are blocked until both humans and tooling agree.
Next session we look at scheduled pipelines — making sure code already in production keeps getting scanned, even when nobody is committing.
Key Takeaways#
A Pull Request is a discussion, not a final submit. Open it early, keep batches small, and let the conversation and the workflows shape the change as it evolves.
Branch Protection is what makes the PR a gate. Without it, the PR is a nice UI on top of a merge button. Required PRs, required approvals and required status checks turn it into something that blocks bad changes.
Pin your workflows to the right branches. Restricting the pipeline to
mainonly means PR scans never run. Either remove the branch filter or add explicit pull-request triggers.Add every status check by name. GitHub does not let you require “all checks”. You list them: Build, SCA, License Compliance, SAST, Container Scan, DAST, Tests. Tedious — but explicit and auditable.
PRs surface new findings, not resolved ones. Code scanning shows what this PR introduced, sometimes from multiple tools (Semgrep and CodeQL both flagged the MD5). Useful for review; just know that “what got fixed” is not in the PR view.
“Do not allow bypassing the above settings” exists for a reason. Turn it on for production branches so even admins go through the rules. The whole point is that nobody — including future-you in a hurry — can skip the gate.
