Published on

SOC 2 Compliance for Backend Engineers — What You Actually Need to Build

Authors

Introduction

SOC 2 is mandatory for enterprise SaaS. It''s not about "being secure"—it''s about proving you''re secure to auditors. That means evidence: access logs, change management records, encryption keys, incident reports.

This post covers what SOC 2 Type II actually requires, what to build, how to automate evidence collection, and what to expect during audit.

SOC 2 Type II Requirements for Engineering

SOC 2 has five trust principles: CC (Common Criteria), C (Confidentiality), I (Integrity), A (Availability), R (Restricted Use).

Most SaaS pursue Type II CC+C+I (Confidentiality and Integrity, not Availability).

Auditors check:

  1. Access Control (CC7): Who can access systems? Evidence?
  2. Encryption (CC6): Data encrypted at rest and in transit?
  3. Change Management (CC8): How do code changes get deployed? Is there approval?
  4. Incident Response (SI1): Did you detect and respond to breaches?
  5. Monitoring (SI3): Are you logging everything? Do you alert on anomalies?
  6. Vulnerability Management (VS1): Do you scan for vulnerabilities?

What Auditors Actually Look At

Auditors review:

  • CloudTrail/audit logs for 3+ months
  • Git commits and PR reviews
  • Deployment records
  • Security patches applied
  • Access requests and approvals
  • Incident response runbooks
  • Password policies
  • MFA enforcement
  • Vulnerability scan results

They don''t care about code quality or architecture. They care about: Is this documented? Can you prove it happened?

Automated Evidence Collection

Don''t manually compile evidence. Automate it.

1. CloudTrail for AWS actions:

aws cloudtrail start-logging --trail-name my-trail
aws s3api put-bucket-versioning --bucket audit-logs --versioning-configuration Status=Enabled

2. GitHub actions for deployment records:

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm test
      - run: |
          echo "Deployment: $(date)" >> deployment.log
          echo "Commit: $(git rev-parse HEAD)" >> deployment.log
          echo "Author: $(git log -1 --pretty=%an)" >> deployment.log
          aws s3 cp deployment.log s3://audit-logs/deployments/
      - run: npm run deploy

3. Query logs for evidence:

// Audit log schema
interface AuditLog {
  timestamp: Date;
  actor: string;
  action: string; // 'user_created', 'password_changed', 'api_key_rotated'
  resource: string;
  result: 'success' | 'failure';
  ipAddress: string;
}

// Query for 90 days of access logs
const logs = await db.auditLogs.findMany({
  where: {
    timestamp: {
      gte: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000),
    },
  },
});

// Export for auditor
const csv = logs
  .map((log) => `${log.timestamp},${log.actor},${log.action},${log.resource}`)
  .join('\n');

fs.writeFileSync('audit-log-export.csv', csv);

Encryption at Rest

All persistent data must be encrypted.

RDS (PostgreSQL):

aws rds create-db-instance \
  --db-instance-identifier my-db \
  --engine postgres \
  --storage-encrypted \
  --kms-key-id arn:aws:kms:us-east-1:ACCOUNT:key/KEY_ID

S3:

aws s3api put-bucket-encryption \
  --bucket my-bucket \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "aws:kms",
          "KMSMasterKeyID": "arn:aws:kms:us-east-1:ACCOUNT:key/KEY_ID"
        }
      }
    ]
  }'

DynamoDB:

aws dynamodb update-table \
  --table-name my-table \
  --sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=KEY_ID

Enable encryption on all services. Keep KMS keys in a separate AWS account.

Encryption in Transit

All network traffic must be encrypted. TLS 1.3 minimum.

// Express with TLS 1.3
import https from 'https';
import fs from 'fs';

const options = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem'),
  minVersion: 'TLSv1.3',
};

https.createServer(options, app).listen(443);

AWS ALB:

aws elbv2 modify-listener \
  --listener-arn arn:aws:elasticloadbalancing:... \
  --protocol HTTPS \
  --ssl-policy ELBSecurityPolicy-TLS-1-2-2017-01 # Use TLS 1.3 policy

Test with ssl-labs.com or testssl.sh.

Access Control Review Automation

Quarterly audit: do users still need access?

// List all users with access
interface AccessRecord {
  userId: string;
  email: string;
  role: string;
  lastLogin: Date;
  grantedAt: Date;
}

