In short

A SOC 2 report is a CPA firm's opinion on whether a service organization's controls over a named system meet the AICPA Trust Services Criteria: at a point in time for Type 1, and over a period, with tests of operating effectiveness, for Type 2. Engineering owns most of three common-criteria series: CC6 logical access, CC7 system operations and CC8 change management. Build controls that run in your systems, such as protected branches, pipeline-only deployment, identity-provider access and triaged alerts, and keep system-generated evidence for the whole period, because the auditor samples from the complete population.

  • The criteria are the 2017 Trust Services Criteria with points of focus revised in 2022; the 2022 revision changed the points of focus, not the criteria.
  • The common criteria (CC1 to CC9) apply in every SOC 2 engagement; Availability, Processing Integrity, Confidentiality and Privacy add criteria when they are in scope.
  • A Type 2 report covers design and operating effectiveness throughout a stated period, with the auditor's tests and results; a Type 1 report covers design without testing operation.
  • CC8.1 covers authorizing, designing, developing or acquiring, configuring, documenting, testing, approving and implementing changes to infrastructure, data, software and procedures, so the pipeline is the control.
  • Auditors test samples drawn from a population you provide, so the population must be complete and system-generated: every merged change and deployment in the period, not a hand-picked list.
  • The report covers only the system in its description; carved-out subservice organizations and complementary user entity controls stay with others.

Verify it

  1. Run the change export for last quarter and confirm every merged pull request appears, then compare its count with the repository's merge history.
  2. Check that the default-branch ruleset requires approval of the latest push and that its bypass list is empty or reviewed.
  3. Confirm that only the pipeline's identity can deploy to production, by reading the cloud IAM policy, not the runbook.
  4. Match last quarter's leavers against identity-provider deprovisioning events and note any account removed more than a business day late.
  5. Pick one alert from each month of the period and trace it to a triage decision.
Applies to
  • AICPA Trust Services Criteria 2017 criteria with revised points of focus (2022)
  • AICPA SOC 2 Description Criteria 2018 Description Criteria with revised implementation guidance (2022)
  • GitHub REST API Pulls, reviews, check runs and commit statuses endpoints

Service organizations, typically SaaS and cloud providers, that undergo SOC 2 examinations, and the engineering teams whose controls are in scope. The auditor decides the testing approach and sample sizes.

Part of
Workflow: Build an AppSec program
Learning goals and prerequisitesAbout 29 minutes: 6 to read and 23 for the exercise

What you will learn

  • Explain what a SOC 2 Type 1 and Type 2 report cover and which criteria engineering controls answer.
  • Map CC6, CC7 and CC8 to controls in an identity provider, CI/CD pipeline and monitoring stack.
  • Produce complete, system-generated evidence for a period, such as a change population with independent approvals.

Before you start

Engineering, platform and security leads at SaaS companies preparing for or maintaining a SOC 2 Type 2 report

  • Know how code reaches production in your organization: branches, reviews, CI and deployment.
  • Know who grants and removes access to production systems.

Refresh first: ISO 27001, SOC 2, NIST CSF or FedRAMP: choose the framework by the question it answers

Go to the guided practice

What is a SOC 2 report, and what does engineering produce for it?

A SOC 2 examination is performed by a CPA firm under the AICPA's attestation standards. Management describes the system, using the AICPA's Description Criteria, and asserts that its controls meet the Trust Services Criteria for the categories in scope; the auditor gives an opinion. The common criteria apply in every engagement: CC1 to CC5 follow the 17 principles of the COSO internal control framework, and CC6 to CC9 add criteria for logical and physical access, system operations, change management and risk mitigation. Availability, Processing Integrity, Confidentiality and Privacy add their own criteria when chosen.

Engineering's part is the controls that run in its systems and the evidence they leave. The criteria say what must be achieved, not how, so a criterion like CC8.1 can be met by a branch ruleset, a CI pipeline and a deployment approval just as well as by a change advisory board. What matters is that the control is designed to meet the criterion and, for Type 2, that it operated throughout the period, which the auditor tests by sampling.

What do the CC6 access criteria mean in engineering terms?

CC6 covers logical and physical access. The criteria are paraphrased below; the AICPA publishes the exact text. In a cloud-hosted product the provider handles most of CC6.4 and CC6.5, physical access and media disposal, as a subservice organization, and your report says so.

