# CI Security & Portability Checks

Authoritative sources:
[`docs/adr/017-security-baseline-and-secrets-management.md`](../adr/017-security-baseline-and-secrets-management.md),
[`docs/adr/018-content-security-policy-and-headers.md`](../adr/018-content-security-policy-and-headers.md).

## Workflows

| Workflow                         | Trigger              | What it blocks on            |
| -------------------------------- | -------------------- | ---------------------------- |
| `security-codeql.yml`            | PR + push to main + weekly | CodeQL JS/TS findings (severity ≥ high via branch protection) |
| `security-gitleaks.yml`          | PR + push to main    | Any leaked secret in commit history |
| `security-deps.yml`              | PR                   | Dependency advisories at severity ≥ high |
| `portability-matrix.yml`         | PR + push to main + nightly | Profile validation, typecheck, lint, forbidden-strings (warn) |

## What to do when one fails

### CodeQL

1. Open the **Security** tab on the PR or repo.
2. Read the alert; CodeQL points at the exact line.
3. Fix at the source — never silence the alert globally. If a finding is a
   true false-positive, dismiss it with a written justification (visible in
   the audit log).

### gitleaks

1. **Rotate the leaked credential immediately**, even if the PR is closed
   without merge. Git history can be cloned.
2. Log the incident in `governance/scorecard/risk-register.md` with the
   rotation timestamp.
3. Remove the secret from history (`git filter-repo` or BFG) on a separate
   security branch; coordinate with the team before force-pushing.

### dependency-review

1. The PR comment lists the offending package and advisory.
2. Update to a patched version, or replace the dependency.
3. If neither is possible, document the deferral in the risk register and
   add a `package.json` `overrides` entry pinning a safe transitive.

### portability-matrix

A failure means **the change broke another profile**. Most common causes:

- Hardcoded a customer string in core code → caught by forbidden-strings.
- Imported from `lib/customers/...` in a core file → caught by typecheck.
- Added a feature that isn't behind a flag → add the flag to `profile.ts`
  and switch in each profile.

## Local equivalents

```bash
npm run check:profiles      # Validate every profile against the Zod schema
npm run check:forbidden     # Greps for customer-specific strings in core
npm run check:portability   # Bundles the above + lint + typecheck
npm run lint:boundaries     # Just the import-boundary lint
```

## Phase boundaries

| Check                   | Phase 1 mode | Phase 2 mode |
| ----------------------- | ------------ | ------------ |
| Boundaries lint         | warn         | error        |
| Forbidden-strings       | warn         | error        |
| CSP                     | report-only  | enforced     |
| CodeQL / gitleaks / dep | hard fail    | hard fail    |
