CI/CD Integration

Integrate CSE signals into your CI/CD pipelines to catch compliance issues early. This guide covers patterns for GitHub Actions, GitLab CI, and other pipeline tools.

Why CI/CD Integration?

  • Shift-left compliance: Catch issues before they reach production
  • Consistent policy: Enforce the same standards across all deployments
  • Developer feedback: Provide actionable compliance guidance in PRs
  • Audit trail: Document what was checked before each deployment

GitHub Actions

Basic Security Scan with CSE Output

.github/workflows/security-scan.yml
# .github/workflows/security-scan.yml
name: Security Scan

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Security Scanner
        id: scan
        run: |
          # Run your scanner with CSE output format
          scanner run --format cse --output findings.json

      - name: Check for Critical Findings
        run: |
          # Fail if any high-severity findings
          CRITICAL=$(jq '[.findings[] | select(.severity == "critical" or .severity == "high")] | length' findings.json)
          if [ "$CRITICAL" -gt 0 ]; then
            echo "::error::Found $CRITICAL critical/high severity findings"
            jq '.findings[] | select(.severity == "critical" or .severity == "high") | {signal_id, title: .signal.title}' findings.json
            exit 1
          fi

      - name: Upload Findings
        uses: actions/upload-artifact@v4
        with:
          name: cse-findings
          path: findings.json

Framework-Specific Gates

.github/workflows/compliance-gate.yml
# .github/workflows/compliance-gate.yml
name: Compliance Gate

on:
  pull_request:
    branches: [main]

env:
  CSE_API_KEY: ${{ secrets.CSE_API_KEY }}

jobs:
  hipaa-compliance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run IaC Scanner
        run: |
          terraform-scanner scan ./terraform --format cse --output findings.json

      - name: Check HIPAA Compliance
        run: |
          # Extract signal IDs from findings
          SIGNALS=$(jq -r '.findings[].signal_id' findings.json | sort -u)

          # Check each signal for HIPAA mappings
          for signal in $SIGNALS; do
            HIPAA_MAPPINGS=$(curl -s "https://api.cseregistry.org/v1/signals/$signal/mappings" \
              -H "Authorization: Bearer $CSE_API_KEY" | \
              jq '[.data.mappings[] | select(.framework == "HIPAA")] | length')

            if [ "$HIPAA_MAPPINGS" -gt 0 ]; then
              echo "::warning::Signal $signal affects HIPAA controls"
            fi
          done

      - name: Generate Compliance Report
        run: |
          python scripts/generate_compliance_report.py \
            --findings findings.json \
            --framework HIPAA \
            --output compliance-report.md

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('compliance-report.md', 'utf8');
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: report
            });

GitLab CI

.gitlab-ci.yml
# .gitlab-ci.yml
stages:
  - test
  - security
  - deploy

security_scan:
  stage: security
  image: your-scanner-image:latest
  variables:
    CSE_API_KEY: $CSE_API_KEY
  script:
    - scanner run --format cse --output findings.json
    - |
      # Parse findings and check severity
      python3 << 'EOF'
      import json
      import sys

      with open('findings.json') as f:
          data = json.load(f)

      critical = [f for f in data['findings']
                  if f.get('severity') in ['critical', 'high']]

      if critical:
          print(f"Found {len(critical)} critical/high findings:")
          for f in critical:
              print(f"  - {f['signal_id']}")
          sys.exit(1)
      EOF
  artifacts:
    reports:
      dotenv: cse-findings.env
    paths:
      - findings.json
    when: always
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

compliance_report:
  stage: security
  needs: [security_scan]
  script:
    - python scripts/compliance_report.py --findings findings.json
  artifacts:
    paths:
      - compliance-report.html
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Generic Pipeline Script

A Python script for processing CSE findings in any CI system:

scripts/check_compliance.py
#!/usr/bin/env python3
# scripts/check_compliance.py

import json
import sys
import os
import requests
from typing import List, Dict

CSE_API_KEY = os.environ.get("CSE_API_KEY")
CSE_API_URL = "https://api.cseregistry.org/v1"

def load_findings(path: str) -> List[Dict]:
    with open(path) as f:
        data = json.load(f)
    return data.get("findings", data)

def get_signal_mappings(signal_id: str) -> List[Dict]:
    response = requests.get(
        f"{CSE_API_URL}/signals/{signal_id}/mappings",
        headers={"Authorization": f"Bearer {CSE_API_KEY}"}
    )
    if response.status_code == 404:
        return []
    response.raise_for_status()
    return response.json()["data"]["mappings"]

def check_compliance(
    findings: List[Dict],
    framework: str = None,
    fail_on_severity: List[str] = ["critical", "high"]
) -> Dict:
    results = {
        "total_findings": len(findings),
        "blocking_findings": [],
        "framework_impacts": {},
        "passed": True
    }

    for finding in findings:
        signal_id = finding.get("signal_id")
        severity = finding.get("severity", "unknown")

        # Check severity threshold
        if severity in fail_on_severity:
            results["blocking_findings"].append({
                "signal_id": signal_id,
                "severity": severity,
                "resource": finding.get("artifact", {}).get("resource_id")
            })
            results["passed"] = False

        # Check framework impact
        if CSE_API_KEY:
            mappings = get_signal_mappings(signal_id)
            for mapping in mappings:
                fw = mapping["framework"]
                if framework and fw != framework:
                    continue

                if fw not in results["framework_impacts"]:
                    results["framework_impacts"][fw] = []

                results["framework_impacts"][fw].append({
                    "signal_id": signal_id,
                    "control_id": mapping["control_id"],
                    "relationship": mapping["relationship"]
                })

    return results

