Mei is the CTO of a 25-person SaaS startup that just landed their first enterprise prospect. The deal is worth $400K/year, but the procurement team requires SOC 2 Type II certification. Mei has heard horror stories: 6+ months of preparation, $50K+ in consulting fees, teams of people filling spreadsheets. She doesn't have the budget for a compliance team, so she automates everything — every control is enforced via code, every policy is a CI check, every audit question has an automated answer.
The Problem: 80+ Controls, Zero Compliance Team
SOC 2 Type II requires demonstrating that security controls work continuously over a review period (typically 6-12 months). The five trust service criteria — Security, Availability, Processing Integrity, Confidentiality, and Privacy — translate into 80+ specific controls. Doing this manually means:
- Reviewing access permissions quarterly (and proving it)
- Scanning for vulnerabilities regularly (and documenting results)
- Managing secrets securely (and showing rotation logs)
- Monitoring infrastructure for anomalies (and keeping incident records)
- Maintaining change management processes (and tracking every deploy)
Mei's approach: make compliance a side effect of good engineering practices, all codified and automated.
Control 1: Secure Code — Static Analysis in CI
Every pull request is automatically scanned for security vulnerabilities before merge. This satisfies multiple SOC 2 controls around secure development practices.
# .github/workflows/security-scan.yml
name: Security Scan
on: pull_request
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST scan
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
p/typescript
generateSarif: true
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
# Fail PR if critical/high findings
- name: Check for blocking findings
run: |
CRITICAL=$(cat semgrep.sarif | jq '[.runs[].results[] | select(.level == "error")] | length')
if [ "$CRITICAL" -gt 0 ]; then
echo "❌ $CRITICAL critical findings. Fix before merging."
exit 1
fi
The audit evidence is automatic: every PR shows the scan results, every merge proves the code was reviewed and scanned. No spreadsheet needed.
Control 2: Infrastructure Security — IaC Scanning
Infrastructure misconfigurations (public S3 buckets, unencrypted databases, overly permissive IAM) are caught before they reach production:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkov IaC scan
uses: bridgecrewio/checkov-action@v12
with:
directory: ./infrastructure
framework: terraform,dockerfile,kubernetes
output_format: sarif
soft_fail: false
# SOC 2 relevant checks
check: >-
CKV_AWS_18,CKV_AWS_19,CKV_AWS_20,CKV_AWS_21,
CKV_AWS_145,CKV_AWS_149,CKV_AWS_300
# Generate compliance report
- name: Compliance report
run: |
checkov -d ./infrastructure --framework terraform \
--output json > /tmp/checkov-report.json
# Store as audit artifact
aws s3 cp /tmp/checkov-report.json \
s3://audit-artifacts/checkov/$(date +%Y-%m-%d)/report.json
Every infrastructure change is scanned, and results are stored in an audit bucket with timestamps. When the auditor asks "how do you ensure infrastructure is configured securely?", Mei shows them the CI pipeline and the artifact bucket.
Control 3: Secrets Management
No hardcoded secrets, rotating credentials, audit logs for every access:
// lib/config.ts — All secrets from Infisical, never from env files
import { InfisicalClient } from "@infisical/sdk";
const infisical = new InfisicalClient({
siteUrl: "https://vault.ourcompany.com",
auth: { universalAuth: { clientId: process.env.INFISICAL_CLIENT_ID!, clientSecret: process.env.INFISICAL_CLIENT_SECRET! } },
});
export async function getSecrets() {
const secrets = await infisical.listSecrets({
environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
projectId: process.env.INFISICAL_PROJECT_ID!,
});
return {
databaseUrl: secrets.find(s => s.secretName === "DATABASE_URL")!.secretValue,
stripeKey: secrets.find(s => s.secretName === "STRIPE_SECRET_KEY")!.secretValue,
jwtSecret: secrets.find(s => s.secretName === "JWT_SECRET")!.secretValue,
};
}
// Infisical provides:
// - Audit log of every secret access (who, when, which secret)
// - Automatic rotation reminders
// - Secret versioning (rollback if rotation breaks something)
// - IP allowlisting (secrets only accessible from production IPs)
The SOC 2 evidence: Infisical's audit log shows every secret access with timestamps, user IDs, and IP addresses. Automatic rotation policies prove secrets are rotated quarterly.
Control 4: Access Control Reviews
Instead of quarterly manual reviews, access is defined as code and continuously validated:
// scripts/access-audit.ts — Runs weekly via cron
// Generates evidence for SOC 2 access review controls
async function auditAccess() {
const report: AuditReport = {
date: new Date().toISOString(),
findings: [],
summary: { totalUsers: 0, activeUsers: 0, inactiveUsers: 0, excessivePermissions: 0 },
};
// Check GitHub org members
const githubMembers = await octokit.orgs.listMembers({ org: "ourcompany" });
const activeEmployees = await hr.getActiveEmployees();
const activeEmails = new Set(activeEmployees.map(e => e.email));
for (const member of githubMembers.data) {
const profile = await octokit.users.getByUsername({ username: member.login });
if (!activeEmails.has(profile.data.email)) {
report.findings.push({
severity: "high",
type: "orphaned-account",
detail: `GitHub user ${member.login} (${profile.data.email}) not found in active employee list`,
recommendation: "Remove from GitHub organization",
});
}
}
// Check AWS IAM for excessive permissions
const iamUsers = await iam.listUsers({}).promise();
for (const user of iamUsers.Users) {
const policies = await iam.listAttachedUserPolicies({ UserName: user.UserName }).promise();
const hasAdmin = policies.AttachedPolicies.some(p => p.PolicyName === "AdministratorAccess");
if (hasAdmin) {
report.findings.push({
severity: "medium",
type: "excessive-permission",
detail: `IAM user ${user.UserName} has AdministratorAccess`,
recommendation: "Replace with least-privilege policy",
});
report.summary.excessivePermissions++;
}
}
// Store report for auditor
await s3.putObject({
Bucket: "audit-artifacts",
Key: `access-reviews/${new Date().toISOString().split("T")[0]}/report.json`,
Body: JSON.stringify(report, null, 2),
}).promise();
// Alert on findings
if (report.findings.some(f => f.severity === "high")) {
await slack.send("#security", `⚠️ Access audit found ${report.findings.length} issues. Review: ${reportUrl}`);
}
return report;
}
Control 5: Continuous Monitoring and Incident Response
All services report to a centralized observability stack. Anomalies trigger alerts, and every alert creates an incident record:
// lib/monitoring.ts — Structured incident logging
import { trace, metrics } from "@opentelemetry/api";
const meter = metrics.getMeter("compliance");
const incidentCounter = meter.createCounter("security_incidents", {
description: "Security incidents detected",
});
async function handleSecurityAlert(alert: SecurityAlert) {
incidentCounter.add(1, { severity: alert.severity, type: alert.type });
// Create incident record (SOC 2 requires documented incident response)
const incident = await db.incidents.create({
data: {
alertId: alert.id,
severity: alert.severity,
type: alert.type,
description: alert.description,
status: "open",
detectedAt: new Date(),
assignedTo: getOnCallEngineer(),
},
});
// Notify
await slack.send("#incidents", {
text: `🚨 Security incident INC-${incident.id}: ${alert.description}`,
blocks: [
{ type: "section", text: { type: "mrkdwn", text: `*Severity:* ${alert.severity}\n*Type:* ${alert.type}\n*Assigned:* ${incident.assignedTo}` } },
{ type: "actions", elements: [
{ type: "button", text: { type: "plain_text", text: "Acknowledge" }, action_id: `ack-${incident.id}` },
] },
],
});
return incident;
}
Results
Mei's team achieves SOC 2 Type II certification in 4 months (vs. typical 6-12 months), spending $12K total (vs. $50K+ typical).
- Audit readiness: Auditor accessed artifact bucket and reviewed CI pipelines; 90% of evidence was already collected automatically
- Findings: Zero critical findings; 3 minor observations (all addressed same week)
- Time investment: 2 engineers, ~4 hours/week each for 4 months; not a full-time compliance role
- CI security: Semgrep caught 47 vulnerabilities in PRs before merge over the review period (all documented)
- IaC scanning: Checkov prevented 12 infrastructure misconfigurations from reaching production
- Secret hygiene: Zero hardcoded secrets found in codebase; all rotated on schedule
- Access reviews: Weekly automated reviews caught 2 orphaned accounts within 1 day of employee departure
- Incident response: 8 security alerts during review period, all acknowledged within 15 minutes, resolved within 4 hours
- Enterprise deal: Closed the $400K contract 3 weeks after certification; 2 more enterprise prospects in pipeline
- Ongoing effort: <2 hours/week to maintain compliance; everything is automated and continuous