StackPractices
intermediate By Mathias Paulenko

SOC 2 Compliance — Basics for Engineering Teams

A practical guide to SOC 2 Type II for developers: Trust Service Criteria, evidence collection, access controls, and building compliant systems from day one.

Topics: security

Overview

The deal is signed in principle, procurement is done, and then the customer’s security team sends a 300-line questionnaire. Question 12: “Attach your most recent SOC 2 Type II report.” If you don’t have one, the deal stalls for months — or dies quietly. That’s the moment most engineering teams first meet SOC 2, and it’s the wrong moment to start preparing.

SOC 2 (Service Organization Control 2) is an attestation report issued by an independent CPA firm under the AICPA’s Trust Services Criteria. It tells customers that an auditor verified your controls for security, availability, processing integrity, confidentiality, and privacy — not just that you wrote policies, but that the controls actually operated over a period of months. For engineering teams, SOC 2 is mostly a build-and-instrument problem: the systems you ship have to produce evidence as a byproduct of running.

What follows: what an auditor evaluates, how to scope a first audit, how to build controls that generate their own evidence, what the process costs, and where teams usually burn time.

When to Use

  • Your sales team needs a SOC 2 report to close enterprise or regulated-industry customers.
  • You handle customer data as a processor or subprocessor and contracts keep asking for independent assurance.
  • You’re designing systems that will be audited later and want controls baked in instead of retrofitted.
  • You’re moving from “we’re secure, trust us” to “here’s a report from a third party”.

You probably don’t need SOC 2 yet if you’re pre-product-market-fit, sell to consumers, or your customers never ask for it. A security questionnaire answered honestly plus a penetration test covers most early-stage objections, and SOC 2 spent before there’s demand is budget burned. If what you actually need is to find control gaps before committing to a full audit, start with a gap analysis instead — the Compliance Gap Analysis Template is built for exactly that step.

What SOC 2 Actually Evaluates

Type I vs Type II

Type IType II
What it provesControls are designed appropriatelyControls operated effectively over a period
Evidence windowA single point in timeUsually 3–12 months
Cost and effortLower; weeks of prepHigher; months of operating evidence
Customer weightAccepted by some, often as a bridgeThe report enterprises actually ask for

A Type I is useful as a stepping stone: it proves you finished building the controls. It’s not the end state. Customers who say “SOC 2” almost always mean Type II.

The five Trust Service Criteria

SOC 2 evaluates your system against five categories defined in the Trust Services Criteria (TSC). Only Security is mandatory — it’s the Common Criteria every report includes. The other four are scoped in based on what your service promises.

CriteriaFocusWhat it means for developers
Security (CC)System protected against unauthorized accessIAM, MFA, encryption, logging, change management
Availability (A1)System is operational and accessibleUptime monitoring, capacity planning, disaster recovery tests
Processing Integrity (PI1)Processing is complete, valid, accurateInput validation, reconciliation, error handling, idempotency
Confidentiality (C1)Designated information is protectedData classification, encryption at rest/in transit, retention limits
Privacy (P1–P8)Personal info handled per your privacy noticeConsent, collection limits, subject rights, disposal

A typical first audit is Security + Availability. Privacy usually gets scoped in once GDPR obligations make it unavoidable; if that’s your situation, the GDPR compliance guide covers the privacy criteria in depth.

What auditors actually test

Auditors don’t read your policies and take your word for it. For each control in scope, they:

  1. Inspect the design: does the control, as written, address the criterion?
  2. Sample operating evidence: they’ll pick, say, 25 of your 52 weekly access reviews and ask to see proof each one happened, was documented, and was reviewed.
  3. Inquire and reperform: they interview control owners and sometimes re-execute the control themselves.

This is why “we do access reviews” fails audits while “here’s the ticket, the reviewer sign-off, and the diff of what changed” passes. The control is only real if it leaves a trail.

Scoping Your First Audit

Three decisions shape everything downstream:

Which criteria? Start with Security. Add Availability if you’ve got an SLA or your customers’ operations depend on your uptime. Add Confidentiality if contracts require it. Adding criteria you can’t yet evidence just creates findings.

