Feature Flags as Evidence: Turning Release Toggles into SOC 2 & PCI DSS Controls Your Auditors Will Love
Most teams already use feature flags, kill switches, and progressive delivery to ship safer changes. The missed opportunity is this: those same flags can double as change management, least privilege, and rollback evidence for SOC 2 and PCI DSS—if you design them that way.
This guide shows how engineering teams can turn feature flags as evidence into a first-class pattern:
- Every flag change becomes a traceable change record (SOC 2 CC8, PCI DSS Req. 6).
- Targeting rules reflect least privilege and access control (SOC 2 CC6, PCI DSS Req. 7).
- Progressive rollouts and kill switches produce rollback and logging proof (SOC 2 CC7, PCI DSS Req. 10).
- Each rollout automatically triggers a lightweight risk assessment using your CI tools plus the free Website Vulnerability Scanner from Pentest Testing Corp (free.pentesttesting.com).

🔎 Want a practical playbook for tagging PII/PHI/card data and wiring those tags into your pipelines? Read our deep dive: Master Data Classification as Code in CI/CD.
Along the way, we’ll plug into Pentest Testing Corp’s Risk Assessment Services and Remediation Services so your auditors and customers get formal, external evidence on top of your in-house telemetry.
1. Model feature flag changes as structured evidence
To treat feature flags as evidence, you first need a consistent event model for flag changes.
Think in terms of “mini change records”:
- Who changed what flag?
- In which environment?
- With which approvals?
- Linked to which ticket/risk item?
- With which rollback plan?
TypeScript model for feature flag evidence
// feature-flag-evidence.ts
export type Framework = 'soc2' | 'pci' | 'iso27001' | 'hipaa' | 'gdpr';
export interface ControlRef {
framework: Framework;
id: string; // e.g. "CC8.1", "PCI-6.5.1"
description?: string;
}
export interface FeatureFlagChange {
id: string; // uuid
flagKey: string; // e.g. "payments.strong-auth"
env: 'dev' | 'staging' | 'prod';
previousValue: string | null;
newValue: string;
changeType: 'create' | 'update' | 'delete';
changedBy: string; // user id / email
approvals: string[]; // approver ids
ticketId: string; // Jira / Azure DevOps / Linear
riskSummary: string; // short text summary
createdAt: string; // ISO timestamp
rollbackPlan: string; // link or short description
controls: ControlRef[]; // mapped SOC 2 / PCI DSS controls
evidenceArtifacts: string[]; // paths/URLs to reports, screenshots, logs
}Now, every time a flag changes, you emit one of these events into your log or evidence store.
Node.js helper to emit flag evidence events
// log-flag-change.ts
import { FeatureFlagChange, ControlRef } from './feature-flag-evidence';
import { randomUUID } from 'crypto';
import fs from 'fs';
import path from 'path';
const EVIDENCE_DIR = process.env.EVIDENCE_DIR || 'evidence/flags';
function mapControls(flagKey: string): ControlRef[] {
// Simple example – tune for your org
const controls: ControlRef[] = [
{ framework: 'soc2', id: 'CC8.1', description: 'Change management' },
{ framework: 'soc2', id: 'CC7.2', description: 'Monitoring & incidents' },
{ framework: 'pci', id: '6.5', description: 'Secure development' },
];
if (flagKey.includes('admin') || flagKey.includes('role')) {
controls.push(
{ framework: 'soc2', id: 'CC6.1', description: 'Access control' },
{ framework: 'pci', id: '7.1', description: 'Access to cardholder data' },
);
}
return controls;
}
export async function logFeatureFlagChange(input: Omit<FeatureFlagChange, 'id' | 'createdAt' | 'controls'>) {
const event: FeatureFlagChange = {
...input,
id: randomUUID(),
createdAt: new Date().toISOString(),
controls: mapControls(input.flagKey),
};
const day = event.createdAt.slice(0, 10); // yyyy-mm-dd
const file = path.join(EVIDENCE_DIR, `${day}.jsonl`);
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.appendFileSync(file, JSON.stringify(event) + '\n');
console.log('[flag-evidence] wrote', event.id, 'to', file);
}Call logFeatureFlagChange() from your feature flag management layer (or webhooks from your flag provider). Now you have append-only, queryable evidence proving:
- Changes were approved and ticketed.
- Controls like SOC 2 CC8 and PCI change requirements are actively enforced, not just documented.
2. Wire flag approvals into CI/CD and tickets
Next step: tie feature flag changes directly into your CI/CD pipeline and ticketing system so approvals and risk context are fully traceable.
Example: GitHub Action to validate required approvals per flag
Assume each deployment PR sets a list of flags being modified via labels or a simple YAML metadata file.
# .github/flag-changes.yaml (checked into the PR branch)
flags:
- key: "payments.strong-auth"
env: "prod"
ticketId: "SEC-1234"
approvers:
- "[email protected]"
- "[email protected]"
riskSummary: "Enforce MFA for high-value payments"GitHub Action to enforce approvals and log feature flags as evidence:
# .github/workflows/feature-flag-evidence.yml
name: feature-flag-evidence
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
validate-flags:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Load flag changes
id: flags
run: |
test -f .github/flag-changes.yaml || echo "flags: []" > .github/flag-changes.yaml
echo "flags=$(yq -o=json '.flags' .github/flag-changes.yaml)" >> "$GITHUB_OUTPUT"
- name: Validate approvals and ticket
run: |
node scripts/validate-flags.js '${{ steps.flags.outputs.flags }}'And a simple validator:
// scripts/validate-flags.ts
import { logFeatureFlagChange } from '../flag-evidence/log-flag-change';
const [,, raw] = process.argv;
const flags = JSON.parse(raw || '[]');
function requireValue<T>(val: T | undefined, msg: string): T {
if (!val) {
console.error(msg);
process.exitCode = 1;
throw new Error(msg);
}
return val;
}
(async () => {
const actor = process.env.GITHUB_ACTOR!;
const sha = process.env.GITHUB_SHA!;
const env = process.env.DEPLOY_ENV || 'staging';
for (const flag of flags) {
requireValue(flag.ticketId, `Flag ${flag.key} missing ticketId`);
if (!Array.isArray(flag.approvers) || flag.approvers.length === 0) {
console.error(`Flag ${flag.key} missing approvers`);
process.exitCode = 1;
}
await logFeatureFlagChange({
flagKey: flag.key,
env,
previousValue: flag.previousValue ?? null,
newValue: flag.newValue ?? 'enabled',
changeType: flag.changeType ?? 'update',
changedBy: actor,
approvals: flag.approvers,
ticketId: flag.ticketId,
riskSummary: flag.riskSummary ?? '',
rollbackPlan: `Rollback via flag toggle in release ${sha}`,
evidenceArtifacts: [], // we’ll enrich this later
});
}
})();Result: every flag change is:
- Linked to a ticket + approvers (SOC 2 CC8.1 change management, PCI DSS Req. 6).
- Stored in an evidence file per day or per release.
- Validated at CI time, not as an afterthought.
3. Use feature flags to enforce least privilege
Feature flags are also a powerful way to demonstrate least privilege and role-based access for SOC 2 CC6 and PCI DSS Req. 7.
Instead of treating flags as simple on/off switches, encode the who into your evaluation logic and logs.
Role-aware flag evaluation (Node/TypeScript)
// flag-eval.ts
interface UserContext {
userId: string;
roles: string[];
orgId: string;
isPrivileged: boolean;
}
interface FlagRule {
key: string;
allowedRoles: string[]; // e.g. ["admin", "support"]
environments: string[]; // ["staging", "prod"]
}
function isFlagEnabled(
flag: FlagRule,
user: UserContext,
env: 'dev' | 'staging' | 'prod'
): boolean {
if (!flag.environments.includes(env)) return false;
if (user.isPrivileged) return true;
return user.roles.some(role => flag.allowedRoles.includes(role));
}
// usage
const flag: FlagRule = {
key: 'billing.refund-portal',
allowedRoles: ['billing-admin', 'support-level2'],
environments: ['staging', 'prod'],
};
const user: UserContext = {
userId: 'u-123',
roles: ['support-level2'],
orgId: 'org-9',
isPrivileged: false,
};
const enabled = isFlagEnabled(flag, user, 'prod');Now log evaluations for high-risk flags:
function logFlagEvaluation(flagKey: string, user: UserContext, env: string, enabled: boolean) {
console.log(JSON.stringify({
type: 'flag_eval',
flagKey,
env,
userId: user.userId,
roles: user.roles,
orgId: user.orgId,
enabled,
ts: new Date().toISOString(),
controls: ['SOC2-CC6.x', 'PCI-7.x'],
}));
}In an audit, you can show:
- Only specific roles ever see the flagged feature.
- Access is enforced in code, not just in a policy document.
- Logs are searchable by control IDs.
4. Turn progressive delivery & kill switches into rollback evidence
Progressive delivery + kill switches already help you manage risk. With a small amount of structure, they become rollback evidence for SOC 2 CC7 and PCI DSS logging requirements.
Example: percentage rollout with automatic kill switch
// rollout-manager.ts
interface RolloutConfig {
flagKey: string;
env: 'staging' | 'prod';
percentage: number; // 0–100
errorRateThreshold: number; // e.g. 1.5 (%)
}
async function getCurrentErrorRate(service: string): Promise<number> {
// call Prometheus / Datadog / NewRelic API here
return 0.8; // mock
}
async function applyRollout(config: RolloutConfig) {
const rate = await getCurrentErrorRate('payments-api');
const eventBase = {
flagKey: config.flagKey,
env: config.env,
ts: new Date().toISOString(),
controls: ['SOC2-CC7.2', 'PCI-10.x'],
};
if (rate > config.errorRateThreshold) {
// kill switch
console.log(JSON.stringify({
...eventBase,
type: 'flag_killswitch',
reason: `errorRate ${rate}% > threshold ${config.errorRateThreshold}%`,
}));
await setFlagPercentage(config.flagKey, config.env, 0);
return;
}
console.log(JSON.stringify({
...eventBase,
type: 'flag_rollout_step',
newPercentage: config.percentage,
}));
await setFlagPercentage(config.flagKey, config.env, config.percentage);
}
async function setFlagPercentage(flagKey: string, env: string, pct: number) {
// Call feature flag provider API here
console.log(`[flags] ${flagKey} in ${env} set to ${pct}%`);
}For each rollout wave, you get:
- A rollout step event with control IDs.
- A kill-switch event if metrics go bad.
Auditors can follow the complete story for a risky change: “We tested at 5%, 25%, 50%. At 50% we saw a spike, so the kill switch automatically set the flag to 0% and the deployment was rolled back.”
5. Attach SAST/DAST/IaC and free website scans to each flag rollout
To fully operationalize feature flags as evidence, attach automated testing around each high-risk flag:
- SAST / SCA / secrets scanning.
- DAST or integration tests.
- IaC and configuration scans.
- Free external web scan via the Website Vulnerability Scanner from Pentest Testing Corp.
This gives you a mini risk assessment per rollout that can be formalized later via Pentest Testing Corp’s Risk Assessment Services and Remediation Services pages.
GitHub Actions: gate rollout on tests + website scan
# .github/workflows/flag-rollout.yml
name: flag-rollout
on:
workflow_dispatch:
inputs:
env:
required: true
type: choice
options: [staging, prod]
flagKey:
required: true
type: string
jobs:
rollout:
runs-on: ubuntu-latest
env:
DEPLOY_ENV: ${{ inputs.env }}
FLAG_KEY: ${{ inputs.flagKey }}
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run SAST / SCA
run: npm run lint:sec && npm run test:sec
- name: Run IaC checks
run: npm run iac:scan
- name: Run integration / DAST tests
run: npm run test:e2e
- name: Run external website scan (CLI wrapper)
run: |
node scripts/run-website-scan.js \
--url "https://app.example.com" \
--output "artifacts/webscan-${{ inputs.env }}.json"
- name: Collect evidence manifest
run: |
node scripts/build-evidence-manifest.js \
--flag "$FLAG_KEY" \
--env "$DEPLOY_ENV" \
--scan "artifacts/webscan-${{ inputs.env }}.json" \
--out "artifacts/evidence-${{ inputs.env }}.json"
- name: Upload evidence artifacts
uses: actions/upload-artifact@v4
with:
name: flag-evidence-${{ inputs.env }}
path: artifacts/
- name: Apply rollout
run: node scripts/apply-rollout.js "$FLAG_KEY" "$DEPLOY_ENV"Build an “evidence manifest” for the rollout
// scripts/build-evidence-manifest.ts
import { FeatureFlagChange } from '../flag-evidence/feature-flag-evidence';
import fs from 'fs';
interface Args {
flag: string;
env: string;
scan: string;
out: string;
}
function parseArgs(): Args {
const argv = process.argv.slice(2);
const args: any = {};
for (let i = 0; i < argv.length; i += 2) {
args[argv[i].replace(/^--/, '')] = argv[i + 1];
}
return args as Args;
}
const args = parseArgs();
const manifest = {
type: 'flag_rollout_evidence',
flagKey: args.flag,
env: args.env,
createdAt: new Date().toISOString(),
pipeline: {
runId: process.env.GITHUB_RUN_ID,
repo: process.env.GITHUB_REPOSITORY,
sha: process.env.GITHUB_SHA,
},
tests: {
sast: 'pass',
iac: 'pass',
dast: 'pass',
},
artifacts: {
websiteScanReport: args.scan,
// You can add screenshot paths here
},
controls: [
'SOC2-CC7.x',
'SOC2-CC8.1',
'PCI-6.x',
'PCI-10.x',
],
};
fs.mkdirSync('artifacts', { recursive: true });
fs.writeFileSync(args.out, JSON.stringify(manifest, null, 2));
console.log('Wrote evidence manifest to', args.out);This manifest can be:
- Attached to a change ticket.
- Exported as part of your SOC 2 or PCI DSS evidence binder.
- Handed off to Pentest Testing Corp as input to a deeper Risk Assessment or Remediation engagement.
6. Mapping feature flag telemetry to SOC 2 & PCI DSS controls
Here’s how feature flags as evidence line up with common control objectives:
| Evidence source | SOC 2 examples | PCI DSS 4.x examples |
|---|---|---|
| Flag change events + approvals | CC8.1 (change mgmt) | Req. 6 (change mgmt / SDLC) |
| Flag evaluation logs w/ roles & orgs | CC6.x (access control) | Req. 7 (access control) |
| Rollout + kill-switch logs | CC7.2 (incident detection/response) | Req. 10 (logging & monitoring) |
| CI/CD evidence manifest per rollout | CC7.x, CC8.x, CC9.x (ops & risk) | Req. 6 & 10 (secure dev + logging) |
| Website scanner + SAST/DAST findings per flag | CC7.x (vuln mgmt) | Req. 11 & 12 (testing & risk reporting) |
The same artifacts also help with:
- ISO 27001 Annex A (A.12 operations security, A.14 secure development).
- HIPAA technical safeguards around change and access.
- GDPR Art. 25/32 (data protection by design and by default).
7. Using the free Website Vulnerability Scanner as an external signal
Your internal telemetry shows how your CI/CD and flags behave. Auditors also like external signals: what does the outside world see?
Screenshot of our free Website Vulnerability Scanner interface

