docs(devops): document two-token model, OSV alerts, and nightly-audit runbook in ci-gitea.md
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m28s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 5m12s
CI / fail2ban Regex (pull_request) Successful in 48s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m9s

Adds §Renovate + Nightly Audit — Token Model covering:
- Two-token model: RENOVATE_TOKEN vs NIGHTLY_AUDIT_TOKEN (issues-only), blast
  radius rationale, PAT rotation cadence (annual + on-compromise)
- OSV vs platform alerts on Gitea — osvVulnerabilityAlerts is the load-bearing
  detector; Gitea exposes no vulnerability graph for vulnerabilityAlerts
- Nightly vs PR gate divergence table (dev deps in/out)
- Runbook: triage severity → pin/upgrade/override → close issue

Refs #818. See ADR-041 for full rationale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-13 11:27:22 +02:00
parent d56a9eb401
commit de1a36a0d6

View File

@@ -462,3 +462,82 @@ jobs:
name: e2e-results name: e2e-results
path: frontend/test-results/e2e/ path: frontend/test-results/e2e/
``` ```
---
## Renovate + Nightly Audit — Token Model
> See [ADR-041](../adr/041-renovate-runner-setup.md) for full rationale.
### Two-token model
This repo uses two separate tokens for automated dependency work, both manually
provisioned as Gitea secrets. There is **no auto-provided `GITEA_TOKEN`** on
self-hosted Gitea runners — it must be created manually (see §Context Variable Names
table above).
| Secret | Scopes | Job | Reason |
|--------|--------|-----|--------|
| `RENOVATE_TOKEN` | `contents` + `pull_request` + `issues` | `renovate.yml` | Renovate needs to read/write files and open PRs |
| `NIGHTLY_AUDIT_TOKEN` | `issues` only | `nightly.yml``npm-audit` job | Only needs to file a tracking issue; an `issues`-only token cannot push branches or read contents — limits blast radius on leak |
Both tokens belong to a single dedicated bot account. **Branch protection on `main`
must forbid the bot pushing directly**, because a `contents`-scoped token can push
to any unprotected branch.
### PAT rotation cadence
Rotate both tokens:
- **Annually** (calendar reminder)
- **Immediately** on suspected compromise (runner log leak, accidental `set -x`, etc.)
Tokens are stored exclusively as Gitea secrets. Never commit them to `.env` files or
log them. The nightly audit step passes its token via `env:` at the step level, reads
it as `$NIGHTLY_AUDIT_TOKEN` in the shell, and never runs the API `curl` under
`set -x`.
### OSV vs platform alerts on Gitea
Renovate's `vulnerabilityAlerts` config key requires a *platform* vulnerability graph.
**Gitea does not expose one** — it has no equivalent to the GitHub Advisory Database
API. On this runner, `vulnerabilityAlerts` is a label carrier only: it attaches
`security` and `P1-high` labels to PRs that `osvVulnerabilityAlerts` already raised.
`osvVulnerabilityAlerts: true` is the load-bearing detector. Renovate queries
[OSV.dev](https://osv.dev) directly, which works regardless of platform. The runner
host must be able to reach `osv.dev:443`. If egress is filtered and OSV.dev is
unreachable, the flag silently no-ops — verify egress when standing up the runner.
### Nightly audit vs PR gate (divergence)
| Gate | Command | Dev deps | When |
|------|---------|----------|------|
| PR gate (`ci.yml`) | `npm audit --audit-level=high --omit=dev` | ❌ excluded | Every PR |
| Nightly audit (`nightly.yml`) | `npm audit --audit-level=high` | ✅ included | Nightly + `workflow_dispatch` |
The nightly job is **deliberately broader** — it catches dev-tooling advisories
(esbuild, Vite, vitest, etc.) that the PR gate ignores. A red nightly audit job does
**not** mean the PR gate is broken; the two signals are independent.
### Runbook: nightly-opened tracking issue
When the `npm-audit` job opens or updates the tracking issue
"Nightly npm audit: high-severity advisory":
1. **Triage severity.** Check the advisory page (link in the issue body). Is it
exploitable in production? A dev-only dep (e.g. esbuild, prettier) has no
production attack surface — treat as low urgency.
2. **Pin or upgrade.** If a non-breaking upgrade is available, update
`frontend/package.json` and regenerate the lockfile. Open a PR.
3. **Override if justified.** If the advisory does not apply (dev-only dep, no
exploitable path), add an `npm audit` override in `package.json`:
```json
"overrides": { "esbuild": ">=0.25.4" }
```
Document the rationale in the PR body. See #817 for the reference decision tree.
4. **Close the tracking issue** once the advisory is resolved or overridden and the
nightly job runs clean (verify via the `✅ npm audit clean` heartbeat in the job
summary).