What do the CC6 access criteria mean in engineering terms?
CriterionWhat it asks (paraphrased)Engineering controls and evidence
CC6.1Logical access security software, infrastructure and architectures protect information assetsSSO with MFA, network segmentation, encryption keys in a KMS; configuration exports
CC6.2Users are registered and authorized before credentials are issued, and credentials are removed when access is no longer authorizedProvisioning from the HR system through the identity provider; leaver tickets matched to deprovisioning logs
CC6.3Access is granted, changed and removed by role, with least privilege and segregation of dutiesRoles defined in code, quarterly access reviews, no standing production admin; review sign-offs
CC6.6Logical access measures protect against threats from outside the system boundaryWAF, rate limits, private networking, exposed-port scans; configuration and scan results
CC6.7Transmission, movement and removal of information is restricted to authorized users and processes and protectedTLS everywhere, restricted data exports, DLP on support tooling; TLS scans, export logs
CC6.8Unauthorized or malicious software is prevented or detected and acted onEndpoint protection, signed artifacts, admission policies, dependency scanning; alert and policy records

What do the CC7 system operations criteria ask for?

CC7 is about finding problems and dealing with them. CC7.1 asks for detection and monitoring procedures that identify configuration changes introducing new vulnerabilities and susceptibility to newly discovered ones: vulnerability scanning of code, dependencies, images and cloud configuration, with drift detection. CC7.2 asks for monitoring of components for anomalies that indicate malicious acts, natural disasters and errors, and for analysis of those anomalies, which is your alerting and on-call triage. CC7.3 asks that security events are evaluated to decide whether they are incidents, CC7.4 that incidents are handled under a defined response program to understand, contain, remediate and communicate, and CC7.5 that recovery activities are identified and carried out.

The evidence is operational data with timestamps: scanner results with triage decisions, alerts with acknowledgement and classification, incident records with timelines, post-incident reviews and restore tests. Keep it for the whole period. A quiet period still needs evidence that monitoring ran, such as the alert queue export or a test alert.

How does CC8 change management map to your pipeline?

CC8.1 asks that the entity authorizes, designs, develops or acquires, configures, documents, tests, approves and implements changes to infrastructure, data, software and procedures. In a pull-request workflow the pull request is the change record: the description and linked ticket authorize and document it, CI tests it, an independent review of the final commit approves it, and the pipeline implements it. Infrastructure as code and database migrations go through the same path, so they are covered too.

Three gaps commonly turn into exceptions: approvals that came before the last push, administrators who bypass branch rules, and deployments outside the pipeline. Require approval of the latest push, keep the bypass list empty or log and review every bypass, and give only the pipeline's identity permission to deploy. Emergency changes need a written path with review after the fact, inside a stated time.

How do you give the auditor a complete change population?

For a Type 2 period the auditor asks for every change, picks a sample and tests each one. Generate the population from the system of record rather than assembling it by hand, and flag exceptions yourself before the auditor does. The script lists every pull request merged into the default branch in the period, the author, who approved the final commit and which checks passed, using the GitHub REST API with the Python standard library. Run it with a read-only token; a period with many changes needs authenticated requests to stay within GitHub's rate limits.

change_population.pyPython
"""Export the change population for a SOC 2 period and flag changes that skipped review.

Lists every pull request merged into the default branch between two dates, with who wrote it,
who approved its final commit and whether its required checks passed, as CSV for the auditor.
Usage: GITHUB_TOKEN=... python3 change_population.py OWNER/REPO 2026-01-01 2026-06-30 main ci/test
"""

import csv
import json
import os
import sys
import urllib.request

API = "https://api.github.com"


def get(path):
    request = urllib.request.Request(API + path, headers={"Accept": "application/vnd.github+json"})
    if os.environ.get("GITHUB_TOKEN"):
        request.add_header("Authorization", "Bearer " + os.environ["GITHUB_TOKEN"])
    with urllib.request.urlopen(request) as response:
        return json.load(response)


