Building a release process when you don't have one
If your team raises pull requests to main, merges, and deploys from there, you already have most of a release process. What these controls ask for is smaller than the phrase suggests:
- A gate before the merge — checks that have to pass.
- A person who isn't the author — one approval.
- A record — which your pull request already is.
That's the whole ask. No change advisory board, no sign-off form, no release manager, no two-week freeze. You use pull requests, so the gate is nearly there. The only thing missing is that right now the pull request can't say no.
Make main mean something
Here's what the setup above costs you. Everything merged to main is a candidate for production whether or not it's ready, so main becomes a queue of unverified code. Ask "what's in production?" and the honest answer is "roughly what's in main, minus whatever hasn't gone out yet." Nobody can answer it precisely, and neither can your auditor.
The rule that fixes it is one sentence: main is always releasable. Not "main is what we deploy" — releasable. If merging something would make main un-releasable, it shouldn't have merged. Everything below falls out of that one rule.
Trunk-based development is fine
You are not being told to adopt long-lived release branches, GitFlow, or a two-week freeze. A single main branch with a real pull request gate satisfies every one of these tests. The control is about the gate, not the branch count.
If you do run release branches, that's fine too. Just keep the branch you actually ship from as your repository's default branch, because that's the branch the branch protection test reads.
Environments are not branches
Deploying main to staging and then promoting that same build to production is a good pattern — keep it. The mistake is using a branch as the environment gate: a "staging branch" and a "production branch" that code gets merged between.
Promotion is about which build you've verified, not which branch it sat on. Build once from a merged commit, verify that build in staging, promote that exact build to production. Merging between branches to represent a deploy splits the record of what was reviewed from the thing that actually shipped — and that record is what every one of these tests reads.
Step 1 — protect the default branch
Stop direct pushes to your default branch, so every change has to arrive as a pull request. This is the single change that turns the pull request from a habit into a gate.
Each provider does this differently, and the remediation on the branch protection test's own page walks through it for yours. What matters is the outcome: nobody, including you, can push to that branch directly.
This is what the branch protection test reads — it checks that the default branch of each repository in scope is protected. It's one of the two forgiving tests: turn it on and the next run reflects it.
It also makes "what's in production?" answerable — a known-good commit, a build you can redeploy, and a short list of what changed since.
Step 2 — require an approval from someone who isn't the author
Turn on a required approval on the pull request, and make sure the approval has to come from someone other than the person who raised it.
One reviewer is enough for most teams. The control is independence, not headcount — an auditor is looking for a second pair of eyes, not a committee.
Self-approval counts for nothing here, and this is the most common reason the code review test fails. A pull request you raised and approved yourself scores the same as one nobody looked at, no matter how careful you were. If your provider allows the author to approve their own pull request, turn that off.
This is the code review test.
Step 3 — make your checks blocking, not advisory
Your test run, your dependency scan and your security scan probably already run on pull requests. Make them required to merge rather than informational — a red check should stop the merge, not just show up on the page.
Then tell UprootSecurity what each one is called. Telling UprootSecurity which CI checks to look for has the exact name to use on each provider, and getting that name right is most of the work. These are the integration testing, dependency testing and SAST tests.
If there's nobody else to review
"I'm the only engineer" is a real constraint, and it's worth being straight about it. Ranked honestly:
- Bring in a second reviewer, even part-time. A contractor, a technical advisor, a co-founder who can read code. They don't need to review everything deeply — they need to be a genuine second person who looks and approves. This is the only option that actually satisfies the control.
- If that's truly impossible, record it as a documented exception. Write down that you're a single-engineer team, what you do instead — automated checks that block the merge, a scan on every change, a rollback path — and the date you expect the exception to end. Expect the code review test to stay red in the meantime, and let it: a red test with a written, dated exception behind it is a known gap you're managing, which is a position an auditor can work with. Don't clear it by marking pull requests Not Applicable one after another — that lever is for the individual item that genuinely isn't part of your claim, and a run of them tells the opposite story to the one you're trying to tell.
What doesn't work is approving your own pull requests. It doesn't satisfy the test, and in fieldwork an auditor notices that every approver is also the author — which turns a team-size conversation into a harder one.
Emergency changes and hotfixes
A production incident at 2am should not be blocked by process, and no auditor expects it to be. The rule is that the gate can be bypassed, but never silently.
When you skip the gate, say what you skipped, why, and who decided — in the pull request, in the incident ticket, wherever your team will actually find it. Then get the review done retrospectively, same day. A pull request merged without approval keeps that verdict, so the code review test will show it. That's the correct outcome — the record is what makes an emergency explicable.
A small number of documented emergency changes reads as a functioning process under pressure. An undocumented one reads as no process at all.
