# Story 4.52: Enforce Security Scanning in CI

**Status:** Ready
**Priority:** Medium (P2 - Security Hygiene)
**Depends on:** Story 4.13 (CI Security Scanning & SBOM Publishing) - Note: 4.13 partially implemented (scanning exists, enforcement missing)

---

## Context / Background

Story 4.13 introduced security scanning with `pip-audit` and SBOM generation in `.github/workflows/security-sbom.yml`. However, the current implementation only uploads artifacts—it does not fail the build when vulnerabilities are found.

**Current behavior** (`.github/workflows/security-sbom.yml:29-39`):
```yaml
- name: Vulnerability Scan (pip-audit)
  run: |
    pip install pip-audit
    pip-audit -f json -o audit.json
- name: Upload artifacts
  uses: actions/upload-artifact@v4
```

**Problem:** Known vulnerabilities can be merged and released without any CI gate. The scan runs but results are ignored for pass/fail determination.

---

## Scope (In / Out)

### In Scope

- Add failure threshold to `pip-audit` step
- Configure severity-based gating (fail on HIGH/CRITICAL)
- Add PR comment with vulnerability summary
- Allow temporary bypass with explicit label (e.g., `security-override`)
- Documentation for maintainers

### Out of Scope

- Fixing existing vulnerabilities (separate work)
- Adding new scanning tools (bandit already covered in 4.13)
- Automatic dependency updates (Dependabot config)

---

## Acceptance Criteria

### AC1: CI Fails on Critical/High Vulnerabilities

**Description:** The security workflow fails when `pip-audit` finds HIGH or CRITICAL severity vulnerabilities.

**Validation:**
```yaml
- name: Vulnerability Scan (pip-audit)
  run: |
    pip install pip-audit
    pip-audit --strict --ignore-vuln GHSA-xxx  # Known false positives
    # Exit code non-zero on any vulnerability
```

### AC2: Configurable Severity Threshold

**Description:** Severity threshold is configurable by parsing JSON output and failing conditionally.

**Note:** `pip-audit --strict` fails on ANY vulnerability. For severity-based filtering, parse the JSON output.

**Validation:**
```yaml
- name: Check severity threshold
  run: |
    # Parse audit.json and fail only on high/critical
    python -c "
    import json, sys
    audit = json.load(open('audit.json'))
    high_crit = [d for d in audit.get('dependencies', [])
                 if any(v.get('fix_versions') for v in d.get('vulns', []))]
    if high_crit:
        print(f'Found {len(high_crit)} vulnerable dependencies')
        sys.exit(1)
    "
```

### AC3: PR Summary Comment

**Description:** On PRs, post a comment summarizing vulnerabilities found (even if passing).

**Validation:**
- Comment shows count by severity
- Comment includes link to full artifact
- Comment is updated on re-runs (not duplicated)

### AC4: Override Label for Emergencies

**Description:** PRs with `security-override` label can bypass the check (with audit trail).

**Validation:**
```yaml
if: ${{ !contains(github.event.pull_request.labels.*.name, 'security-override') }}
```

### AC5: Release Workflow Integration

**Description:** Release workflow also enforces the security gate.

**Validation:**
- Release workflow references security job
- Cannot release with known HIGH/CRITICAL vulnerabilities

---

## Implementation Notes

### File Changes

```
.github/workflows/security-sbom.yml (MODIFIED - add enforcement)
.github/workflows/release.yml (MODIFIED - add security dependency)
docs/SECURITY.md (NEW or MODIFIED - document process)
```

### Key Code

```yaml
name: Security Scan & SBOM

on:
  push:
    branches: [main, "feat/**", "feature/**"]
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: write  # For PR comments

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

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install project
        run: |
          python -m pip install --upgrade pip
          pip install -e .

      - name: Generate SBOM (cyclonedx)
        run: |
          pip install cyclonedx-bom
          cyclonedx-py environment -o sbom.json

      - name: Vulnerability Scan (pip-audit)
        id: audit
        continue-on-error: true  # Capture result for comment
        run: |
          pip install pip-audit
          pip-audit --strict --progress-spinner off \
            --format json --output audit.json 2>&1 | tee audit-output.txt
          echo "exit_code=$?" >> $GITHUB_OUTPUT

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: security-artifacts
          path: |
            sbom.json
            audit.json

      - name: Comment on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const audit = JSON.parse(fs.readFileSync('audit.json', 'utf8'));
            const vulnCount = audit.dependencies?.filter(d => d.vulns?.length > 0).length || 0;

            const body = vulnCount > 0
              ? `## ⚠️ Security Scan Found ${vulnCount} Vulnerable Dependencies\n\nSee workflow artifacts for details.`
              : `## ✅ Security Scan Passed\n\nNo known vulnerabilities found.`;

            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });

      - name: Fail on vulnerabilities
        if: |
          steps.audit.outputs.exit_code != '0' &&
          !contains(github.event.pull_request.labels.*.name, 'security-override')
        run: |
          echo "::error::Vulnerabilities found. Add 'security-override' label to bypass (requires justification)."
          exit 1
```

---

## Tasks

### Phase 1: Core Enforcement

- [ ] Update `pip-audit` step to use `--strict` flag
- [ ] Add exit code capture and conditional failure
- [ ] Test with intentionally vulnerable dependency

### Phase 2: PR Integration

- [ ] Add PR comment step with vulnerability summary
- [ ] Add `security-override` label bypass logic
- [ ] Ensure comment updates on re-runs

### Phase 3: Release Integration

- [ ] Add security job as dependency in release workflow
- [ ] Document release security requirements

### Phase 4: Documentation

- [ ] Create/update SECURITY.md with process
- [ ] Document override label usage and audit requirements
- [ ] Update CHANGELOG

---

## Tests

### Manual Verification

- [ ] Workflow fails when vulnerability exists
- [ ] Workflow passes when no vulnerabilities
- [ ] PR comment appears with correct summary
- [ ] Override label allows merge (with warning)
- [ ] Release blocked without security check passing

---

## Definition of Done

### Code Complete

- [ ] All acceptance criteria implemented
- [ ] Workflow syntax validated
- [ ] No new linting errors

### Quality Assurance

- [ ] Workflow tested on test branch with known vuln
- [ ] Override label tested
- [ ] PR comment verified

### Documentation

- [ ] SECURITY.md updated
- [ ] CONTRIBUTING.md mentions security checks
- [ ] CHANGELOG updated

---

## Risks / Rollback

### Risks

1. **Risk:** False positives block legitimate PRs
   - **Mitigation:** `security-override` label with audit trail

2. **Risk:** pip-audit service unavailable
   - **Mitigation:** `continue-on-error` with manual review fallback

### Rollback Plan

If issues occur:
1. Remove `--strict` flag to make advisory-only
2. Keep artifact upload for visibility

---

## Related Stories

- **Depends on:** Story 4.13 - CI Security Scanning & SBOM Publishing
- **Related:** Story 4.15 - Developer Docs for Security Scans

---

## Change Log

| Date | Change | Author |
|------|--------|--------|
| 2025-01-23 | Initial draft from security audit | Claude |