export async function auditAccess() {
  const users = await db.users.findMany({
    include: { roles: true },
  });

  const report = users.map((user) => ({
    userId: user.id,
    email: user.email,
    role: user.roles.map((r) => r.name).join(','),
    lastLogin: user.lastLogin,
    daysInactive: Math.floor((Date.now() - user.lastLogin.getTime()) / (1000 * 60 * 60 * 24)),
  }));

  // Flag users inactive for 90+ days
  const risky = report.filter((r) => r.daysInactive > 90);

  // Send to security team for review
  await sendEmail({
    to: 'security@example.com',
    subject: 'Access Control Review',
    body: `${risky.length} users inactive for 90+ days. Review and remove access.`,
  });

  return report;
}

// Run quarterly
schedule.scheduleJob('0 0 1 * *', auditAccess); // First of every month

Automated reports reduce manual work.

Security Monitoring with AWS GuardDuty and CloudTrail

GuardDuty detects threats. CloudTrail records all actions.

# Enable GuardDuty
aws guardduty create-detector --enable

# Review findings
aws guardduty list-findings --detector-id DETECTOR_ID --finding-criteria '{
  "Criterion": {
    "severity": { "Gte": 7 }
  }
}'

Set up alerts:

// Lambda function triggered by GuardDuty findings
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';

export async function handler(event: any) {
  const finding = event.detail;

  if (finding.severity >= 7) {
    const sns = new SNSClient({});
    await sns.send(
      new PublishCommand({
        TopicArn: 'arn:aws:sns:us-east-1:ACCOUNT:security-alerts',
        Subject: `Security Alert: ${finding.type}`,
        Message: JSON.stringify(finding, null, 2),
      })
    );
  }
}

Vulnerability Management Pipeline

Scan dependencies and container images. Fail the build if critical vulnerabilities exist.

# GitHub Actions
name: Security Scan

on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm audit --audit-level=moderate
      - run: npm install -g snyk && snyk test
      - run: docker build -t app:latest . && docker run --rm aquasec/trivy image app:latest

Incident Response Runbook

Document how you respond to breaches.

# Incident Response Runbook

## Detection Phase
1. GuardDuty alert triggers Lambda
2. Security team notified via Slack
3. Investigate: Is it a false positive or real threat?

## Containment Phase
1. Revoke compromised API keys
2. Force password reset for affected users
3. Block attacker IP if possible
4. Enable enhanced logging

## Eradication Phase
1. Identify root cause
2. Patch vulnerability
3. Remove attacker access
4. Verify logs confirm removal

## Recovery Phase
1. Restore from clean backup if needed
2. Update incident log
3. Monitor for re-compromise
4. Post-mortem with team

## Communication Phase
1. Notify affected users
2. Notify legal and insurance
3. Prepare public statement
4. Document everything

## Timeline
- T+0: Detection
- T+5 min: Containment
- T+2 hours: Root cause identified
- T+24 hours: Eradication complete
- T+7 days: Post-mortem

Keep this updated. Auditors will ask.

SOC 2 Tools

Vanta: Automates evidence collection. ~$500/mo.

# Vanta integrates with AWS, GitHub, Okta
# Automatically pulls CloudTrail, Git commits, access logs

Drata: Similar to Vanta. ~$400/mo.

Tugboat Logic: DIY approach. ~$200/mo + engineering time.

Manual: Spreadsheets and manual reviews. Free but painful.

For early-stage startups, use Vanta or Drata. For scale-ups with strong DevOps, invest in custom automation.

Timeline and Cost

For small team (1-5 engineers):

  • Preparation: 2-3 months
  • Audit: 1-2 months
  • Cost: $20k-40k (tool subscriptions + auditor fees)
  • Total: 4-5 months

For medium team (5-15 engineers):

  • Preparation: 1-2 months
  • Audit: 1 month
  • Cost: $30k-60k
  • Total: 3-4 months

Ongoing maintenance:

  • Monthly: Access control reviews
  • Quarterly: Vulnerability scans, log reviews
  • Annually: Re-audit (refresh audit)

Checklist

  • Enable CloudTrail and S3 versioning
  • Enable RDS/S3/DynamoDB encryption with KMS
  • Enforce TLS 1.3 on all endpoints
  • Enable GuardDuty and configure alerts
  • Implement audit logging in application
  • Set up automated access control reviews
  • Run Snyk and Trivy in CI
  • Create incident response runbook
  • Choose SOC 2 tool (Vanta, Drata, or manual)
  • Schedule audit 3-4 months out

Conclusion

SOC 2 Type II is not hard, but it''s tedious. The hard part isn''t building security—it''s proving you built it. Automate evidence collection with tools like Vanta or Drata. Document your processes. Keep audit logs for 90+ days. Rotate credentials and review access quarterly. When auditors come, you''ll have the receipts.