Code changes flow through a reviewed pull request workflow with committed evidence
Why it matters
SOC 2 CC8.1 requires that changes to production systems are authorized, tested, and approved before they ship, and the very first thing an auditor asks for is your change management evidence: pull requests, review approvals, and the CI checks that gated them. This is an evidence-pointer check: it verifies committed evidence of the control, which is what an auditor will ask for. Server-side branch protection settings live in the GitHub or GitLab dashboard and are invisible to a repository audit, so the repo needs artifacts that make the workflow real and auditable: CI that triggers on pull requests, a CODEOWNERS file, branch protection exported as code, or a PR template. AI-generated and vibe-coded apps typically fail here because the default workflow is a solo founder (or their coding agent) committing straight to main and letting Vercel deploy it: no PR, no reviewer, no gate. That habit is fine for a prototype, but for SOC 2 it means every change to production is a one-person unauthorized change, and an auditor will flag the entire change management criterion as not operating. Setting up a PR-based flow with branch protection takes about 10 minutes on GitHub, which makes this one of the cheapest readiness wins in the whole audit.
Severity rationale
High because without a review gate a single actor (or a misbehaving coding agent) can push unauthorized changes directly to the production deploy branch, which auditors treat as a pervasive CC8.1 change management failure rather than an isolated finding.
Remediation
On GitHub this takes about 10 minutes: protect your default branch (Settings, Branches or Rulesets: require a pull request before merging, require at least one approval, require status checks), then commit the evidence so an auditor can see it in the repo. Add a pull_request trigger to your CI workflow:
# .github/workflows/ci.yml
on:
pull_request:
push:
branches: [main]
Add a .github/CODEOWNERS file assigning reviewers to paths, a .github/PULL_REQUEST_TEMPLATE.md prompting for test evidence, and export your branch protection as code: either a probot .github/settings.yml with a branches: protection block, or the ruleset JSON from gh api repos/{owner}/{repo}/rulesets committed under .github/rulesets/. Server-side branch protection is invisible to a repo audit, so the committed export is what turns the setting into evidence.
Detection
- ID:
pr-review-workflow-configured - Severity:
high - What to look for: Enumerate four artifact classes that evidence a reviewed pull request workflow. (a) PR-triggered CI:
.github/workflows/*.ymlor*.yamlwhoseon:block includespull_request(orpull_request_target); equivalents count too:.gitlab-ci.ymlwithrules:oronly:matchingmerge_request_event,bitbucket-pipelines.ymlwith apull-requests:section,.circleci/config.ymlfilters targeting PR builds. (b) CODEOWNERS: aCODEOWNERSfile at the repo root,.github/CODEOWNERS,docs/CODEOWNERS, or.gitlab/CODEOWNERS, containing at least one non-comment path-to-owner line. (c) Branch-protection-as-code: a probot.github/settings.ymlcontaining abranches:block withprotection:settings, or committed ruleset JSON exports (for example.github/rulesets/*.json, or any committed JSON whose shape matches a GitHub ruleset export with branch targets and a pull request requirement). (d) PR template:.github/PULL_REQUEST_TEMPLATE.md,.github/PULL_REQUEST_TEMPLATE/*.md, root ordocs/PULL_REQUEST_TEMPLATE.md, or.gitlab/merge_request_templates/*.md, with non-empty content. CORROBORATION ONLY, never required and never sufficient on its own: IF and only if a.gitdirectory exists at the project root being audited, recent default-branch commit subjects matchingMerge pull request #or squash-merge suffixes like(#123)corroborate that the workflow is actually used. NEVER run git commands when the project root has no.gitdirectory (fixtures, exports, archives): in that case simply note corroboration was not possible. This applies identically to Python and Go repos; the artifacts are host-level, not language-level, so do not skip non-JS projects. For each artifact class, quote the file path and the load-bearing line before counting it. - Pass criteria: Count the substantive artifact classes. Pass only when the count is at least TWO of the four classes (a) through (d): the CI workflow actually lists a
pull_requesttrigger, the CODEOWNERS file has at least one active (non-comment) rule, the settings.yml or ruleset export actually contains branch protection requiring pull requests, the PR template has real content. Merge-commit corroboration can strengthen confidence but never substitutes for a missing artifact and never tips a one-artifact repo to pass. - Fail criteria: Fewer than two substantive artifact classes. Sneaky variants that do NOT count toward the two: a CI workflow whose
on:block only haspush,schedule, orworkflow_dispatch(no pull request trigger); a CODEOWNERS file that is empty or entirely commented out; a.github/settings.ymlwith nobranches:protection block (repo metadata only); a zero-byte or placeholder-only PR template; a commented-outpull_request:line in a workflow. A history full ofMerge pull request #commits with zero committed artifacts also fails: corroboration alone is not evidence an auditor can point to. Do NOT pass when only one artifact class is substantive, no matter how polished that one artifact is. There is no penalty exemption for solo repos. - Skip (N/A) when: Never. This check has no skip: a solo repository with no PR flow fails, because that absence is exactly the CC8.1 finding a SOC 2 auditor would raise. Every repository preparing for SOC 2 can commit these artifacts regardless of stack, host, or team size.
- Before evaluating, quote: For each artifact class you count toward the pass threshold, quote the file path plus the load-bearing line: the
pull_request:trigger line from the workflow, the first active CODEOWNERS rule, theprotection:or pull-request-requirement key from settings.yml or the ruleset JSON, the first heading of the PR template. If using corroboration, first state that a.gitdirectory exists at the project root, then quote 2 or 3 matching commit subjects. If no.gitdirectory exists, state that and do not run git. - Report even on pass:
"PR review evidence: <k>/4 artifact classes (<list, e.g. pull_request CI + CODEOWNERS>); merge-commit corroboration: yes | no | unavailable (no .git)". - Detail on fail:
"1 of 4 PR-workflow artifacts found: .github/workflows/ci.yml triggers on pull_request, but no CODEOWNERS, no branch-protection-as-code, and no PR template. If branch protection is enabled on the GitHub side it is invisible to a repo audit; commit the ruleset export to .github/rulesets/ as evidence"or"No committed evidence of a PR review workflow: CI triggers on push only and no CODEOWNERS, settings.yml, or PR template exists. A PR-based flow with branch protection takes about 10 minutes to set up on GitHub". - Remediation: Enable branch protection on the default branch (require a pull request, at least one approval, and passing status checks), then commit the evidence: add
pull_request:to theon:block of.github/workflows/ci.yml, create.github/CODEOWNERSwith at least a* @your-handlefallback rule, add.github/PULL_REQUEST_TEMPLATE.md, and export rulesets viagh api repos/{owner}/{repo}/rulesetsinto.github/rulesets/(or maintain a probot.github/settings.ymlwith abranches:protection block). Any two of these artifacts satisfy the check; committing all four gives an auditor a complete CC8.1 evidence trail.
External references
- soc2:2017 · CC8.1 — Change management: authorization, testing, and approval of changes
- iso-27001:2022 · A.8.32 — Change management
- nist:rev5 · CM-3 — Configuration Change Control
Taxons
History
- 2026-07-03·v1.0.0·Initial soc2-readiness v1 authoring — repo-visible pull request review workflow artifacts (PR-triggered CI, CODEOWNERS, branch-protection-as-code, PR template) as committed evidence for SOC 2 CC8.1 change management.·by soc2-readiness-v1-build