shipped

Spec: F54 — CI Security Scanning & Vulnerability Gates

Status: shipped · Audited: 2026-07-14

1. Problem & CEO Job

Job: Trust the signal — but there is no signal when a CVE ships to production. Lumi’s merge path now blocks high-severity Python, JavaScript, CodeQL, and repository-wide Trivy findings, while Android has its own dependency audit and production deploy scans the exact image before push. The remaining platform gaps are explicit: CodeQL annotations/Security-tab upload are unavailable on the current private-repository plan, and iOS package-vulnerability proof is not yet attached.

This spec adds security scanning to the deployment-path CI runs: Python (pip-audit), JavaScript (npm audit), Gradle, and GitHub Actions CodeQL analysis. Critical/high CVEs block the merge. The cost of adding these is measured in minutes of CI configuration, not days of development.

2. User Stories & Acceptance Criteria

  • As a security-conscious developer, I want CI to fail when a Python dependency has a known CVE, so that vulnerable code never reaches production.

    • pip-audit runs in the backend CI job for backend paths on deploy/backend* pushes/PRs.
    • Critical CVEs → CI fails immediately.
    • High CVEs → CI fails.
    • Medium CVEs → CI passes with warning annotation.
    • Low CVEs → no action.
    • pip-audit uses the backend/pyproject.toml dependency manifest. (backend/requirements.txt was deleted in F56; pip-audit now audits pyproject.toml.)
  • As a frontend developer, I want CI to fail when an npm dependency has a known vulnerability, so that XSS or supply-chain attacks through npm are caught at the PR stage.

    • npm audit runs in the frontend CI job for frontend paths on deploy/* pushes/PRs.
    • --audit-level=high — fails on high and critical vulnerabilities.
    • Dependabot npm PRs that pass npm audit are auto-merged (F42 auto-merge for patch-level).
    • npm audit output is human-readable in CI logs.
  • As a mobile developer, I want dependency scanning for Gradle (Android) and Swift (iOS), so that mobile builds don’t ship vulnerable libraries.

    • Android CI runs ./gradlew dependencyCheckAnalyze or owasp-dependency-check-gradle plugin.
    • iOS CI runs Swift package vulnerability check (Xcode 16+ has built-in package vulnerability warnings — ensure they fail the build).
    • Both CI jobs upload vulnerability reports as workflow artifacts.
  • As a security engineer, I want CodeQL analysis on every relevant deploy/backend-prod push and PR, so that code-level vulnerabilities (SQL injection patterns, XSS vectors, hardcoded secrets) are detected.

    • GitHub CodeQL workflow analyzes Python and JavaScript/TypeScript codebases.
    • CodeQL runs only on relevant deploy/backend-prod pushes and pull requests.
    • CodeQL analysis is pinned to GitHub-hosted ubuntu-latest and does not use the shared trusted-event router.
    • CodeQL findings appear as PR annotations and Security tab alerts.
    • CodeQL error-level or security-severity ≥7.0 findings block merge through a repository-owned SARIF gate; lower severities remain advisory.
  • As a developer, I want the .githooks/pre-commit hook to catch basic secrets before commit, so that I don’t push API keys or tokens accidentally.

    • Pre-commit hook runs detect-secrets or trufflehog on staged files.
    • Blocked patterns: [A-Za-z0-9_-]{20,} that look like API keys or tokens, .p8 private key files, .pem certificate files.
    • Allowlist: known placeholder values (demo passwords, dev keys where explicitly marked with # nosecrets).
  • As a maintainer, I want trusted Linux jobs to use the repository’s runner most of the time without exposing it to pull-request code, so that CI cost falls while the persistent host remains a trusted boundary.

    • One reusable router emits JSON labels consumed through fromJSON.
    • Pull-request events always select ubuntu-latest.
    • Trusted events select [self-hosted, ubuntu-22.04] for SHA buckets 1–9 and ubuntu-latest for bucket 0.
    • Every routed Linux job explicitly depends on the router; CodeQL, fixed macOS/Windows jobs, and the persistent deploy-worker job remain pinned to their required hosts.
    • The workflow policy gate rejects routing drift, missing router dependencies, unsafe PR selection, or a changed 98/2 ratio.

3. Scope Boundaries

  • In:
    • pip-audit for Python (backend/pyproject.toml). (backend/requirements.txt was deleted in F56.)
    • npm audit for JavaScript (frontend/package.json).
    • CodeQL for Python and JavaScript/TypeScript.
    • Pre-commit hook for basic secret detection.
    • Gradle dependency check (Android) — basic integration.
    • Failing CI on critical/high CVEs.
    • Deterministic trusted-event Linux runner routing with a hosted-only pull-request boundary.
  • Out:
    • Docker container scanning (Trivy/Grype for backend/Dockerfile and frontend/Dockerfile) — separate spec when container security becomes a priority.
    • Software Bill of Materials (SBOM) generation.
    • Runtime vulnerability scanning (Snyk, Wiz, or similar).
    • SAST beyond CodeQL (no SonarQube, no Semgrep).
    • DAST (dynamic application security testing — requires a running instance).
    • Penetration testing automation.
    • Supply-chain attestation / SLSA framework compliance.
    • Fixing discovered vulnerabilities — scanning finds them; fixing is covered by existing Dependabot + F42 auto-merge.

4. Data Model

No model changes. CI configuration only.

4.1 CI Workflow Changes

jobs:
  router:
    uses: ./.github/workflows/runner-router.yml

  backend:
    needs: router
    runs-on: ${{ fromJSON(needs.router.outputs.runner) }}

The reusable router runs on ubuntu-latest and does not check out repository code. For pull_request it emits ["ubuntu-latest"]. For trusted events it hashes github.sha into ten stable buckets: bucket 0 emits ["ubuntu-latest"]; buckets 1–9 emit ["self-hosted","ubuntu-22.04"]. CodeQL is intentionally outside this router and is pinned to ubuntu-latest for all supported events, so security analysis never runs on the self-hosted runner.

jobs:
  backend:
    # ... existing steps ...
    - name: Python vulnerability scan
      run: |
        pip install pip-audit
        pip-audit --project backend/pyproject.toml \
          --ignore-vuln PYSEC-2024-XXX  # explicit allowlist for triaged CVEs
        # Note: backend/requirements.txt was deleted in F56; pyproject.toml is the dependency manifest.
  
  frontend:
    # ... existing steps ...
    - name: JavaScript vulnerability scan
      run: |
        cd frontend
        npm audit --audit-level=high
  
  android:
    # ... existing steps ...
    - name: Gradle dependency check
      run: |
        cd android
        ./gradlew dependencyCheckAnalyze --info
  
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v4
        with:
          languages: python, javascript-typescript
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v4

4.2 .githooks/pre-commit Changes

#!/bin/bash
# ... existing hooks ...

# Secret detection
if command -v detect-secrets &> /dev/null; then
  detect-secrets-hook --baseline .secrets.baseline "$@"
fi

# Block .p8 and .pem files
staged_p8=$(git diff --cached --name-only --diff-filter=A | grep '\.p8$' || true)
if [ -n "$staged_p8" ]; then
  echo "ERROR: Private key file detected: $staged_p8"
  echo "Store secrets in AWS Secrets Manager, not in git."
  exit 1
fi

4.3 .secrets.baseline (detect-secrets allowlist)

Generated by detect-secrets scan and updated when legitimate strings are committed. Contains hashes of allowlisted secrets. Checked into the repo.

5. API Contract

No API changes.

6. UI / UX

No application UI changes. Developer workflow change:

  1. Developer opens PR.
  2. Deployment-path CI runs: backend tests + pip-audit + frontend build + npm audit + CodeQL.
  3. If pip-audit finds a CVE-2024-XXXX in dependency requests==2.31.0:
    • CI job fails.
    • PR comments: ”🚨 Security: requests==2.31.0 has CVE-2024-XXXX (Critical). Upgrade to requests>=2.32.0.”
    • Dependabot automatically opens a PR to upgrade requests if it hasn’t already.
  4. Developer upgrades the dependency or adds an explicit allowlist (--ignore-vuln CVE-2024-XXXX) with a justification comment.
  5. CI re-runs. pip-audit passes. PR unblocks.

6.1 Vulnerability Triage Flow

When a vulnerability is found but can’t be fixed immediately (no upgrade available, fix would break compatibility):

  1. Developer adds --ignore-vuln CVE-XXXX-YYYY to the CI command.
  2. Developer adds a comment: # CVE-XXXX-YYYY: Low severity, no fix available. Review by 2026-08-01.
  3. The allowlist expires after 60 days — CI starts failing again if not removed, forcing re-review.
  4. This is enforced via a CI check on the allowlist comment date.

7. Test Plan

#CaseTypeWhere
1pip-audit passes on clean backend/pyproject.tomlciCI workflow run
2pip-audit fails when a known-vulnerable package is addedciTest PR with requests==2.31.0 pinned
3npm audit passes on clean package.jsonciCI workflow run
4npm audit fails on high-severity vulnerabilityciTest PR with vulnerable package
5CodeQL passes on clean codebaseciCI workflow run
6CodeQL catches hardcoded secret patternciTest PR with SECRET_KEY = "test-secret-12345"
7Pre-commit blocks .p8 file stagingunitGit hook test
8Vulnerability allowlist expires after 60 daysciTimestamp check in CI

8. Current Behavior

  • CI runs linting (ruff E,F,W rules) + tests + coverage.
  • pip-audit runs in backend CI.
  • pnpm audit --audit-level=high runs in frontend CI.
  • CodeQL workflow exists at .github/workflows/security-codeql.yml; it runs only on deploy/backend-prod pushes and pull requests, writes SARIF locally, and blocks on error-level or security-severity ≥7.0 findings without requiring private-repository code-scanning upload.
  • CodeQL analysis is pinned to GitHub-hosted ubuntu-latest and is excluded from the shared 98/2 trusted-event router.
  • Pull-request-executable jobs use isolated GitHub-hosted runners; release/deploy-only jobs may retain controlled self-hosted runners.
  • Trusted Linux workflow runs share one deterministic router: 90% self-hosted and 10% GitHub-hosted by SHA; pull requests are forced hosted. Fixed macOS/Windows jobs and the persistent Docker worker deploy remain pinned.
  • Routed JavaScript jobs establish their declared Node version before pnpm setup; pnpm’s action owns its cache so hardened persistent runners do not fall back to an incompatible standalone bootstrap.
  • Dependency audits and PR security scans are blocking, repository-wide Trivy reviews supported manifest changes without requiring GitHub Advanced Security, and production scans the exact image before push.
  • scripts/check_ci_security_policy.py is the regression gate for runner, trigger, warn-only, CodeQL-local-SARIF, and image-scan ordering policy; its owning docs workflow and dependency review run on deploy/backend* pull requests, and the checker tests that trigger. scripts/check_codeql_sarif.py enforces the finding threshold.
  • Pre-commit secret detection is active in .githooks/pre-commit.
  • Dependabot opens PRs but doesn’t fail CI.
  • Remaining gap: allowlist-expiry enforcement is still not wired.

9. Error States

#ScenarioUser-visibleInternalLogRetry
1pip-audit cannot reach PyPI advisory databaseCI job fails with: “pip-audit: could not fetch advisory database. Check network connectivity.”Subprocess error captured, exit code 2ERROR “pip_audit_network_error”CI re-run on push
2npm audit cannot reach npm registryCI job fails with: “npm audit: advisory database unavailable.”npm exit code 1, network errorERROR “npm_audit_network_error”CI re-run on push
3CodeQL analysis times out or emits no readable SARIFCodeQL job fails; merge remains blockedAnalyze step fails or local SARIF gate rejects missing/malformed outputERROR “codeql_analysis_unavailable”Re-run after correcting the runner/tool failure
4Secret detected in staged file via pre-commit hookCLI: “ERROR: Secret pattern detected in: . Commit blocked.”Hook blocks commit, exit code 1INFO “precommit_secret_detected”Developer removes secret or adds allowlist
5Secret pushed via --no-verify detected in CICI fails: “Secret pattern detected in commit . This commit bypassed pre-commit hooks.”CI regex check on commit diffERROR “ci_secret_detected_post_push”Developer must amend commit or revoke leaked secret
6NVD feed unavailable for Gradle OWASP dependency checkCI step emits: “NVD feed unavailable. Skipping dependency check for this run.”OWASP plugin returns feed errorWARNING “gradle_dependency_nvd_unavailable”Auto-retry on next CI run, max 3 consecutive skips before failing
7pip-audit version pinned but OS-level Python version mismatchCI fails: “pip-audit requires Python 3.9+. Runner has 3.8.”Environment mismatchERROR “pip_audit_env_mismatch”Fix CI Python version, re-run
8pip-audit finds critical CVE with no fix available (no patched version)CI fails with CVE details + “No patched version available for . Add —ignore-vuln with expiry justification if triaged.”pip-audit exit code 1WARNING “cve_no_fix_available”Developer adds ignore with expiry comment, re-runs CI
9Gradle dependency check plugin not configured (not yet installed)CI step emits: “Dependency check plugin not configured. Skipping.” Passes.Plugin presence check failsINFO “gradle_dependency_plugin_missing”n/a — informational only
10.secrets.baseline file missing or unreadablePre-commit hook emits: “detect-secrets baseline not found. Run detect-secrets scan > .secrets.baseline first.” Blocks commit.File not found, exit code 1ERROR “secrets_baseline_missing”Developer generates baseline, re-commits
11Router output is missing, malformed JSON, or references unavailable labelsDependent Linux jobs do not start; the policy gate names the invalid router contract before mergefromJSON or runner matching fails closedERROR “ci_runner_router_invalid”Correct the reusable router and re-run CI

10. Diagrams & Docs Touched

  • deploy/DIAGRAMS.md or CI workflow diagrams
  • F39 security program docs
  • Contributing or release docs that define merge gates

11. Implementation Audit

Edge Cases Catalog (formerly §9, 2026-06-14; 9 items): scanner outage explicit failure, suppression expiry (60-day), monorepo dedup via per-manifest scanning, CodeQL false positive 90-day annotation review, --no-verify bypass retroactive CI catch, pip-audit version pinning, production-manifest scan, pnpm audit --prod, Gradle plugin-absent guard. Full catalog in git history.

#ClaimStatusEvidence
1pip-audit in backend CI job✅ match.github/workflows/ci-be.yml backend job
2pnpm audit --audit-level=high in frontend CI job✅ match.github/workflows/ci-fe.yml frontend job
3bandit SAST + safety dependency check✅ match.github/workflows/security-scan.yml
4CodeQL workflow (Python + JS/TS; deploy/backend-prod pushes/PRs)✅ match.github/workflows/security-codeql.yml
5Pre-commit secret detection (.p8/.pem + pattern scan)✅ match.githooks/pre-commit
6Gradle Android dependency check (with plugin-absent guard)✅ match.github/workflows/android-ci.yml
7Vulnerability allowlist expiry enforcement (60-day parse at CI runtime)⚠️ gapImplementation Order item 6 — not confirmed built

12. Open Questions / Decisions

  1. pip-audit vs safety: Both scan PyPI packages for CVEs. pip-audit is free (PyPA-backed), safety requires a free account for the vulnerability DB. Decision: pip-audit — zero signup, zero API key, PyPA-backed, and supports --ignore-vuln for triage.
  2. npm audit false positives: npm audit has a reputation for noisy dev-dependency warnings. Decision: --audit-level=high filters out low/medium. --production ignores dev dependencies in CI (devDeps are not in the production bundle). If false positives persist, --audit-level=critical is the fallback.
  3. Gradle dependency scanning tool: OWASP Dependency-Check Gradle plugin vs Gradle’s built-in dependency verification. Decision: OWASP Dependency-Check — battle-tested, supports NVD data feed, generates HTML reports. More thorough than Gradle’s built-in signature verification.
  4. CodeQL language coverage: Python + JavaScript/TypeScript covers the current web/backend critical paths. Kotlin is not included in the shipped CodeQL workflow; Android vulnerability scanning relies on the separate Gradle dependency-check lane. Swift is not supported by CodeQL. iOS vulnerability scanning relies on Xcode’s built-in package vulnerability warnings (Xcode 16+ checks Swift Package dependencies against the NVD).

13. Bug / Change Log

2026-07-14 — Make supply-chain controls merge/deploy gates

Type: Security hardening

  • Moved pull-request execution to hosted runners and added PR CodeQL, repository-native dependency review, and security scanning.
  • Removed warn-only Python/JavaScript dependency audit behavior.
  • Pinned Trivy and scan the final SHA-tagged container before pushing it.
  • Added executable workflow-policy fixtures and an F54 Contract Graph declaration.
  • Replaced unavailable private-repository CodeQL upload with a fail-closed local SARIF gate; GitHub annotations/Security-tab alerts remain an explicit platform gap.

2026-07-15 — Scope backend deployment CI gates

Type: CI policy

  • Scoped CI Docs, Dependency Review, Security Scan, and Android Benchmark Gate to deploy/backend*, matching the backend deployment promotion path.
  • Updated the executable workflow-policy checker, fixture tests, deploy guide, and CI/CD diagram to keep trigger policy machine-checked and documented.

2026-07-16 — Restrict CodeQL to production backend promotion

Type: CI policy

  • Scoped CodeQL push and pull-request execution to the exact deploy/backend-prod branch.
  • Updated the executable trigger policy, fixtures, CI trigger map, deployment diagram, and current F39/F54 audit truth.

2026-07-16 — Pin CodeQL to hosted Linux

Type: CI infrastructure

  • Pinned the CodeQL analysis matrix to GitHub-hosted ubuntu-latest for every supported event.
  • Kept the shared 98/2 runner router for other eligible Linux jobs while adding a machine-checked CodeQL exception and updating the deployment/security docs.

2026-07-15 — Route trusted Linux jobs 98/2

Type: CI infrastructure

  • Added one reusable runner router and migrated Linux-capable workflow jobs to depend on its JSON output.
  • Trusted events use stable SHA buckets: 90% target the registered self-hosted + ubuntu-22.04 runner and 10% use ubuntu-latest.
  • Pull-request events remain hosted-only; fixed macOS/Windows jobs and the persistent deploy-worker host are explicit exceptions.
  • Extended the executable F54 workflow policy so routing coverage, ratio, labels, and the PR safety override cannot silently drift.

2026-06-14 — Spec restructured to TEMPLATE.md canonical order (§1–§13)

Type: Documentation Change

Summary:

  • §9 Edge Cases (9 items) absorbed into §11 Implementation Audit note.
  • §12 Implementation Audit converted from 3-column table to canonical 4-column table (7 rows: 6 ✅, 1 ⚠️ gap for allowlist expiry enforcement).
  • Sections renumbered: §10 ES → §9, §11 Diags → §10, §12 Audit → §11, §13 OQ → §12, §14 BL → §13.

Reason:

  • Align with TEMPLATE.md canonical section order per SP fleet restructuring pass.

Impact:

  • Spec authors and future implementation agents.

2026-07-02 — Fix stale paths: backend/requirements.txt → backend/pyproject.toml (batch 3)

Type: Documentation

Summary:

  • backend/requirements.txt was deleted in F56 (F56 locked backend/pyproject.toml as the single dependency manifest).
  • Updated all five occurrences across §2 AC, §3 Scope Boundaries, §4.1 CI YAML snippet, §7 Test Plan row 1, and §13 safety command to reference backend/pyproject.toml instead.
  • No code changes; no CI workflow files changed.

2026-07-01 — Close CodeQL workflow drift and stale evidence paths

Type: CI hardening + documentation truth fix

Summary:

  • security-codeql.yml now runs on protected-branch pushes and pull requests in addition to the weekly sweep, matching the intended merge gate.
  • Updated F54 to point at the actual workflow filenames in the repo (security-codeql.yml, security-scan.yml) and removed stale prose that still claimed CodeQL and pre-commit secret detection did not exist.
  • Kept the remaining open gap narrow and honest: allowlist-expiry enforcement is still not built.

2026-06-13 (T3-F R11) — security-scan CI job added

CI security scanning steps (Partial)

  • Added security-scan job to CI; the active backend security scan now lives in .github/workflows/ci-be.yml.
  • safety check --project backend/pyproject.toml || true — warn-only until baseline clean. (backend/requirements.txt was deleted in F56; safety now targets pyproject.toml.)
  • bandit -r backend/apps/ -ll — medium+ severity findings fail the job (this is the blocking gate).
  • pip-audit (backend job) and pnpm audit --audit-level=high (frontend job) both already present with continue-on-error: true.
  • Remaining: CodeQL workflow, pre-commit hook, Gradle dependency check, allowlist expiry.

2026-07-02 — Spec drift fix: checked shipped ACs in §2

Type: Documentation

Summary:

  • 8 of 19 [ ] ACs in §2 flipped to [x] — confirmed shipped against §11 audit rows 1–6.
  • Checked: pip-audit in backend CI; npm/pnpm audit --audit-level=high in frontend CI; Android Gradle dependency check; CodeQL (Python + JS/TS; protected-branch pushes/PRs); pre-commit secret detection (.p8/.pem + pattern scan).
  • Left [ ]: severity-threshold sub-behaviors (not enumerated in §11 rows), iOS Swift vulnerability check, artifact upload, CodeQL PR annotations/severity gate, npm audit human-readable logs, allowlist, Dependabot auto-merge, and the confirmed-open ⚠️ gap (allowlist expiry enforcement).
  • No code changes.

2026-06-13 — Spec hardening: numbered edge cases, detailed error matrix, status → agreed

Type: Documentation Change

Summary:

  • Replaced §9 loose bullet points with 9 numbered edge cases (9.1–9.9) covering scanner outages, baseline expiry, monorepo dedup, CodeQL false positives, pre-commit bypass detection, version drift, manifest mismatch, devDependency noise, and Gradle plugin availability.
  • Replaced §10 with 10-row detailed error matrix (user-visible/internal/log/retry).
  • Updated spec status from draft to agreed.

Reason:

  • Spec needed hardening per TEMPLATE.md. Edge cases were loose prose, error states lacked structured detail.

Impact:

  • Spec authors and future implementation agents.

Tests:

  • Spec-only change — no code tests affected.