Records the negative space for #818: why no auto GITEA_TOKEN, why two tokens not one, why digest-pin on the Renovate action, OSV-vs-platform distinction on self-hosted Gitea, why the weekly schedule does not mute security PRs, why lockFileMaintenance has no automerge, and why there is no l2-containers.puml entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
124 lines
6.0 KiB
Markdown
124 lines
6.0 KiB
Markdown
# ADR-041 — Renovate runner stand-up: two-token model, OSV surfacing, digest pinning
|
|
|
|
**Date:** 2026-06-13
|
|
**Status:** Accepted
|
|
**Issue:** [#818](https://git.raddatz.cloud/marcel/familienarchiv/issues/818)
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
Issue #817 (esbuild/cookie advisory) revealed that `main` had no early-warning
|
|
mechanism for newly-published advisories. An advisory landed against already-pinned
|
|
versions, turned the `npm audit --audit-level=high --omit=dev` gate red on `main`,
|
|
and then ambushed the next unrelated PR (#774). The author who hit it did not cause
|
|
it and had no warning.
|
|
|
|
`renovate.json` existed but `renovatebot` had never actually run: there was no
|
|
`.gitea/workflows/renovate.yml` and zero Renovate-authored PRs in the repo's entire
|
|
history. The three `packageRules` (bucket4j / tiptap / privileged-digest) were
|
|
silently inert.
|
|
|
|
This ADR records the **negative space** — why specific design choices were made,
|
|
so future maintainers do not "tidy up" toward a worse outcome.
|
|
|
|
---
|
|
|
|
## Decision
|
|
|
|
### Why there is no auto-provided `GITEA_TOKEN`
|
|
|
|
Self-hosted Gitea runners do not auto-inject a `GITEA_TOKEN` equivalent.
|
|
`docs/infrastructure/ci-gitea.md` (and its current line ~251) explicitly states the
|
|
token "must be created manually." No existing workflow in this repo references
|
|
`GITEA_TOKEN` for API calls — only for container registry auth (`docker login`).
|
|
Both `RENOVATE_TOKEN` and `NIGHTLY_AUDIT_TOKEN` must be manually provisioned as
|
|
Gitea secrets by a repository admin.
|
|
|
|
### Why two tokens, not one
|
|
|
|
The two jobs have different blast radii on token compromise:
|
|
|
|
| Token | Scopes | Used by |
|
|
|-------|--------|---------|
|
|
| `RENOVATE_TOKEN` | `contents` + `pull_request` + `issues` | Renovate — must read/write files and open PRs |
|
|
| `NIGHTLY_AUDIT_TOKEN` | `issues` only | Nightly audit — only needs to file a tracking issue |
|
|
|
|
The nightly job's token appears in step `env:` and is passed to `curl -H`. A leak via
|
|
runner logs, process arguments, or a misconfigured step would expose the token.
|
|
An `issues`-only token cannot push branches, open PRs, or read repository contents —
|
|
the leaked token's blast radius is limited to creating/editing issues.
|
|
|
|
A single broad token would give any leak path full `contents` + `pull_request` write
|
|
access to the repository. That risk is asymmetric with the upside (one fewer secret).
|
|
|
|
Both tokens belong to one dedicated bot account (consistent authorship; one identity
|
|
to audit and rotate). **Branch protection on `main` must forbid the bot pushing
|
|
directly**, because a `contents`-scoped token can push to any unprotected branch.
|
|
|
|
### Why the Renovate action is digest-pinned
|
|
|
|
`renovatebot/github-action` executes with the `RENOVATE_TOKEN` in scope. That token
|
|
carries `contents` + `pull_request` + `issues` — enough to read files, open PRs, and
|
|
write issues. An unpinned `@v40` tag can be re-pointed by the upstream maintainer
|
|
(or a compromised maintainer account) at any time. A pinned digest (`@<sha>`) cannot
|
|
be silently modified; the SHA is immutable. This is the same threat model applied to
|
|
all privileged CI steps in this repo (see the `matchFileNames` rule in `renovate.json`
|
|
for `.gitea/workflows/**`).
|
|
|
|
Renovate itself will open a PR to bump the digest when a new release ships, which is
|
|
the intended update path.
|
|
|
|
### Why `osvVulnerabilityAlerts` is the load-bearing detector on Gitea
|
|
|
|
Renovate's `vulnerabilityAlerts` config key triggers off a *platform* vulnerability
|
|
graph. GitHub exposes the GitHub Advisory Database via its API; **Gitea does not
|
|
expose an equivalent vulnerability graph**. On self-hosted Gitea, `vulnerabilityAlerts`
|
|
is effectively a label carrier — it attaches the configured labels to PRs that
|
|
`osvVulnerabilityAlerts` already detected, but it is not an independent detector.
|
|
|
|
`osvVulnerabilityAlerts: true` is the load-bearing flag: Renovate queries
|
|
[OSV.dev](https://osv.dev) directly (platform-agnostic). The runner host must be able
|
|
to reach OSV.dev over HTTPS — if egress is filtered, allow `osv.dev:443` or the flag
|
|
silently no-ops.
|
|
|
|
### Why the root `schedule` does not mute security PRs
|
|
|
|
`"schedule": ["before 6am on monday"]` in `renovate.json` batches **routine** dependency
|
|
updates (version bumps outside any security context) to a weekly window. This reduces
|
|
noise from routine update PRs while still allowing review before merge.
|
|
|
|
**Security and vulnerability PRs bypass the schedule by design** — Renovate raises
|
|
them immediately regardless of the schedule window. A future "tidy-up" that removes
|
|
or widens the schedule cannot mute vulnerability alerts; this is worth stating
|
|
explicitly to prevent that misunderstanding.
|
|
|
|
### Why `lockFileMaintenance` has no `automerge`
|
|
|
|
`lockFileMaintenance` refreshes transitive pins weekly so the dependency tree drifts
|
|
into fewer advisories over time. It is explicitly set without `automerge: true` because
|
|
a weekly transitive pin refresh can silently break the build if a transitive dep
|
|
introduces a breaking change. These PRs are small and should be reviewed.
|
|
|
|
### Why there is no entry in `l2-containers.puml`
|
|
|
|
`docs/architecture/c4/l2-containers.puml` documents long-lived infrastructure
|
|
containers (services that run continuously). Renovate is a scheduled CI job that runs
|
|
on a Gitea Actions runner and exits — it is not a long-lived container. Adding it to
|
|
the container diagram would misrepresent the architecture. This omission is deliberate,
|
|
not an oversight.
|
|
|
|
---
|
|
|
|
## Consequences
|
|
|
|
- Newly-published advisories against our frontend dependencies are surfaced within
|
|
one day (daily Renovate cron) rather than at the next contributor PR.
|
|
- A nightly `npm audit` job provides an independent signal for dev-dependency advisories
|
|
that Renovate may not cover via OSV.
|
|
- Two secrets (`RENOVATE_TOKEN`, `NIGHTLY_AUDIT_TOKEN`) must be manually provisioned
|
|
and rotated annually (or on suspected compromise). See
|
|
`docs/infrastructure/ci-gitea.md` for the runbook.
|
|
- The bot account must be kept active and branch protection on `main` must forbid
|
|
it pushing directly. These are operational prerequisites, not code invariants.
|