# Code changes flow through a reviewed pull request workflow with committed evidence

- **Pattern:** `ab-002632` (`soc2-readiness.change-management.pr-review-workflow-configured`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-07-03
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002632
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002632)

## 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:

```yaml
# .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/*.yml` or `*.yaml` whose `on:` block includes `pull_request` (or `pull_request_target`); equivalents count too: `.gitlab-ci.yml` with `rules:` or `only:` matching `merge_request_event`, `bitbucket-pipelines.yml` with a `pull-requests:` section, `.circleci/config.yml` filters targeting PR builds. (b) CODEOWNERS: a `CODEOWNERS` file 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.yml` containing a `branches:` block with `protection:` 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 or `docs/` `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 `.git` directory exists at the project root being audited, recent default-branch commit subjects matching `Merge 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 `.git` directory (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_request` trigger, 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 has `push`, `schedule`, or `workflow_dispatch` (no pull request trigger); a CODEOWNERS file that is empty or entirely commented out; a `.github/settings.yml` with no `branches:` protection block (repo metadata only); a zero-byte or placeholder-only PR template; a commented-out `pull_request:` line in a workflow. A history full of `Merge 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, the `protection:` 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 `.git` directory exists at the project root, then quote 2 or 3 matching commit subjects. If no `.git` directory 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 the `on:` block of `.github/workflows/ci.yml`, create `.github/CODEOWNERS` with at least a `* @your-handle` fallback rule, add `.github/PULL_REQUEST_TEMPLATE.md`, and export rulesets via `gh api repos/{owner}/{repo}/rulesets` into `.github/rulesets/` (or maintain a probot `.github/settings.yml` with a `branches:` 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 CC8.1
- iso-27001 A.8.32
- nist CM-3

Taxons: change-management, regulatory-conformance

HTML version: https://auditbuffet.com/patterns/ab-002632