This visual reinforces that:
- Every internet-facing change can have a quick external scan attached.
- The scan URL can be added to the
evidenceArtifactsarray in yourFeatureFlagChangeevents.
Sample report from our free scanner to check Website Vulnerability

In your evidence manifest, store:
- The raw JSON output for machine-readable tracking.
- The PDF/report and screenshot path for auditors.
Those artifacts become part of the change record for the relevant feature flag—especially useful for PCI DSS and SOC 2 evidence packs.
8. When to pull in Pentest Testing Corp for formal evidence
Once your team has basic feature flags as evidence wired into CI/CD, you can layer on formal assessments from Pentest Testing Corp:
- Use their Risk Assessment Services to review your change and rollout patterns against HIPAA, PCI DSS, SOC 2, ISO 27001, and GDPR and build a prioritized roadmap.
- Use their Remediation Services to implement control fixes, documentation, and audit-ready artifacts mapped to those frameworks.
Practical flow:
- Build flag evidence, manifests, and external scan outputs as shown above.
- Share a subset with Pentest Testing Corp.
- They turn your raw data into formal risk reports, remediation plans, and clean evidence sets for customers, QSAs, and auditors.
This also pairs naturally with the CI/CD-focused content already on the Cyber Rely blog, such as:
- Embedded compliance in CI/CD tactics – building audit gates and evidence pipelines into your pipelines.
- CI gates for API security with OPA – merge-blocking checks that generate SOC 2 and PCI DSS evidence.
- Policy-as-code rollouts with OPA vs Cedar – authZ decisions as code with CI gating and observability.
- Mapping CI/CD findings to SOC 2 & ISO 27001 – normalizing scanner output into auditor-ready control mappings.
🔐 Frequently Asked Questions (FAQs)
Find answers to commonly asked questions about Feature flags as evidence for SOC 2 & PCI DSS.