def merged_pulls(repo, base, start, end):
    page = 1
    while True:
        query = f"state=closed&base={base}&sort=updated&direction=desc&per_page=100&page={page}"
        pulls = get(f"/repos/{repo}/pulls?{query}")
        for pull in pulls:
            if pull["updated_at"][:10] < start:
                return  # sorted by last update; merging updates a pull request
            if pull["merged_at"] and start <= pull["merged_at"][:10] <= end:
                yield pull
        if len(pulls) < 100:
            return
        page += 1


def main(repo, start, end, base, *required_checks):
    out = csv.writer(sys.stdout)
    out.writerow(["pr", "merged_at", "author", "approvers_of_final_commit", "checks", "exception"])
    for pull in merged_pulls(repo, base, start, end):
        head = pull["head"]["sha"]
        reviews = get(f"/repos/{repo}/pulls/{pull['number']}/reviews?per_page=100")
        approved = {r["user"]["login"] for r in reviews
                    if r["state"] == "APPROVED" and r["commit_id"] == head}
        approvers = sorted(approved - {pull["user"]["login"]})
        runs = get(f"/repos/{repo}/commits/{head}/check-runs?per_page=100")["check_runs"]
        statuses = get(f"/repos/{repo}/commits/{head}/status")["statuses"]
        passed = {run["name"] for run in runs if run["conclusion"] == "success"}
        passed |= {status["context"] for status in statuses if status["state"] == "success"}
        missing = [name for name in required_checks if name not in passed]
        exception = "; ".join(
            (["no independent approval of the final commit"] if not approvers else [])
            + [f"check {name} did not pass" for name in missing]
        )
        out.writerow([pull["number"], pull["merged_at"], pull["user"]["login"],
                      " ".join(approvers), " ".join(sorted(passed)), exception])


if __name__ == "__main__":
    main(*sys.argv[1:])

How do you keep evidence for every control?

Keep a register in the repository that ties each control to the criteria it supports, the system that produces its evidence, how often it runs and who owns it. It is the engineering view of the control matrix the auditor tests, and it tells you which exports to schedule so that evidence for the whole period exists before fieldwork starts.

Configurationevidence-register.jsonJSON
{
  "system": "Payments platform (as named in the system description)",
  "period": ["2026-01-01", "2026-06-30"],
  "controls": [
    {
      "id": "CHG-1",
      "criteria": ["CC8.1"],
      "control": "Changes to the default branch need an approving review of the final commit by someone other than the author, and passing required checks",
      "evidence": "change_population.py export for the period, ruleset settings with their change history",
      "frequency": "every change",
      "owner": "engineering productivity lead"
    },
    {
      "id": "CHG-2",
      "criteria": ["CC8.1", "CC6.3"],
      "control": "Only the deployment pipeline can deploy to production; deployments require an approved release",
      "evidence": "Cloud IAM policy export, deployment log for the period",
      "frequency": "every deployment",
      "owner": "platform lead"
    },
    {
      "id": "ACC-1",
      "criteria": ["CC6.2", "CC6.3"],
      "control": "Production access is granted by role through the identity provider and removed within one business day of leaving",
      "evidence": "Joiner and leaver tickets reconciled with identity provider logs; quarterly access review sign-off",
      "frequency": "quarterly review, every leaver",
      "owner": "security engineering"
    },
    {
      "id": "VUL-1",
      "criteria": ["CC7.1"],
      "control": "Dependency and image scanning on every build; configuration drift detection daily",
      "evidence": "Scanner results with triage decisions, drift alerts and their tickets",
      "frequency": "every build, daily",
      "owner": "product security"
    },
    {
      "id": "MON-1",
      "criteria": ["CC7.2", "CC7.3"],
      "control": "Security alerts are triaged by the on-call engineer and classified as event or incident",
      "evidence": "Alert queue export with acknowledgement times and classifications",
      "frequency": "continuous",
      "owner": "SRE on-call"
    },
    {
      "id": "IR-1",
      "criteria": ["CC7.4", "CC7.5"],
      "control": "Declared incidents follow the incident response plan, with a post-incident review and recovery test",
      "evidence": "Incident records, post-incident reviews, restore test results",
      "frequency": "every incident, restore test quarterly",
      "owner": "incident commander rota"
    }
  ]
}

What does a SOC 2 report not cover?