Which systems? The system boundary is what you describe in the report’s system description: infrastructure, applications, people, and processes that deliver the service. Keep it tight: your production SaaS, not your marketing site. Every system inside the boundary gets its controls tested.

Which period? The observation window is the stretch of time the auditor samples. Most first-timers run 3–6 months for the initial Type II, then move to 12-month windows for annual renewal. A longer window means more mature-looking evidence but more months before you get the report.

The Common Criteria in Practice

The Security criterion breaks down into nine Common Criteria families. This is where the audit maps onto engineering work:

CriterionCoversTypical engineering controls
CC1 Control environmentTone at the top, org structure, competenceWritten security policy, code of conduct, background checks in hiring
CC2 Communication & informationPolicies reach the people who need themInternal wiki, annual security training with attendance logs, incident reporting channel
CC3 Risk assessmentIdentifying and analyzing riskQuarterly risk register, threat modeling for new capabilities, vendor risk reviews
CC4 Monitoring activitiesEvaluating whether controls keep workingImmutable audit log, security alerting, quarterly access reviews
CC5 Control activitiesThe controls themselvesRBAC with least privilege, MFA everywhere, PR + approval on every change
CC6 Logical & physical accessWho can reach whatSSO + MFA, joiner/mover/leaver process, offboarding within 24h
CC7 System operationsDetecting and responding to anomaliesPatch SLAs, vulnerability scanning cadence, incident response plan
CC8 Change managementChanges are authorized, tested, documentedCI/CD with approval gates, segregation of duties, rollback plans
CC9 Risk mitigationMitigating identified risksDisaster recovery plan, tested backups, business continuity

Notice the pattern: nearly every line is something a healthy engineering team already does informally. The audit adds two requirements: the control is written down, and it produces evidence someone else can verify.

Building Compliant Systems

The four controls below carry the most weight in a typical SaaS audit: access and change management alone account for most of what gets sampled. Build them so the evidence writes itself.

Access controls (CC6)

Least privilege plus a reviewable trail. In practice that means role checks on sensitive operations, MFA on anything touching production, and an audit record on every privileged action. If you need the implementation details, the RBAC recipe walks through a working model.

# app/audit.py — audit record on privileged actions
from datetime import datetime, timezone
from functools import wraps
from flask import abort, g


def requires_role(role: str, action: str):
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            user = g.current_user
            if not user or not user.has_role(role):
                audit_log.record(
                    actor=getattr(user, "id", "anonymous"),
                    action=action,
                    result="denied",
                    timestamp=datetime.now(timezone.utc),
                )
                abort(403)
            result = fn(*args, **kwargs)
            audit_log.record(
                actor=user.id,
                action=action,
                result="success",
                timestamp=datetime.now(timezone.utc),
            )
            return result
        return wrapper
    return decorator


@requires_role("production_deployer", "deploy_production")
def deploy_to_production(artifact_id: str):
    return deploy(artifact_id)

Two things auditors look for here: the denial is logged too (not just successes), and the timestamp is trustworthy: datetime.now(timezone.utc), not utcnow(), which is deprecated since Python 3.12 and returns naive datetimes that complicate log correlation later.

Monitoring and detection (CC7)

Anomalies have to be detected, alerted on, and answered. Privilege changes are the classic test case: an auditor will ask how you’d know if someone granted themselves admin.

# checks/privilege_escalation.py — runs hourly off your auth event stream
from dataclasses import dataclass


@dataclass
class AuthEvent:
    user_id: str
    action: str
    old_role: str | None
    new_role: str | None


def detect_escalation(events: list[AuthEvent]) -> list[str]:
    alerts = []
    for e in events:
        if e.action == "role_change" and e.new_role == "admin" and e.old_role != "admin":
            alerts.append(f"Privilege escalation: {e.user_id} granted admin")
    return alerts

The auditor’s question isn’t “does this function exist” — it’s “show me an alert from last quarter and the ticket that closed it.” Wire detections into whatever creates tickets, not just Slack messages that scroll away.

Change management (CC8)

Every production change needs to be authorized, tested, and documented. GitOps plus a protected environment gets you most of the control for free:

# .github/workflows/deploy-production.yml
name: Deploy to Production
on:
  workflow_dispatch:

jobs:
  deploy:
    environment: production  # manual approval required via repo settings
    steps:
      - uses: actions/checkout@v4
      - name: Verify change ticket
        run: ./scripts/verify-change-ticket.sh "${{ github.sha }}"
      - name: Deploy
        run: ./scripts/deploy.sh production

The environment: production line does the heavy lifting: GitHub environment protection rules force a named approver, and the approval is recorded; that’s your CC8 evidence. Segregation of duties falls out naturally if the approver can’t be the commit author.

Vulnerability management (CC9)

Scanners are cheap; the control is the SLA on findings. A defensible setup scans dependencies and containers on every build and routes critical findings into your tracker with a deadline:

# scripts/vuln_triage.py — runs in CI after trivy/snyk scans
import json
import subprocess
from datetime import date, timedelta

SLA_DAYS = {"CRITICAL": 1, "HIGH": 7}


def trivy_findings(image: str) -> list[dict]:
    out = subprocess.run(
        ["trivy", "image", "--format", "json", "--severity", "CRITICAL,HIGH", image],
        capture_output=True, check=True, text=True,
    ).stdout
    report = json.loads(out)
    return [
        v
        for result in report.get("Results", [])
        for v in result.get("Vulnerabilities", [])
    ]


def ticket_for(vuln: dict) -> dict:
    severity = vuln.get("Severity", "HIGH")
    return {
        "summary": f"[{severity}] {vuln['VulnerabilityID']} in {vuln['PkgName']}",
        "due_date": str(date.today() + timedelta(days=SLA_DAYS.get(severity, 7))),
    }

Patch SLAs like “critical in 48–72h, high in 7 days” are what auditors write into their test plan: pick numbers you can actually keep, because a missed SLA is a finding.

Evidence Collection That Survives an Audit

The difference between “we do this” and “we can prove this” is a table like this one: every control mapped to the artifact it leaves behind:

ControlEvidence the auditor samplesHow to automate it
Access reviewsQuarterly review tickets + IAM export diffExport IAM users via API; diff against previous quarter
PR approvalsApproval trail on merged PRsGitHub API export of reviews per PR
Backup restorationRestore-test logs with pass/failScheduled restore job that writes results to the audit log
Penetration testThird-party report + remediation ticketsAnnual vendor test; track each finding to closure
Incident responsePostmortems + timeline + resolution timePostmortem template with mandatory fields
OffboardingTicket showing access revoked within SLATrigger on HR offboarding event; log revocation timestamps

The access review is the control teams most often fake without meaning to. A defensible quarterly review looks like this: export the current IAM state (users, roles, last-login), diff it against the previous export, have a named reviewer mark each entry “still needed / revoke”, file the result as a ticket, and actually revoke what was flagged. The ticket plus the diff is the evidence; the export is reproducible, so an auditor can reperform it.

Two rules of thumb: screenshots and spreadsheets work for Type I but rot during a 12-month Type II window, so automate exports where you can. And the audit trail itself has to be tamper-evident; append-only storage (object lock, a dedicated log bucket, or a SIEM) beats a table your own admins can edit.

# app/audit_log.py — the event shape auditors expect to see
from datetime import datetime
from pydantic import BaseModel


class AuditEvent(BaseModel):
    timestamp: datetime
    actor: str
    action: str
    resource: str
    result: str          # success | denied | error
    ip_address: str
    user_agent: str
    correlation_id: str  # joins the audit event to the request trace

The fields that matter in an audit are actor, action, result, and correlation_id; with those four you can reconstruct who did what, when, and what else happened in the same request.

A working evidence kit (access review diff script, PR approval exporter, evidence map, vendor checklist) lives in the companion repository.

The Audit Timeline

Most first Type II audits run about a year end-to-end. The part teams underestimate is that months 3–5 are pure engineering time: building controls, not paperwork.

Mermaid flowchart LR diagram

Gap assessment is a self-review (or a consultant’s review) against the TSC: which criteria apply, which controls exist, which don’t. Build missing controls is where the real work lives — this guide’s earlier sections. Readiness check is a dress rehearsal: someone verifies evidence actually exists for each control before the clock starts. The observation window is the 3–12 month stretch the auditor samples — controls must run the whole time, and gaps inside the window become report exceptions. Fieldwork is when the auditor requests evidence, interviews owners, and writes the report.

