CI/CD Compliance: Controls Across the Delivery Pipeline
This guide maps control work to individual delivery stages and shows how to organize the evidence each stage produces so it is reviewable at audit time.
Most engineering teams treat compliance as a documentation exercise. They write a change management policy, file it in a shared drive, and then continue deploying the way they always have, merging to main, running a build, shipping to production. When the auditor arrives, someone scrambles to reconstruct evidence from Git logs, Slack messages, and memory.
This approach fails. SOC 2 Trust Services Criteria CC8.1 (Change Management) and ISO 27001 Annex A controls A.8.25 through A.8.33 require documented, repeatable, and verifiable controls over how code moves from a developer's machine to production. An auditor does not want a policy document that says "we do code reviews." An auditor wants timestamped proof that every production change went through a defined approval workflow, passed automated security checks, and was logged in an immutable audit trail.
The good news: if you build your CI/CD pipeline correctly, compliance evidence generates itself. Every pull request, every scan result, every approval, every deployment becomes an audit artifact, produced automatically, stored permanently, and queryable on demand. This guide maps specific CI/CD pipeline stages to SOC 2 CC8 and ISO 27001 Annex A controls, with concrete tool configurations, code examples, and implementation patterns for GitHub Actions, GitLab CI, and ArgoCD.
The Control Mapping: CI/CD Stages to Audit Requirements
Before building anything, understand exactly which audit controls map to which pipeline stages. This table is the foundation for every design decision that follows.
| CI/CD Pipeline Stage | SOC 2 TSC Control | ISO 27001 Annex A Control | What the Auditor Expects |
|---|---|---|---|
| Branch protection / PR requirements | CC8.1 (Changes are authorized) | A.8.25 (Secure development lifecycle) | Evidence that changes require approval before merge |
| Code review and approval workflows | CC8.1 (Authorized changes), CC6.1 (Logical access) | A.8.4 (Access to source code), A.8.25 | Timestamped review approvals from authorized personnel |
| SAST (Static Application Security Testing) | CC7.1 (Identify vulnerabilities), CC8.1 | A.8.25, A.8.28 (Secure coding) | Scan results with pass/fail criteria; evidence that failures block deployment |
| SCA (Software Composition Analysis) | CC7.1, CC8.1 | A.8.28, A.8.25 | Dependency vulnerability reports; SBOM generation |
| Secrets scanning | CC6.1 (Logical access controls), CC6.7 | A.8.4, A.8.11 (Data masking) | Evidence that secrets cannot enter the codebase |
| IaC scanning | CC8.1, CC7.1 | A.8.25, A.8.9 (Configuration management) | Terraform/CloudFormation scans proving infrastructure changes are validated |
| Container image scanning | CC7.1 (Vulnerability identification) | A.8.25, A.8.28 | CVE scan results for every container image deployed to production |
| DAST (Dynamic Application Security Testing) | CC7.1 | A.8.25, A.8.29 (Security testing in dev/acceptance) | Evidence that running applications are tested for runtime vulnerabilities |
| Approval gates before production | CC8.1 (Changes are authorized) | A.8.25, A.8.31 (Separation of dev/test/prod) | Manual or automated approval records before production deployment |
| Immutable deployment logging | CC8.1, CC7.2 (Monitor for anomalies) | A.8.15 (Logging), A.8.25 | Tamper-proof records of what was deployed, when, by whom |
| Rollback capability | CC8.1 (Changes can be reversed) | A.8.25, A.8.32 (Change management) | Evidence that failed deployments can be and have been rolled back |
| Environment separation | CC8.1 | A.8.31 (Separation of dev/test/prod) | Distinct environments with independent access controls |
This is not a "nice-to-have" checklist. Every row represents a control point that a SOC 2 Type II auditor or an ISO 27001 certification body will evaluate. If you cannot produce evidence for a given row, you have a control gap.
What the Controls Actually Say
Auditors are not CI/CD experts. They will not read your workflow files line by line. They ask questions that map to specific control requirements and expect evidence that those controls are enforced consistently. It helps to know what the underlying requirements say in plain language.
SOC 2 CC8.1 (Changes to infrastructure and software). The organization authorizes, designs, develops or acquires, configures, documents, tests, approves, and implements changes to infrastructure, data, software, and procedures. In practice: every production change is authorized by someone other than the author, tested by automated checks that pass, and documented with a record of what changed, who approved it, and when it was deployed.
SOC 2 CC7.1 (Detection and monitoring). The organization detects and monitors for security events and vulnerabilities. In the pipeline, this is your SAST, SCA, secrets, IaC, and container scanning.
SOC 2 CC6.1 (Logical access). Controls over who can access what. For CI/CD this means who can merge to protected branches, who can trigger production deployments, and who can modify pipeline configuration.
ISO 27001 A.8.25 (Secure development life cycle). Rules for secure development of software and systems are established and applied: code review, testing, security scanning, and change management.
ISO 27001 A.8.26 (Application security requirements) and A.8.27 (Secure system architecture and engineering principles). Security requirements are identified and approved when developing or acquiring applications, and secure engineering principles are documented and applied to development activity.
ISO 27001 A.8.28 (Secure coding). Secure coding principles are applied. Auditors look for SAST results, code review practice, and enforced coding standards.
ISO 27001 A.8.32 (Change management). Changes to information processing facilities and systems follow a change management procedure, including the handling of failed changes.
Stage 1: Branch Protection and Access Controls
The pipeline starts before any code is written. Branch protection rules are the first control an auditor evaluates because they enforce the principle that no single individual can push unreviewed code to production.
What the Frameworks Require
SOC 2 CC8.1 requires that changes to infrastructure and software are authorized through a defined process. A developer pushing directly to main and triggering a production deployment is, by definition, an unauthorized change, even if the developer is the CTO.
ISO 27001 A.8.4 requires restricted access to source code. A.8.25 requires a secure development lifecycle that includes defined rules for code changes.
GitHub Implementation
# .github/settings.yml (using probot/settings or GitHub API)
# Alternatively, configure via Settings > Branches > Branch protection rules
branches:
- name: main
protection:
required_pull_request_reviews:
required_approving_review_count: 2
dismiss_stale_reviews: true
require_code_owner_reviews: true
require_last_push_approval: true
required_status_checks:
strict: true
contexts:
- "sast-scan"
- "sca-scan"
- "secrets-scan"
- "iac-scan"
- "unit-tests"
enforce_admins: true
required_linear_history: true
allow_force_pushes: false
allow_deletions: false
required_conversation_resolution: true
The critical settings for audit purposes:
enforce_admins: truemeans even repository administrators cannot bypass protection rules. Without this, an auditor will flag that privileged users can circumvent change management.dismiss_stale_reviews: trueensures that if a developer pushes new commits after receiving approval, the approval resets and a new review is required.require_last_push_approval: trueprevents a developer from approving their own final push.required_status_checks.strict: truerequires the branch to be up to date withmainbefore merging, preventing a class of issues where checks passed on a stale branch but fail on the merged result.
GitLab Implementation
# Configure via Settings > Repository > Protected Branches
# Or via GitLab API:
# POST /api/v4/projects/:id/protected_branches
{
"name": "main",
"push_access_level": 0,
"merge_access_level": 40,
"allow_force_push": false,
"code_owner_approval_required": true
}
# Approval rules via Settings > Merge Requests
# POST /api/v4/projects/:id/approval_rules
{
"name": "Security Review",
"approvals_required": 2,
"rule_type": "regular",
"user_ids": [],
"group_ids": [<security-team-group-id>]
}
Audit evidence produced: Branch protection configuration exports, access control logs showing who has write access to protected branches, merge request approval history.
Stage 2: SAST and Secure Coding Analysis
Static Application Security Testing scans source code for vulnerabilities before the application is built. This is the most direct evidence of ISO 27001 A.8.28 (Secure coding) and SOC 2 CC7.1 (vulnerability identification).
GitHub Actions Implementation
# .github/workflows/security-scan.yml
name: Security Scans
on:
pull_request:
branches: [main, develop]
jobs:
sast-scan:
name: SAST - Semgrep
runs-on: ubuntu-latest
container:
image: semgrep/semgrep:latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep SAST
run: |
semgrep ci \
--config "p/owasp-top-ten" \
--config "p/cwe-top-25" \
--config "p/security-audit" \
--sarif --output semgrep-results.sarif \
--error # Exit code 1 on findings = blocks merge
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
- name: Upload SARIF to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep-results.sarif
- name: Archive scan results
if: always()
uses: actions/upload-artifact@v4
with:
name: sast-results-${{ github.sha }}
path: semgrep-results.sarif
retention-days: 2555 # 7 years for SOC 2 evidence retention
GitLab CI Implementation
# .gitlab-ci.yml
sast-scan:
stage: test
image: semgrep/semgrep:latest
script:
- semgrep ci
--config "p/owasp-top-ten"
--config "p/cwe-top-25"
--config "p/security-audit"
--json --output gl-sast-report.json
--error
artifacts:
reports:
sast: gl-sast-report.json
paths:
- gl-sast-report.json
expire_in: 7 years
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
Tool alternatives: CodeQL (GitHub-native, strong for compiled languages), SonarQube (broad language support, quality + security), Checkmarx (enterprise-grade, supports compliance-specific rule sets). Semgrep is recommended for most teams because its rules are open source, its configuration is declarative, and it produces SARIF output that integrates directly with GitHub Security.
SAST tools by language ecosystem:
| Tool | Languages | Integration |
|---|---|---|
| Semgrep | Python, JavaScript, Go, Java, Ruby, and more | GitHub Actions, GitLab CI, CLI |
| SonarQube / SonarCloud | Broad multi-language support | GitHub Actions, GitLab CI, Jenkins |
| CodeQL | C/C++, C#, Go, Java, JavaScript, Python, Ruby | Native GitHub Actions |
| Checkmarx | Enterprise multi-language | Compliance-specific rule sets |
| Bandit | Python | GitHub Actions, GitLab CI, CLI |
| Brakeman | Ruby on Rails | GitHub Actions, GitLab CI, CLI |
Whichever tool you pick, upload results in SARIF format. The SARIF upload creates a persistent, auditable record of scan results in your repository's security tab, which is exactly the artifact an auditor asks for.
The audit requirement: The scan must run on every change. Results must be stored. Findings above a defined severity threshold must block the merge. If you allow developers to override findings without a documented exception process, the control is ineffective.
Stage 3: Software Composition Analysis and SBOM Generation
SCA identifies known vulnerabilities in third-party dependencies. SOC 2 CC7.1 and ISO 27001 A.8.28 both require that organizations identify and remediate vulnerabilities in software components, including components they did not write.
sca-scan:
name: SCA - Dependency Review
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
deny-licenses: "GPL-3.0, AGPL-3.0"
allow-ghsas: "" # No advisory bypasses without documented exception
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: spdx-json
output-file: sbom-${{ github.sha }}.spdx.json
- name: Scan SBOM with Grype
uses: anchore/scan-action@v4
with:
sbom: sbom-${{ github.sha }}.spdx.json
fail-build: true
severity-cutoff: high
output-format: sarif
- name: Archive SBOM
uses: actions/upload-artifact@v4
with:
name: sbom-${{ github.sha }}
path: sbom-${{ github.sha }}.spdx.json
retention-days: 2555
The SBOM (Software Bill of Materials) is increasingly important for audits. It provides a machine-readable inventory of every dependency in your application, which directly satisfies ISO 27001 A.8.9 (Configuration management) and supports SOC 2 CC8.1 evidence by documenting the full composition of every release.
Stage 4: Secrets Scanning
Hardcoded secrets in source code are among the most common SOC 2 findings. SOC 2 CC6.1 (Logical access controls) and CC6.7 (Restricting the transmission of confidential information) both apply. ISO 27001 A.8.4 (Access to source code) and A.8.11 (Data masking) are the corresponding controls.
Pre-commit + CI Implementation
Secrets scanning must run in two places: as a pre-commit hook (catching secrets before they enter Git history) and in CI (catching anything the pre-commit hook missed).
secrets-scan:
name: Secrets Detection - Gitleaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for scanning all commits in the PR
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
with:
args: --log-opts="origin/main..HEAD"
env:
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
GITLEAKS_NOTIFY_USER_LIST: "@security-team"
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: secrets-scan-${{ github.sha }}
path: results.sarif
retention-days: 2555
Pre-commit hook (developer machine):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
Tool alternatives: TruffleHog (excellent entropy-based detection), GitHub Advanced Security secret scanning (native integration, partner pattern detection), GitLab Secret Detection (built into GitLab Ultimate). Gitleaks is recommended for teams that need a tool that works across any CI system, is fully open source, and supports custom regex patterns for organization-specific secret formats.
Secrets scanning tools compared:
| Tool | Approach | Where It Runs |
|---|---|---|
| GitHub Secret Scanning | Native GitHub feature with partner pattern detection | Automatic for public repos; private repos with Advanced Security |
| Gitleaks | Open source, regex-based with custom patterns | GitHub Actions, GitLab CI, pre-commit hook |
| TruffleHog | Open source, entropy plus regex detection | GitHub Actions, GitLab CI, CLI |
| detect-secrets (Yelp) | Open source, plugin-based | Pre-commit hook, CI/CD |
| GitLab Secret Detection | Built into GitLab Ultimate | GitLab CI |
Running a scanner in both places gives you a preventive control (the hook) and a detective control (the CI job). Auditors like to see both.
Critical configuration: The .gitleaks.toml file must be reviewed and maintained. Allowlisting paths or patterns requires a documented exception with a justification. Auditors will check whether your allowlist effectively disables the scanner.
Stage 5: Infrastructure as Code Scanning
If your infrastructure is defined in Terraform, CloudFormation, Pulumi, or Kubernetes manifests, those definitions are code, and they are subject to the same change management requirements as application code. SOC 2 CC8.1 and ISO 27001 A.8.9 (Configuration management) require that infrastructure changes go through a defined review and approval process.
iac-scan:
name: IaC Security - Checkov
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: ./infrastructure/
framework: terraform,kubernetes,dockerfile
output_format: cli,sarif
output_file_path: console,checkov-results.sarif
soft_fail: false # Hard fail = blocks merge on findings
skip_check: "" # No skips without documented exception
quiet: true
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov-results.sarif
Checkov evaluates IaC against hundreds of policies covering:
- S3 buckets without encryption (SOC 2 CC6.7, ISO 27001 A.8.24)
- Security groups with unrestricted ingress (SOC 2 CC6.6, ISO 27001 A.8.20)
- IAM policies with wildcard permissions (SOC 2 CC6.3, ISO 27001 A.8.3)
- RDS instances without encryption at rest (SOC 2 CC6.1, ISO 27001 A.8.24)
- Kubernetes pods running as root (SOC 2 CC6.1, ISO 27001 A.8.25)
Tool alternatives: tfsec (Terraform-specific, fast), KICS (Checkmarx, multi-framework), Trivy (Aqua Security, covers IaC + containers + SBOM in one scanner). Checkov is recommended because it covers the broadest set of IaC frameworks and outputs compliance-framework-specific results.
Stage 6: Container Image Scanning
If you deploy containers, every image must be scanned before it reaches production. SOC 2 CC7.1 and ISO 27001 A.8.28 require vulnerability identification in all software components, including base images and OS-level packages.
container-scan:
name: Container Image Scan - Trivy
runs-on: ubuntu-latest
needs: [build]
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/[email protected]
with:
image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1" # Fail build on CRITICAL or HIGH
ignore-unfixed: false
vuln-type: "os,library"
- name: Upload Trivy scan results
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
Audit evidence produced: CVE scan results for every container image, linked to the specific Git commit and deployment. Auditors will verify that images with critical vulnerabilities were not deployed to production, or, if they were, that a documented risk acceptance exists.
Stage 7: DAST in Staging Environments
Dynamic Application Security Testing runs against a deployed instance of your application, testing for runtime vulnerabilities that static analysis cannot detect. ISO 27001 A.8.29 specifically requires security testing in development and acceptance, DAST in a staging environment directly satisfies this control.
dast-scan:
name: DAST - OWASP ZAP
runs-on: ubuntu-latest
needs: [deploy-staging]
steps:
- name: OWASP ZAP Full Scan
uses: zaproxy/[email protected]
with:
target: "https://staging.yourapp.com"
rules_file_name: ".zap/rules.tsv"
cmd_options: >-
-j
-z "-config alert.maxInstances=10"
artifact_name: "zap-results-${{ github.sha }}"
allow_issue_writing: false
- name: Evaluate ZAP results
run: |
HIGH_ALERTS=$(jq '[.site[].alerts[] | select(.riskcode == "3")] | length' zap-results.json)
if [ "$HIGH_ALERTS" -gt 0 ]; then
echo "::error::DAST found $HIGH_ALERTS high-risk alerts. Blocking deployment."
exit 1
fi
DAST is typically the most time-consuming scan. Run it against staging after deployment, not in the PR pipeline. The results still gate production deployment through the approval workflow described in the next section.
Stage 8: Approval Gates and Deployment Authorization
This is the stage that most directly satisfies SOC 2 CC8.1's requirement for authorized changes and ISO 27001 A.8.31's requirement for separation of development, test, and production environments.
GitHub Environments with Required Reviewers
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy-staging:
environment: staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: ./scripts/deploy.sh staging
deploy-production:
needs: [deploy-staging, dast-scan]
environment:
name: production
url: https://app.yourcompany.com
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Verify all security scans passed
run: |
# Query GitHub API for all required check statuses
CHECKS=$(gh api repos/${{ github.repository }}/commits/${{ github.sha }}/check-runs \
--jq '.check_runs[] | select(.conclusion != "success") | .name')
if [ -n "$CHECKS" ]; then
echo "::error::The following checks have not passed: $CHECKS"
exit 1
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to production
run: ./scripts/deploy.sh production
- name: Record deployment metadata
run: |
echo '{
"sha": "${{ github.sha }}",
"deployer": "${{ github.actor }}",
"environment": "production",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"approvers": "${{ github.event.review.user.login }}",
"pipeline_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}' | tee deployment-record.json
- name: Upload deployment record
uses: actions/upload-artifact@v4
with:
name: deployment-record-${{ github.sha }}
path: deployment-record.json
retention-days: 2555
The environment: production key is critical. Configure the production environment in GitHub Settings with:
- Required reviewers: At least one member of the infrastructure or security team must approve before the deployment job runs.
- Wait timer: Optional delay (e.g., 15 minutes) to allow for last-minute objections.
- Deployment branches: Restrict to
mainonly, preventing deployments from feature branches.
Environment Separation Requirements
Approval gates only count if the environments they protect are genuinely separate. Auditors look for four things:
- Isolated environments. Development, staging, and production are distinct, and developers cannot reach production data from development environments. Infrastructure configuration enforces the separation; a policy document alone does not.
- Explicit deployment approval. Production deployments require sign-off from an authorized person, whether through a GitHub Environment reviewer, a GitLab protected environment, or a change advisory board process.
- A deployment audit trail. Every production deployment records who initiated it, who approved it, what was deployed (the commit SHA), and when.
- Rollback capability. The ability to revert a production deployment quickly, which also supports the Availability and Processing Integrity criteria.
GitOps with ArgoCD
For teams using GitOps, ArgoCD provides a declarative deployment model where the desired state of production is defined in a Git repository. ArgoCD continuously reconciles the cluster state with the repository state.
# argocd/applications/production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
annotations:
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: deployments
notifications.argoproj.io/subscribe.on-sync-failed.slack: deployments
spec:
project: production
source:
repoURL: https://github.com/yourorg/k8s-manifests.git
targetRevision: main
path: environments/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: false # Do not auto-delete resources, require manual action
selfHeal: true # Revert manual cluster changes to match Git state
syncOptions:
- CreateNamespace=false
- PrunePropagationPolicy=foreground
- ApplyOutOfSyncOnly=true
The compliance advantage of GitOps: Git is the audit trail. Every production change is a Git commit. Every Git commit has an author, a timestamp, a reviewer, and a diff. ArgoCD's sync history provides a complete record of when each commit was applied to the cluster and whether it succeeded or failed.
ArgoCD RBAC for separation of duties:
# argocd-rbac-cm ConfigMap
p, role:developer, applications, get, production/*, allow
p, role:developer, applications, sync, staging/*, allow
p, role:deployer, applications, sync, production/*, allow
p, role:deployer, applications, get, production/*, allow
g, dev-team, role:developer
g, sre-team, role:deployer
This configuration ensures that developers can view production applications but cannot trigger syncs. Only SRE team members can synchronize changes to production. This is a direct implementation of ISO 27001 A.8.31 (Separation of environments) and SOC 2 CC6.1 (Role-based access).
Stage 9: Immutable Audit Logging
Every stage described above produces artifacts. But artifacts are only useful for audits if they are immutable (cannot be altered after creation), centralized (queryable from a single location), and retained for the required period (SOC 2 typically requires 1 year of observation plus retention; ISO 27001 A.8.17 requires synchronized, tamper-evident logs).
Centralized Log Pipeline
# .github/workflows/audit-log-export.yml
name: Export Audit Logs
on:
schedule:
- cron: "0 2 * * *" # Daily at 2 AM UTC
jobs:
export-logs:
runs-on: ubuntu-latest
steps:
- name: Export GitHub Audit Log
run: |
gh api /orgs/${{ github.repository_owner }}/audit-log \
--paginate \
--jq '.[] | select(.created_at > (now - 86400 | todate))' \
> audit-log-$(date +%Y-%m-%d).json
env:
GH_TOKEN: ${{ secrets.AUDIT_LOG_TOKEN }}
- name: Ship to immutable storage
run: |
aws s3 cp audit-log-$(date +%Y-%m-%d).json \
s3://compliance-audit-logs/github/$(date +%Y/%m/%d)/ \
--storage-class GLACIER_IR
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AUDIT_AWS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AUDIT_AWS_SECRET }}
Immutability guarantees:
- S3 Object Lock (Compliance mode): Prevents any user, including the root account, from deleting or overwriting objects for the retention period. Configure a 7-year retention period for SOC 2 evidence.
- AWS CloudTrail with log file validation: Provides tamper-evident logging of all API calls to the S3 bucket itself, proving that no one modified the audit logs.
- Alternative: GCP Cloud Storage with Bucket Lock or Azure Blob Storage with immutability policies provide equivalent guarantees.
What to Log
The following data must be captured for every production deployment to satisfy auditor evidence requests:
| Data Point | Source | SOC 2 Control | ISO 27001 Control |
|---|---|---|---|
| Git commit SHA | Git / CI | CC8.1 | A.8.25 |
| Author of the change | Git commit metadata | CC8.1, CC6.1 | A.8.4, A.8.25 |
| Pull request reviewers and approvers | GitHub/GitLab API | CC8.1 | A.8.25 |
| All security scan results (SAST, SCA, DAST, secrets, IaC) | CI artifacts | CC7.1 | A.8.25, A.8.28, A.8.29 |
| Approval timestamp for production deployment | CI environment approval | CC8.1 | A.8.25 |
| Deployment timestamp and target environment | CI/ArgoCD | CC8.1 | A.8.31 |
| Deployment success/failure status | CI/ArgoCD | CC8.1 | A.8.25 |
| Rollback events (if any) | CI/ArgoCD | CC8.1 | A.8.32 |
| SBOM for the deployed artifact | CI artifacts | CC8.1 | A.8.9 |
Stage 10: Rollback and Incident Response Integration
SOC 2 CC8.1 requires that changes can be reversed. ISO 27001 A.8.32 (Change management) requires defined procedures for handling failed changes. Your pipeline must support rollback, and your rollback procedures must be documented and tested.
Automated Rollback with Health Checks
- name: Deploy with canary and automatic rollback
run: |
# Deploy canary (10% traffic)
kubectl set image deployment/myapp \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--record
# Wait for rollout and monitor health
if ! kubectl rollout status deployment/myapp --timeout=300s; then
echo "::error::Deployment failed health check. Initiating rollback."
kubectl rollout undo deployment/myapp
# Log rollback event for audit trail
echo '{
"event": "automatic_rollback",
"sha": "${{ github.sha }}",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"reason": "health_check_failure",
"pipeline_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}' > rollback-record.json
exit 1
fi
For ArgoCD-based deployments: Rollback is a Git revert. Revert the commit in the manifests repository, and ArgoCD automatically syncs the cluster back to the previous state. The Git history provides the audit trail.
Evidence Collection: Keeping the Pipeline Audit-Ready
The controls above are only valuable if you can prove they exist and operate consistently. Your pipeline already produces the evidence; the work is collecting it in a form an auditor can review.
Where the Evidence Comes From
- Pull request history, showing code review approvals by someone other than the author
- Branch protection audit logs, showing that rules are active and have not been bypassed or weakened
- CI run logs, showing that tests and scans execute on every change
- SAST, SCA, IaC, and DAST results in SARIF format, showing vulnerabilities tracked and resolved
- Secrets scanning alerts, showing secrets detected before deployment
- Deployment records, showing production changes authorized and logged
Automate the Collection
Do not wait until audit time to gather this. Build collection into the pipeline itself:
- Export CI run summaries to a centralized evidence repository on a fixed schedule, not on demand.
- Generate compliance reports from your scanning tools that show trends, remediation times, and current open findings by severity.
- Maintain a change log that correlates every production deployment to its pull request, approvers, test results, and scan outcomes.
For SOC 2 Type II specifically, the auditor needs evidence that controls operated effectively across the entire observation period, not at a single point in time. Continuous collection is the only practical way to produce that.
Putting It All Together: The Complete Pipeline Architecture
The full compliant pipeline follows this sequence:
Developer pushes to feature branch
|
v
Pull Request opened against main
|
+---> SAST scan (Semgrep/CodeQL)
+---> SCA scan (Dependency Review + Grype)
+---> Secrets scan (Gitleaks)
+---> IaC scan (Checkov)
+---> Unit + integration tests
+---> Container image build + scan (Trivy)
|
v
All checks pass? ---- No ----> PR blocked, developer fixes findings
|
Yes
|
v
Code review by 2+ authorized reviewers
|
v
PR merged to main
|
v
Deploy to staging environment
|
v
DAST scan against staging (OWASP ZAP)
|
v
DAST passes? ---- No ----> Deployment blocked, findings triaged
|
Yes
|
v
Production deployment approval (required reviewer)
|
v
Deploy to production (with health checks and rollback)
|
v
Deployment record logged to immutable storage
|
v
SBOM archived with release metadata
Every arrow in this diagram produces audit evidence. Every gate is enforceable and verifiable. No manual steps require trust, the pipeline enforces the policy.
Common Audit Findings and How to Prevent Them
Even teams with sophisticated pipelines receive audit findings. These are the most frequent gaps:
Finding: Developers can bypass branch protection. Root cause: enforce_admins is set to false, or organization owners are exempt from branch protection rules. Fix: Enable admin enforcement. Audit your GitHub/GitLab organization role assignments quarterly.
Finding: Security scan results are not retained. Root cause: CI artifacts use default retention (90 days in GitHub Actions), which is insufficient for SOC 2 observation periods. Fix: Set retention-days: 2555 (7 years) on all compliance-relevant artifacts, or export to immutable external storage.
Finding: No evidence of scan failures blocking deployment. Root cause: Scans run but are configured as "informational", they do not fail the build. Fix: Every scan must use exit-code: 1 or equivalent hard-fail configuration. Document any exceptions in a risk acceptance register.
Finding: Production deployment does not require separate approval. Root cause: The same developer who wrote the code can merge the PR and deploy to production without any additional gate. Fix: Use GitHub Environments with required reviewers, or ArgoCD RBAC that restricts production sync to a separate team.
Finding: No SBOM generated for deployed artifacts. Root cause: Teams generate SBOMs only on demand during audits, not as part of the build process. Fix: Generate and archive an SBOM for every build that reaches production. Link the SBOM to the deployment record by Git SHA.
Finding: Rollback procedures are documented but never tested. Root cause: The change management policy describes rollback, but there is no evidence of rollback drills or actual rollbacks. Fix: Schedule quarterly rollback drills. Record the drill execution and results as audit evidence.
How QuickTrust Automates CI/CD Compliance Evidence
QuickTrust integrates directly with your CI/CD pipeline to automate the evidence collection described in this guide. Instead of manually exporting scan results, archiving deployment records, and mapping artifacts to controls, QuickTrust:
- Ingests CI/CD events from GitHub Actions, GitLab CI, and ArgoCD via webhooks and API integrations
- Maps pipeline artifacts to controls automatically, linking your Semgrep scan results to SOC 2 CC7.1, your branch protection configuration to CC8.1, your deployment approvals to ISO 27001 A.8.25
- Generates audit-ready evidence packages that your auditor can review directly, with timestamped artifacts organized by control objective
- Monitors for control drift, alerting you if branch protection rules are weakened, if scan stages are removed from pipelines, or if deployments bypass approval gates
- Maintains your SBOM registry with historical records linked to every production release
The platform is open source. You can inspect the integration code, run it in your own infrastructure, and extend it for custom frameworks.
Have our DevOps engineers set up your compliant CI/CD pipeline. Book a pipeline review and we will audit your current CI/CD configuration, identify control gaps against SOC 2 CC8 and ISO 27001 Annex A, and implement the pipeline stages described in this guide, configured for your specific tech stack and deployment model.