The report covers the system in the description, for the period stated. Subservice organizations, such as your cloud provider, are usually carved out, so their controls are covered by their own reports. Complementary user entity controls, such as customers enforcing MFA on their own accounts, are the customer's job. A clean opinion is not a statement that the product has no vulnerabilities: for that, customers look at application verification such as /frameworks/asvs-verification-evidence/. When a customer reviews your report, the questions they ask are the ones in /frameworks/supplier-software-assurance/; how SOC 2 compares with ISO/IEC 27001 is in /frameworks/choosing-assurance-frameworks/.

Guided practice

Your SaaS company is three months into its first SOC 2 Type 2 period. A readiness review sampled the items below. Decide which are likely exceptions, which criterion each affects and what to fix.

Supplied scenario material
Plain text
Readiness sample, period 2026-07-01 to 2026-12-31 (fictional company)
S1 PR #4127 merged 2026-08-14 by an org admin using "bypass branch protections"; no review; hotfix for checkout.
S2 PR #4180 approved by a reviewer at 10:02; the author pushed two more commits at 10:40; merged 10:45.
S3 Deploy log 2026-09-03: a production deployment from an engineer's laptop with personal cloud credentials.
S4 Leaver left 2026-08-29; SSO disabled 2026-08-29; a personal access token for the CI service account,
   created by the leaver, still worked on 2026-09-30.
S5 Container image scanning: results for July and September; August missing after a CI migration.
S6 Alert "multiple failed admin logins" 2026-09-12 02:14 UTC; acknowledged 2026-09-15; no classification.
The change population was exported from a spreadsheet the release manager keeps.
  1. 1. Classify the change items

    Judge S1 and S2 against CC8.1 and the company's control.

    Expected result and reasoning

    Both are likely exceptions to CC8.1 controls. S1 skipped review through an admin bypass, and an emergency hotfix still needs the documented emergency path with review after the fact. S2's approval predates the final commits, so the code that shipped wasn't approved. Fix: require approval of the latest push, remove standing bypass or log and review every use, and write the emergency change procedure.

  2. 2. Judge the deployment

    Decide what S3 shows.

    Expected result and reasoning

    A deployment outside the pipeline breaks the change control (CC8.1) and segregation of duties in access (CC6.3): an engineer could ship untested, unapproved code. Remove human deploy permissions in production so only the pipeline's identity can deploy, and add a detective control that alerts on deployments not made by it.

  3. 3. Follow the leaver

    Assess S4.

    Expected result and reasoning

    CC6.2 requires credentials to be removed when access is no longer authorized. Disabling SSO didn't remove a token the leaver created for a service account, which worked a month later. Inventory tokens and keys by creator, rotate those a leaver created as part of offboarding, and prefer short-lived workload identity over personal access tokens for CI.

  4. 4. Check operations evidence

    Assess S5 and S6 against CC7.

    Expected result and reasoning

    S5 leaves a month with no evidence that CC7.1 vulnerability detection operated; the auditor can't test what doesn't exist, so it is likely an exception even if scans ran. S6 shows an alert left for three days with no evaluation of whether it was an incident (CC7.2, CC7.3). Add monitoring for the scanner itself, and set and measure an acknowledgement target for security alerts.

  5. 5. Fix the population

    Decide whether the spreadsheet population is acceptable.

    Expected result and reasoning

    No. A hand-kept list can't show completeness, and the auditor needs confidence that every change is in the population. Export it from the source systems, merged pull requests and deployment logs, for the whole period, and reconcile the two so a deployment without a reviewed change stands out.

Your deliverable: A table of six readiness items with the criterion affected, whether it is likely an exception and the control fix, plus a plan for a system-generated change population.

The company and sample are fictional. Whether an item is reported as an exception, and how many samples are tested, is the auditor's judgment.

Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.

No draft saved on this device yet.

Sources & further reading

The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.

  1. AICPA: 2017 Trust Services Criteria (with revised points of focus, 2022)
  2. AICPA: 2018 SOC 2 Description Criteria (with revised implementation guidance, 2022)
  3. AICPA: SOC suite of services
  4. GitHub REST API: list pull requests
  5. GitHub REST API: list reviews for a pull request
  6. GitHub REST API: list check runs for a Git reference
  7. GitHub Docs: rulesets, require approval of the most recent reviewable push

Reference information

Download this guide as Markdown