One thing that surprises teams: the clock doesn’t pause for you. If your quarterly access review was skipped in month 7, it shows up as an exception in the report. Missed controls don’t get hidden — they get documented.

Reading the Report

When the report lands, read it the way your customers will: top to bottom, with a pen. The structure is fixed:

The auditor’s opinion comes first, and it’s the only part most readers check. An unqualified opinion means every control did what it was supposed to. A qualified opinion means they mostly did, except for specific criteria — each qualification names the failing control. Adverse and disclaimer opinions are rare and amount to starting over. A qualified opinion isn’t the end of the world, but expect every customer to ask about whatever is qualified.

The system description is your own text: boundaries, architecture, people, and the controls you committed to. Auditors test against this description, so write it tight; anything you claim here becomes fair game.

The controls and test results section is the bulk: every in-scope criterion, the control you stated, the test the auditor performed, and the result. Read the test descriptions, not just pass/fail: they tell you exactly what evidence was sampled and what will be sampled again next year.

Exceptions list every control that failed to operate during the window. For each one, management writes a response. This is the section your security-conscious customers will quote from.

Two concepts confuse first-time readers. Complementary User Entity Controls (CUECs) are things the report assumes your customers do — like keeping their own admin accounts secure. The report doesn’t cover them; if a customer can’t meet their CUECs, your report doesn’t fully apply to them. And subservice organizations are your vendors: under the carve-out method (the default), controls provided by AWS, Stripe, or your managed database are excluded from your report, and the auditor simply notes which vendor reports you reviewed instead. Under the inclusive method — rare — the vendor’s controls are audited as part of yours. Carve-out is why “but AWS handles that” is a valid audit answer, as long as you reviewed their SOC 2 and documented it.

There’s one more artifact worth knowing: the bridge letter. A Type II covers a fixed window, so by March your January–December report already looks stale. For the gap between the window’s end and the next report, your auditor can issue a short letter stating nothing material changed in controls; customers accept it as a stopgap. Ask for one when the deal can’t wait for next year’s report.

Costs, Tools and Realistic Budgets

Published ranges from compliance automation vendors and audit firms cluster around:

ItemTypical range
Type I audit$15K–$30K
Type II audit (first year)$30K–$60K
Type II renewal$20K–$40K / year
Compliance automation (Vanta, Drata, SecureFrame, Thoropass, Sprinto)$5K–$20K / year
Readiness consultant$10K–$25K
Engineering time200–400 hours

A realistic first-year budget is $50K–$80K all-in. The automation platform is usually worth it past ~15–20 controls: these tools connect to your AWS/GCP, GitHub, HRIS, and IAM, then continuously verify controls and collect evidence; the alternative is a quarter of spreadsheet archaeology before every audit. The auditor is separate: pick a CPA firm with SaaS experience, because firms that mostly audit on-prem environments will ask for artifacts that don’t exist in your architecture. Most firms will also discount a bundled Type I + Type II engagement — worth negotiating before you sign either contract, since you’ll almost certainly want both.

SOC 2 and ISO 27001

They overlap heavily (both cover access control, operations, and risk management), but they’re different artifacts:

  • SOC 2 is an attestation report: an auditor describes your system and the operating effectiveness of your controls against the Trust Services Criteria. US-centric, and it’s what American enterprise customers default to.
  • ISO 27001 is a certification of your information security management system (ISMS) against an international standard. Stronger signal in Europe and Asia, slower to obtain, and it certifies your management system rather than describing individual controls.

If customers are asking for SOC 2, do SOC 2 first; the control work transfers cleanly if you later pursue ISO. If you process EU personal data at scale, pair whichever you choose with a privacy program; SOC 2’s privacy criteria and GDPR overlap but aren’t interchangeable.

Vendor Management

Your controls inherit your vendors’ weaknesses. SOC 2 expects due diligence on every third party that touches in-scope data, and the auditor will ask for the list and the reviews. This is also where the carve-out decision lands: for every subservice organization you rely on, you need their SOC 2 on file and a note on which of their controls you’re depending on.

