---
phase: 16-ci-dependency-audit-and-security-checks
plan: "05"
subsystem: ci
tags: [gitea-ci, gitleaks, security, pnpm-audit, dependency-audit, gate]
dependency_graph:
  requires: ["16-02", "16-03", "16-04"]
  provides: ["security job in ci.yml", "gate wired with security check"]
  affects: [".gitea/workflows/ci.yml"]
tech_stack:
  added: []
  patterns: ["security CI job parallel to fast-checks", "individual needs.X.result check (Gitea #31007)", "base.sha probe with git merge-base fallback"]
key_files:
  modified:
    - path: .gitea/workflows/ci.yml
      role: CI workflow — security job added; gate aggregator updated
decisions:
  - "D-12: gitleaks always runs on every PR via unconditional steps (no job-level if:); only pnpm audit/outdated are code-gated at step level"
  - "D-14/D-15: security wired into gate with individual needs.security.result check — must be 'success', not 'success-or-skipped', because gitleaks always runs"
  - "A2/OQ-1: base.sha probe step with git merge-base fallback guards against empty base.sha on some Gitea versions"
metrics:
  duration: 7
  completed: "2026-06-13T12:23:36Z"
  tasks_completed: 2
  files_modified: 1
---

# Phase 16 Plan 05: CI Security Job — Summary

**One-liner:** Dedicated `security` CI job (gitleaks always + audit/outdated code-gated) wired into the `gate` aggregator with an individual `needs.security.result` success check.

## What Was Built

A new `security` job was added to `.gitea/workflows/ci.yml`, placed between `harness` and `gate` in the file order (runs in parallel with `fast-checks`). The job:

- Runs on every PR (`if: github.event_name == 'pull_request'`), `needs: [changes]`
- Uses `actions/checkout@v4` with `fetch-depth: 0` (Pitfall 3 — base.sha must be locally present)
- Has a "Probe PR base/head SHA" step that reads `github.event.pull_request.base.sha` and falls back to `git merge-base origin/${{ github.base_ref }} HEAD` if empty (Assumption A2 / OQ-1), exporting `BASE_SHA` and `HEAD_SHA` to `$GITHUB_ENV`
- Installs gitleaks v8.30.1 from GitHub releases (pinned, no `actions/cache` per D-PROBE-04), then runs `gitleaks git --log-opts="--no-merges ${BASE_SHA}..${HEAD_SHA}" --config .gitleaks.toml --baseline-path scripts/gitleaks-baseline.json --exit-code 1` — both gitleaks steps have **no `if:`** (D-12: blocking on every PR)
- The pnpm setup-node / corepack / install / check-audit / check-outdated steps each carry `if: needs.changes.outputs.code == 'true'` (step-level, so gitleaks still runs on doc-only PRs)

The `gate` aggregator was updated:
- `needs:` expanded to `[fast-checks, changes, api, harness, security]`
- An individual `if [ "${{ needs.security.result }}" != "success" ]` check was inserted between the fast-checks check and the api/harness for loop
- Security is **not** folded into the for loop — it must always succeed (cannot be skipped)

## Commits

| Hash | Message | Files |
|------|---------|-------|
| 61b7586 | ci(16-05): add security job (gitleaks always; audit/outdated code-gated) | .gitea/workflows/ci.yml |
| f0f7d8a | ci(16-05): wire security job into gate aggregator | .gitea/workflows/ci.yml |

## Deviations from Plan

None — plan executed exactly as written.

The task described a "Probe PR base/head SHA" step as a separate explicit step (per PLAN.md acceptance criteria A2/OQ-1). This matches the PLAN.md requirement and was implemented accordingly. The PATTERNS.md skeleton showed a simpler inline version; the PLAN.md explicitly required the probe step with fallback, so the PLAN.md was authoritative.

## Threat Coverage

| Threat | Mitigation | Status |
|--------|-----------|--------|
| T-16-15 — secret introduced in PR diff | gitleaks runs unconditionally, gate blocks on non-success | Mitigated |
| T-16-16 — unwaived High/Critical advisory merges to main | check-audit.mjs code-gated, gate blocks | Mitigated |
| T-16-17 — gitleaks scans nothing (empty range) | fetch-depth:0 + base.sha probe + merge-base fallback | Mitigated |
| T-16-SC — gitleaks binary substitution | version pinned to 8.30.1 from gitleaks/gitleaks official releases | Accepted residual (no checksum) |

## Known Stubs

None.

## Threat Flags

None — this plan adds only CI workflow steps and does not introduce new network endpoints, auth paths, or schema changes.

## Self-Check: PASSED

- `.gitea/workflows/ci.yml` — FOUND
- Commit 61b7586 (add security job) — FOUND
- Commit f0f7d8a (wire gate) — FOUND
- `16-05-SUMMARY.md` — FOUND