def main():
    import argparse

    parser = argparse.ArgumentParser(description="Check CSE compliance")
    parser.add_argument("--findings", required=True, help="Path to findings JSON")
    parser.add_argument("--framework", help="Filter by framework")
    parser.add_argument("--fail-on", default="critical,high",
                        help="Severities that cause failure")
    parser.add_argument("--output", help="Output report path")

    args = parser.parse_args()

    findings = load_findings(args.findings)
    fail_severities = args.fail_on.split(",")

    results = check_compliance(
        findings,
        framework=args.framework,
        fail_on_severity=fail_severities
    )

    # Output results
    print(f"Total findings: {results['total_findings']}")
    print(f"Blocking findings: {len(results['blocking_findings'])}")

    if results["blocking_findings"]:
        print("\nBlocking findings:")
        for f in results["blocking_findings"]:
            print(f"  - [{f['severity'].upper()}] {f['signal_id']}")
            if f.get("resource"):
                print(f"    Resource: {f['resource']}")

    if results["framework_impacts"]:
        print("\nFramework impacts:")
        for fw, impacts in results["framework_impacts"].items():
            controls = set(i["control_id"] for i in impacts)
            print(f"  {fw}: {len(controls)} controls affected")

    if args.output:
        with open(args.output, "w") as f:
            json.dump(results, f, indent=2)

    # Exit with appropriate code
    sys.exit(0 if results["passed"] else 1)

if __name__ == "__main__":
    main()

Policy as Code

Define compliance policies that reference CSE signals:

.cse-policy.yaml
# .cse-policy.yaml
version: "1.0"
name: "Production Deployment Policy"

# Signals that block deployment
blocking_signals:
  - CSE-HIPAA-TECH-ENCRYPT-REST-001
  - CSE-CMMC-COMMS-UNRESTRICTED-SSH-001
  - CSE-CMMC-ACCESS-MFA-001

# Severity thresholds
severity_gates:
  critical: block
  high: block
  medium: warn
  low: allow
  info: allow

# Framework-specific rules
framework_rules:
  HIPAA:
    action: block
    message: "HIPAA violations must be resolved before deployment"

  SOC2:
    action: warn
    message: "SOC 2 findings detected - review before deployment"

# Exceptions (with expiration)
exceptions:
  - signal_id: CSE-GEN-CONFIG-DEBUG-001
    resource_pattern: "*/staging/*"
    reason: "Debug enabled in staging"
    expires: "2025-03-01"
    approved_by: "security-team"
scripts/enforce_policy.py
#!/usr/bin/env python3
# scripts/enforce_policy.py

import yaml
import json
from datetime import datetime

def load_policy(path: str) -> dict:
    with open(path) as f:
        return yaml.safe_load(f)

def check_exception(finding: dict, exceptions: list) -> bool:
    """Check if finding is covered by an exception."""
    signal_id = finding.get("signal_id")
    resource = finding.get("artifact", {}).get("resource_id", "")

    for exc in exceptions:
        if exc["signal_id"] != signal_id:
            continue

        # Check expiration
        if exc.get("expires"):
            if datetime.now() > datetime.fromisoformat(exc["expires"]):
                continue

        # Check resource pattern
        import fnmatch
        if fnmatch.fnmatch(resource, exc.get("resource_pattern", "*")):
            return True

    return False

def enforce_policy(findings: list, policy: dict) -> dict:
    results = {
        "blocked": [],
        "warnings": [],
        "allowed": [],
        "excepted": []
    }

    for finding in findings:
        signal_id = finding.get("signal_id")
        severity = finding.get("severity", "medium")

        # Check exceptions first
        if check_exception(finding, policy.get("exceptions", [])):
            results["excepted"].append(finding)
            continue

        # Check blocking signals
        if signal_id in policy.get("blocking_signals", []):
            results["blocked"].append(finding)
            continue

        # Check severity gates
        gate = policy.get("severity_gates", {}).get(severity, "allow")
        if gate == "block":
            results["blocked"].append(finding)
        elif gate == "warn":
            results["warnings"].append(finding)
        else:
            results["allowed"].append(finding)

    return results

PR Comments

Generate helpful PR comments with compliance context:

def generate_pr_comment(findings: list, policy_results: dict) -> str:
    lines = ["## Security Scan Results\n"]

    if policy_results["blocked"]:
        lines.append("### Blocking Issues\n")
        lines.append("The following must be resolved before merge:\n")
        for f in policy_results["blocked"]:
            lines.append(f"- **{f['signal_id']}**")
            lines.append(f"  - Severity: {f.get('severity', 'unknown')}")
            lines.append(f"  - Resource: `{f.get('artifact', {}).get('resource_id', 'N/A')}`")
            lines.append("")

    if policy_results["warnings"]:
        lines.append("### Warnings\n")
        lines.append("Review these findings before merge:\n")
        for f in policy_results["warnings"]:
            lines.append(f"- {f['signal_id']}: {f.get('severity', 'unknown')}")

    if not policy_results["blocked"] and not policy_results["warnings"]:
        lines.append("No compliance issues detected.")

    lines.append("\n---")
    lines.append("*Powered by [CSE Registry](https://cseregistry.org)*")

    return "\n".join(lines)

Best Practices

  • Start with warnings: Begin with warn-only mode, then add blocking rules
  • Use exceptions sparingly: All exceptions should have expiration dates
  • Cache signal data: Fetch signal info once per pipeline run, not per finding
  • Provide context: Link to CSE docs so developers understand findings
  • Track metrics: Monitor finding trends over time

Next Steps