A workable onboarding checklist:

  • Obtain the vendor’s own SOC 2 report (Type II preferred) and read the exceptions section
  • Document exactly which data categories the vendor receives
  • Sign a DPA before any production data flows
  • Verify encryption in transit and at rest; note the vendor’s subprocessors
  • Confirm an incident notification SLA (24–72h is typical)
  • Record the review and schedule an annual re-assessment

The Vendor Risk Assessment Template turns this into a repeatable document; auditors like seeing the same format applied consistently across vendors.

Best Practices

  • Write controls as “we do X, proven by Y.” If a control has no artifact, it doesn’t exist for audit purposes. Every control should name its evidence.
  • Start the observation window only when controls are stable. A control that changes mid-window makes the auditor test both versions.
  • Automate the boring evidence first. Access review diffs, PR approval exports, and backup-test logs are cheap to script and painful to reconstruct manually.
  • Assign one owner per control family. “Security owns CC6” beats “the team owns everything” when the auditor asks who performs the control.
  • Read your own exceptions section first. Before the report ships, you get a management response letter; exceptions you can’t explain are the ones customers will quote back at you.
  • Treat the report as renewable, not done. Type II is annual. Controls that rot between audits produce next year’s findings.

Common Mistakes

  • Treating SOC 2 as a documentation project. The policies take a week; the evidence takes a year. Teams that write policies in month 1 and start the clock have 3–12 months of proof to produce anyway.
  • Screenshots as an evidence strategy. Manual evidence rots and auditors distrust it. If it can’t be exported from the system of record, assume it won’t survive sampling.
  • Offboarding that lags. Ex-employees with active accounts are among the most common audit exceptions. The control isn’t “we remove access”; it’s “access is revoked within N hours of the HR event, logged.”
  • Infrastructure changes outside change management. Application deploys go through PR review; Terraform applies often don’t. Auditors test infrastructure changes too: put applies behind the same approval gate.
  • Scoping in criteria you can’t evidence. Every optional criterion you add creates its own test plan. Security + Availability first; expand next cycle.
  • Ignoring the exceptions letter. Auditors issue a draft report with findings before the final. Teams that don’t respond in writing ship a report where their silence looks like agreement.
  • Confusing SOC 2 with SOC 1. SOC 1 covers controls relevant to financial reporting. Unless your system affects customers’ financial statements, it’s the wrong report.
  • Sprinting at the end of the window. Teams treat month 11 like a deadline and fix everything right before fieldwork. Auditors sample the whole window: a control that only worked for the last two months shows as a partial-period exception.

See Also

Frequently Asked Questions

What's the difference between SOC 2 Type I and Type II?

Type I evaluates whether your controls are designed appropriately at a single point in time. Type II evaluates whether they operated effectively over a period — usually 3–12 months — and requires sampled evidence for the whole window.

How long does SOC 2 Type II take?

Plan for 3–6 months of preparation (gap assessment, building controls, readiness check), then a 3–12 month observation window, then a few weeks of fieldwork. A first Type II realistically lands 9–15 months after you start.

How much does a SOC 2 audit cost?

Roughly $15K–$30K for Type I and $30K–$60K for a first Type II, based on ranges published by compliance vendors and audit firms. Add $5K–$20K/year for automation tooling, optional consultant fees, and 200–400 hours of engineering time — most teams spend $50K–$80K in year one.

Do I need a compliance automation tool like Vanta or Drata?

Not strictly, but the math favors it past about 15–20 controls. These platforms continuously verify controls against your cloud, code, and HR systems and store the evidence. Without one you do the same work manually, quarterly, forever.

SOC 2 or ISO 27001 — which should I do first?

Follow your customers. US enterprise buyers default to SOC 2; European and international buyers often recognize ISO 27001 faster. The underlying control work mostly overlaps, so the first one is the expensive one.

What happens if a control fails during the audit?

It becomes an exception in the report: the auditor documents that the control didn't operate effectively for part of the window. Exceptions aren't fatal: customers care more about how you responded. Unexplained or repeated exceptions are what erode trust.