SOC 2 Compliance for Engineering Teams: Controls, Evidence Collection, and Automation Without Slowing Down
A practical engineering guide to SOC 2 compliance. Covers Trust Service Criteria, Type I vs Type II, which controls engineering teams own, evidence collection automation from GitHub and AWS, infrastructure-as-code as compliance evidence, common audit mistakes, and tools like Vanta and Drata.
Your CEO walks into Slack and says a prospect needs a SOC 2 report before signing. You have 90 days. Nobody on the team has done this before.
This is how most startups encounter SOC 2. Not through careful planning, but through a sales blocker. The good news: if your engineering practices are already reasonable, you are closer than you think. The bad news: the gap between “reasonable practices” and “auditable practices” is mostly about proving what you do, not changing what you do.
This guide covers what engineering teams actually need to know and do for SOC 2, with concrete examples rather than compliance jargon.
What SOC 2 actually requires
SOC 2 is an audit framework created by the AICPA (American Institute of Certified Public Accountants). It evaluates your organization against five Trust Service Criteria (TSC):
- Security (required for every SOC 2 report)
- Availability
- Processing Integrity
- Confidentiality
- Privacy
Most startups going through their first audit scope only Security, sometimes adding Availability. Each additional criterion adds controls, evidence, and cost. Start narrow.
The criteria are not prescriptive about implementation. SOC 2 does not tell you to use a specific tool or process. It says “you need access controls” and your auditor evaluates whether your approach satisfies the requirement. This flexibility is both a strength and a source of confusion.
Type I vs Type II
Type I evaluates whether your controls are designed properly at a single point in time. The auditor looks at your policies, configurations, and processes on one specific date and says “yes, these are reasonable.”
Type II evaluates whether your controls operated effectively over a period of time, typically 3, 6, or 12 months. The auditor samples evidence from throughout the observation window. This is the report most enterprise buyers actually want.
The common path: get a Type I report first (faster, cheaper, unblocks deals), then follow it with a Type II covering 6 or 12 months. Some teams skip Type I entirely and go straight for a short-window Type II (3 months), which works if you have the time.
Controls engineering teams own
Your compliance and security team will own policies, vendor management, and HR-related controls. Engineering owns the technical controls. Here is what typically falls in your scope.
Access management
SOC 2 cares about who can access what, how access is granted, and how it is revoked. Concretely, you need:
- Role-based access to production systems (not shared credentials)
- A process for granting and revoking access (tied to onboarding/offboarding)
- Periodic access reviews (quarterly is standard)
- MFA on critical systems (cloud console, source control, production databases)
The most common finding in first audits is orphaned access. Someone left the company six months ago and still has AWS console access. Automate offboarding with a checklist that triggers across all systems, or better, centralize identity through an IdP like Okta or Google Workspace and provision access through SCIM.
# Example: pull IAM users who haven't logged in for 90+ days
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | \
base64 -d | \
awk -F',' '$5 != "N/A" && $5 < "'$(date -d "-90 days" +%Y-%m-%d)'" {print $1, $5}'
Change management
Every code change reaching production should be traceable. SOC 2 auditors want to see:
- Code reviews before merge (pull request approvals)
- Branch protection rules preventing direct pushes to main
- CI/CD pipeline that runs tests before deployment
- A record of what was deployed, when, and by whom
If you are already using GitHub with branch protection and pull request reviews, you have most of this. The key gap is usually documentation: can you show an auditor that these rules were enforced consistently over the audit window?
# GitHub branch protection as evidence (export via API)
# GET /repos/{owner}/{repo}/branches/main/protection
{
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true
},
"required_status_checks": {
"strict": true,
"contexts": ["ci/test", "ci/lint", "ci/security-scan"]
},
"enforce_admins": true,
"allow_force_pushes": false,
"allow_deletions": false
}
The enforce_admins: true part matters. Without it, org admins can bypass protections, and auditors will flag that.
Incident response
You need a documented incident response process and evidence that you follow it. This means:
- A written incident response plan (who gets paged, escalation paths, communication templates)
- Incident records showing the plan was followed (postmortems, timeline logs)
- Evidence of root cause analysis and remediation tracking
If you are using PagerDuty or Opsgenie for alerting and write postmortems in a shared wiki, you are most of the way there. The gap is usually consistency: the auditor will sample incidents from the audit window and check whether each one followed the documented process.
Logging and monitoring
Auditors expect:
- Audit logs for access to sensitive systems (who logged into production, who accessed the database)
- Application logs that capture security-relevant events (failed login attempts, permission changes)
- Log retention of at least 90 days (many teams aim for one year)
- Centralized log aggregation (not just logs sitting on individual servers)
- Alerting on security-relevant events
CloudTrail for AWS API calls, GitHub audit logs for repository actions, and application-level structured logging cover most of this. The retention requirement catches teams off guard. Check your current CloudWatch or Datadog retention settings before the audit.
Encryption
- Data at rest: encrypted storage (S3 default encryption, RDS encryption, EBS encryption)
- Data in transit: TLS everywhere (HTTPS, encrypted database connections)
- Key management: who controls encryption keys, rotation policy
Most cloud services encrypt at rest by default now. The common miss is database connections. If your application connects to RDS over an unencrypted connection inside a VPC, technically that is a finding. Enable sslmode=require in your connection strings.
// PostgreSQL connection with SSL enforcement
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: true,
ca: fs.readFileSync('/path/to/rds-combined-ca-bundle.pem').toString(),
},
});
Evidence collection automation
The real burden of SOC 2 is not implementing controls. It is proving they worked, continuously, over the audit window. Manual evidence collection (screenshots, spreadsheets, quarterly exports) does not scale and is error-prone.
Pulling audit logs from GitHub
GitHub’s audit log API provides evidence for change management and access controls.
# Export org audit log events for the audit window
gh api \
--paginate \
"/orgs/{org}/audit-log?phrase=created:>2025-10-01+created:<2026-03-31&include=all" \
--jq '.[] | {timestamp: .created_at, action: .action, actor: .actor, repo: .repo}' \
> github-audit-log.json
Key events to capture:
repo.create,repo.destroyfor repository lifecycleprotected_branch.updatefor branch protection changesorg.add_member,org.remove_memberfor access changesrepo.accessfor permission modifications
Pulling evidence from AWS
# CloudTrail: IAM policy changes over the audit window
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AttachUserPolicy \
--start-time 2025-10-01 \
--end-time 2026-03-31 \
--query 'Events[].{Time:EventTime,User:Username,Event:EventName,Resources:Resources}' \
--output json > iam-policy-changes.json
# Verify S3 bucket encryption settings across all buckets
aws s3api list-buckets --query 'Buckets[].Name' --output text | \
tr '\t' '\n' | \
while read bucket; do
echo "Bucket: $bucket"
aws s3api get-bucket-encryption --bucket "$bucket" 2>/dev/null || echo " NO ENCRYPTION"
done > s3-encryption-evidence.txt
CI/CD pipeline as evidence
Your CI/CD pipeline is itself evidence. Each pipeline run demonstrates that tests, linting, and security scans happened before deployment. Export pipeline run history:
# GitHub Actions: list workflow runs for the audit window
gh api \
--paginate \
"/repos/{owner}/{repo}/actions/runs?created=2025-10-01..2026-03-31&status=completed" \
--jq '.workflow_runs[] | {id: .id, name: .name, conclusion: .conclusion, created_at: .created_at, head_branch: .head_branch}' \
> ci-runs-evidence.json
Infrastructure-as-code as compliance evidence
IaC (Terraform, Pulumi, SST) is one of the strongest evidence sources for SOC 2. Instead of showing auditors a screenshot of a console setting, you show them a code file that is version-controlled, reviewed, and deployed through CI/CD.
# Terraform: RDS encryption and backup configuration
resource "aws_db_instance" "main" {
engine = "postgres"
instance_class = "db.t3.medium"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
backup_retention_period = 30
deletion_protection = true
# Enforce SSL connections
parameter_group_name = aws_db_parameter_group.enforce_ssl.name
}
resource "aws_db_parameter_group" "enforce_ssl" {
family = "postgres15"
parameter {
name = "rds.force_ssl"
value = "1"
}
}
This Terraform file, combined with its git history showing pull request reviews and CI checks, satisfies multiple controls simultaneously: encryption at rest, backup policy, deletion protection, and change management.
IaC drift detection is also valuable. If your infrastructure drifts from what is defined in code, that could indicate unauthorized changes. Run terraform plan on a schedule and alert on drift.
# GitHub Actions: weekly drift detection
name: Terraform Drift Detection
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9 AM
jobs:
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -detailed-exitcode -out=plan.out
id: plan
- name: Alert on drift
if: steps.plan.outputs.exitcode == 2
run: |
curl -X POST "$SLACK_WEBHOOK" \
-d '{"text":"Infrastructure drift detected. Review terraform plan output."}'
Common mistakes startups make during their first audit
Starting with policies instead of practices. Teams write 50-page security policies describing processes they do not actually follow. Auditors are not stupid. They will sample evidence and find the gap. Start with what you actually do, document it, then close genuine gaps.
Scoping too broadly. Including every system in scope increases evidence burden. Work with your auditor to define the scope narrowly around the system your customers care about. Your internal Slack bot does not need to be in scope.
Ignoring the observation window. For Type II, evidence must span the entire audit window. If you implement branch protection two months into a six-month window, you have four months of evidence and two months of gap. Auditors will note this as an exception.
Manual evidence collection. If evidence collection requires someone to log into five consoles and take screenshots quarterly, it will not happen consistently. Automate from day one.
Treating compliance as a one-time project. SOC 2 Type II is annual. The controls need to keep working after the audit. Teams that sprint to compliance and then relax their practices spend the same effort every year scrambling before the next audit window.
No designated owner. Without a clear owner (often a senior engineer or engineering manager with “compliance” added to their responsibilities), tasks fall through cracks. Somebody needs to own the evidence collection pipeline and the relationship with the auditor.
Tools that help
Three platforms dominate the compliance automation space: Vanta, Drata, and Secureframe. They all do roughly the same thing: connect to your infrastructure (AWS, GitHub, Google Workspace, HR systems) via API, continuously collect evidence, map it to SOC 2 controls, and flag gaps.
| Aspect | Vanta | Drata | Secureframe |
|---|---|---|---|
| Integrations | 300+ (broadest) | 200+ | 200+ |
| Pricing (annual) | ~$15K-$25K for startups | ~$12K-$20K for startups | ~$10K-$18K for startups |
| Strengths | Largest integration library, strong vendor risk module | Clean UI, good custom control support | Lower starting price, good for smaller teams |
| Auditor network | Yes (built-in) | Yes | Yes |
| Trust center | Included | Included | Included |
All three are worth evaluating. The real value is not the dashboard but the automated evidence collection. Without a platform, you are building and maintaining evidence collection scripts yourself, which is feasible but takes meaningful engineering time.
A reasonable approach: use the platform for continuous monitoring and evidence aggregation, but understand what it is actually checking. These tools sometimes give false confidence. A green checkmark on “encryption at rest” might mean the tool verified S3 default encryption exists but did not check your self-managed MongoDB cluster.
Timeline and cost for a startup
A realistic timeline for a 10-30 person startup going from zero to SOC 2 Type II report:
| Phase | Duration | What happens |
|---|---|---|
| Readiness assessment | 2-4 weeks | Gap analysis, scope definition, choose auditor and platform |
| Remediation | 4-8 weeks | Close gaps, implement missing controls, write policies |
| Type I (optional) | 2-4 weeks | Point-in-time audit, get initial report |
| Observation window | 3-6 months | Controls operate, evidence collects continuously |
| Type II audit | 4-6 weeks | Auditor reviews evidence, samples controls, writes report |
Total calendar time: 6-12 months from kickoff to Type II report.
Cost breakdown:
- Compliance platform (Vanta/Drata/Secureframe): $10K-$25K/year
- Auditor fees: $15K-$40K (depends on scope and firm)
- Engineering time: 1-2 engineers, 20-40% capacity for 2-3 months during remediation, then 5-10% ongoing
- Penetration test: $5K-$15K (required annually)
Total first-year cost: roughly $40K-$80K for a startup, with the biggest hidden cost being engineering time.
Maintaining compliance without bureaucracy
The goal is compliance as a byproduct of good engineering practices, not compliance as a separate workstream.
Embed controls in your workflow. Branch protection is not a compliance control you perform; it is a GitHub setting that exists permanently. Require PR reviews not because SOC 2 says so, but because it catches bugs. The compliance evidence is a side effect.
Automate access reviews. Instead of a quarterly spreadsheet exercise, run a script that pulls current access from AWS IAM, GitHub org membership, and your IdP, diffs it against your expected roster, and creates a ticket for anomalies.
# Quarterly access review automation
#!/bin/bash
EXPECTED_USERS="users.csv" # from HR system export
# Get current GitHub org members
gh api --paginate /orgs/{org}/members --jq '.[].login' | sort > github_current.txt
# Get current AWS IAM users
aws iam list-users --query 'Users[].UserName' --output text | tr '\t' '\n' | sort > aws_current.txt
# Diff against expected roster
comm -23 github_current.txt <(cut -d',' -f2 "$EXPECTED_USERS" | sort) > github_orphaned.txt
comm -23 aws_current.txt <(cut -d',' -f3 "$EXPECTED_USERS" | sort) > aws_orphaned.txt
if [ -s github_orphaned.txt ] || [ -s aws_orphaned.txt ]; then
echo "Orphaned accounts found. Creating review ticket."
# Create Jira/Linear ticket with findings
fi
Use policy-as-code. Tools like Open Policy Agent (OPA) or AWS Config Rules let you define compliance requirements as code that runs continuously, not as documents that get checked annually.
Make the compliance platform someone’s responsibility, not everyone’s. One engineer owns the dashboard, triages alerts, and runs quarterly reviews. Everyone else follows the normal engineering workflow, which already satisfies the controls because you designed it that way.
Keep policies short and honest. A 3-page incident response policy that you actually follow is worth more than a 30-page policy copied from a template that nobody has read. Auditors respect concise, accurate documentation.
The bottom line
SOC 2 compliance for engineering teams comes down to three things: doing what you probably already do (code reviews, access controls, encryption, logging), proving it consistently over time (automated evidence collection), and documenting it accurately (policies that match reality).
The teams that struggle are the ones that treat SOC 2 as a separate bureaucratic exercise. The teams that handle it well recognize that most SOC 2 controls are just good engineering practices with an audit trail attached. Build the audit trail into your existing workflow, automate evidence collection, and the annual audit becomes a non-event rather than a scramble.
More in DevOps
How Terraform Works Internally: HCL Parsing, Dependency Graph Execution, and the Provider Protocol Behind Infrastructure as Code
A deep dive into Terraform's internal architecture covering HCL parsing, the expression evaluation engine, DAG-based dependency resolution, the gRPC provider plugin protocol, state mechanics, and the plan/apply lifecycle.
How Docker Works Internally: Namespaces, Cgroups, Union Filesystems, and the OCI Runtime Behind Every Container
A deep dive into what happens when you run docker run: Linux namespaces for process isolation, cgroups v2 for resource enforcement, overlay2 union filesystem for layered images, the OCI runtime spec and how runc spawns containers, content-addressable storage, the containerd shim architecture, and networking with veth pairs.
How Kubernetes Works: Pods, Scheduling, and the Reconciliation Loop
A deep dive into Kubernetes internals covering the control plane architecture, etcd watch mechanics, the scheduler's filter and score phases, controller manager reconciliation loops, kubelet pod assignment, the CRI and CNI plugin models, and production considerations for resource requests, pod disruption budgets, and cluster autoscaler behavior.
How Docker Works: Namespaces, Cgroups, Union Filesystems, and the Container Runtime from Build to Execution
A deep dive into Docker internals covering Linux namespace isolation, cgroup resource constraints, OverlayFS copy-on-write layer mechanics, Dockerfile build cache invalidation rules, the containerd and runc OCI runtime stack, and production considerations for image hardening and startup latency.