DevOps: set up Renovate vulnerability surfacing + nightly npm audit early-warning #818
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Issue #817 (esbuild/cookie audit-gate failure) exposed a process gap, not just a dependency problem:
mainhas no early-warning mechanism for newly-published advisories. An advisory landed against already-pinned versions, turned thenpm audit --audit-level=high --omit=devgate (.gitea/workflows/ci.yml:32-33) red onmain, and then ambushed the next unrelated PR (#774, which touched zero frontend files). The author who hit it didn't cause it and had no warning.This issue sets up the prevention layer so a freshly-published advisory is caught on a schedule, on a branch we own, instead of on a contributor's PR.
Goal
Newly-published advisories against our dependencies are surfaced proactively (scheduled Renovate job + dependency dashboard + nightly audit), never first discovered as a red gate on an unrelated PR.
Scope
Phase 0 — Stand up the Renovate runner (prerequisite — the actual load-bearing work)
.gitea/workflows/renovate.yml: weekly cron +workflow_dispatch, runningrenovatebot/github-actionpinned by digest withrenovate-versionpinned to a fixed version (notlatest— matches this repo's pin-everything posture:@v3artifacts,semgrep==1.163.0, etc.). A starter snippet lives indocs/infrastructure/self-hosted-catalogue.md:150— pin it before use.contents+pull_request+issues. (Mend's hosted app does not support self-hosted Gitea — Renovate must self-host.)platform/endpoint/repositories(or autodiscover) so Renovate targets this repo on the self-hosted Gitea.1. Turn on Renovate vulnerability surfacing
Extend
renovate.json(additive — keep the existingpackageRules):osvVulnerabilityAlertsis the load-bearing flag on self-hosted Gitea — Renovate queries OSV.dev directly (platform-agnostic).vulnerabilityAlertskeys off a platform vulnerability graph that Gitea does not expose, so treat that block as a label carrier for the OSV alerts, not an independent detector.dependencyDashboard: trueset explicitly — it is not guaranteed on without onboarding.lockFileMaintenance: refreshes transitive pins weekly so we drift into fewer advisories. Do not addautomerge— a weekly transitive bump can break the build silently; these PRs get reviewed.2. Nightly audit early-warning job
Add a separate job to
.gitea/workflows/nightly.yml(parallel todeploy-staging, not a step inside it — it must produce an independent red/green signal a deploy failure can't mask):--omit=dev) — deliberately broader than the PR gate, to catch dev-tooling advisories (esbuild, Vite, etc.) early. The PR gate stays--omit=dev(unchanged — out of scope). Document this divergence so the broader nightly result isn't mistaken for a gate failure.npm auditneeds onlyfrontend/package-lock.json—checkout+setup-node, nonpm ci/ nonode_modulescache (nightly.yml has no node setup to reuse anyway; this is a fresh job).Nightly npm audit: high-severity advisory) —GETopen issues labelledsecurity, match the marker, update if present, else create. Labels:security,devops, +P1-high(severity parity with the Renovate path). Use the auto-provided${{ secrets.GITEA_TOKEN }}(confirm it hasissues:write).ci.yml's existinggrepself-tests) asserting the dedupe title-match catches an existing-issue sample and ignores an unrelated one.Acceptance criteria
.gitea/workflows/renovate.ymlexists (pinned action + version), bot token wired, and a real Renovate PR has appeared at least once.renovate.jsonenablesosvVulnerabilityAlerts,dependencyDashboard,vulnerabilityAlerts(security+P1-high), andlockFileMaintenance(noautomerge) — existingpackageRulespreserved.npm audit --audit-level=high(including dev deps) againstfrontend/as its own job; on failure it opens/updates one deduped tracking issue.workflow_dispatchagainst a deliberately vulnerable lockfile or a lowered--audit-levelis acceptable proof).docs/infrastructure/ci-gitea.mddocuments: the runner setup, what OSV vs platform alerts mean on Gitea, the nightly job's dev-dep divergence from the PR gate, and the runbook for a nightly-opened issue (triage severity → override/pin or escalate, mirroring the #817 decision tree).Out of scope
--omit=dev; this adds a scheduled, broader sibling).Notes
Review summary — six-persona pre-implementation review (2026-06-13)
Findings folded in from the review; the original per-persona comments were removed so this issue stays the single source of truth.
Resolved decisions
--omit=dev) → §2 above; deliberately broader than the PR gate, divergence documented. (Nora)🏛️ Architecture (Markus) — "We already run Renovate" was false; the missing runner is the load-bearing work, not the config flags. Sequence: runner → prove one PR → then vuln flags. Nightly audit must be its own job, never nested inside
deploy-staging. Capture the runner decision ADR-style in the doc.👨💻 Developer (Felix) ���
npm auditneeds nonode_modules/npm ci(resolves from the lockfile);nightly.ymlhas no node setup to reuse. Dedupe via a fixed title marker:GETopensecurityissues → update-or-create. Pin the Renovate action by digest + fixedrenovate-version(the catalogue's@v40/latestviolates the repo's pin posture). Add a shell self-test for the dedupe match.🛠️ DevOps (Tobias) — Renovate has never run; existing
packageRulesare inert. Mend's hosted app doesn't support Gitea → must self-host.osvVulnerabilityAlerts(OSV.dev) is load-bearing;vulnerabilityAlertsis only a label carrier on Gitea.lockFileMaintenancemust notautomerge. Add a clean-path heartbeat so "no issue" ≠ "never ran."🛡️ Security (Nora) —
--omit=devdivergence resolved (scan dev deps); document exactly what's covered. Severity-label the nightly tracking issue (P1-high) for parity with the Renovate path. Backend Maven SCA blind spot → tracked as #820.🧪 QA (Sara) — The "Renovate PR appears OR nightly fires" AC collapses to "nightly works" until the runner exists — made explicit. The update path needs an explicit test: two consecutive manual runs → one issue, updated not duplicated (capture both run URLs).
📋 Requirements (Elicit) — Recommended splitting into two issues; not taken — kept as one issue with Phase 0 as an explicit gating prerequisite. Added the measurable dedupe AC. Milestone still unassigned (orphan).
🎨 UX (Leonie) — No concerns; pure CI / dependency tooling, no user-facing surface.