diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml
index 11d47992..b8b59cdc 100644
--- a/.gitea/workflows/nightly.yml
+++ b/.gitea/workflows/nightly.yml
@@ -161,3 +161,147 @@ jobs:
# without first re-evaluating ADR-011.
if: always()
run: rm -f .env.staging
+
+ npm-audit:
+ # Independent parallel job — a deploy failure cannot mask the audit signal
+ # and a clean audit cannot hide a broken deploy. Intentionally no `needs:`.
+ #
+ # Scans dev deps too (no --omit=dev), which is deliberately broader than the
+ # PR gate (ci.yml §Security audit) that uses --omit=dev. A nightly broader
+ # result is NOT a PR gate failure — it catches dev-tooling advisories (esbuild,
+ # Vite, etc.) early. See docs/infrastructure/ci-gitea.md §Nightly audit vs PR gate.
+ #
+ # Required Gitea secrets:
+ # NIGHTLY_AUDIT_TOKEN — PAT with issues scope only. An issues-only token
+ # means a leak via logs/process-args cannot push
+ # branches, open PRs, or read repo contents (ADR-041).
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Assert jq is available
+ run: which jq || sudo apt-get install -y jq
+
+ - name: Run npm audit and file tracking issue on findings
+ # Never run under set -x — NIGHTLY_AUDIT_TOKEN in env would leak to logs.
+ env:
+ NIGHTLY_AUDIT_TOKEN: ${{ secrets.NIGHTLY_AUDIT_TOKEN }}
+ run: |
+ MARKER="Nightly npm audit: high-severity advisory"
+ GITEA_URL="${{ github.server_url }}"
+ REPO="${{ github.repository }}"
+ RUN_URL="${GITEA_URL}/${REPO}/actions/runs/${{ github.run_id }}"
+
+ # --- Self-test (mirrors ci.yml §Assert pattern) ---
+ # Tests the exact jq test() call used in the dedupe step, before any
+ # API call, so a broken matcher fails loudly early rather than silently
+ # opening duplicate issues. Proves the regex only — create-vs-update
+ # decision is exercised by the workflow_dispatch AC.
+ echo "{\"title\": \"${MARKER}\"}" \
+ | jq -e --arg m "$MARKER" '.title | test($m; "i")' > /dev/null \
+ || { echo "FAIL: self-test — jq test() missed tracking issue title"; exit 1; }
+ echo '{"title": "fix(deps): update dependency esbuild (CVE-2025-12345)"}' \
+ | jq -e --arg m "$MARKER" '.title | test($m; "i") | not' > /dev/null \
+ || { echo "FAIL: self-test — jq test() incorrectly matched unrelated title"; exit 1; }
+ echo "Self-test passed."
+
+ # --- Run audit ---
+ # No npm ci — audit reads only the lockfile (no network, no install).
+ set +e
+ (cd frontend && npm audit --audit-level=high --json > /tmp/audit.json)
+ AUDIT_EXIT=$?
+ set -e
+
+ if [ "$AUDIT_EXIT" -ne 0 ]; then
+ # --- Build issue body with jq (never string-concat advisory text) ---
+ # Advisory overview/title text is registry-controlled; string-concat
+ # would be an injection/escaping vector into the API body. Truncate
+ # raw excerpt to 500 chars so a pathological overview can't produce
+ # a multi-MB PATCH body.
+ ISSUE_BODY=$(jq -r \
+ --arg run_url "$RUN_URL" \
+ '
+ (.vulnerabilities // {}) as $vulns |
+ ($vulns | to_entries |
+ map(select(.value.severity == "high" or .value.severity == "critical")) |
+ map("- **" + .key + "** (" + .value.severity + ")") |
+ if length > 0 then join("\n") else "_See raw output for details._" end) as $pkg_list |
+ "## npm audit: high/critical advisories\n\n" + $pkg_list +
+ "\n\n**Run:** " + $run_url +
+ "\n\nRaw audit excerpt (first 500 chars)
\n\n```\n" +
+ (tostring | .[0:500]) +
+ "\n```\n\n "
+ ' /tmp/audit.json)
+
+ # --- Dedupe: fetch open security issues, match by title marker ---
+ # Renovate vuln PRs also carry the "security" label, so >1 open
+ # "security" issue WILL occur. Title-match (not just label) ensures
+ # we deduplicate only our own tracking issue.
+ OPEN_ISSUES=$(curl -sf \
+ -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
+ "${GITEA_URL}/api/v1/repos/${REPO}/issues?state=open&type=issues&labels=security&limit=50")
+
+ MATCHED=$(echo "$OPEN_ISSUES" | jq \
+ --arg m "$MARKER" \
+ '[.[] | select(.title | test($m; "i"))] | sort_by(.created_at)')
+ MATCH_COUNT=$(echo "$MATCHED" | jq 'length')
+
+ if [ "$MATCH_COUNT" -gt 0 ]; then
+ # Patch the oldest matched issue (append run URL to body).
+ ISSUE_NUMBER=$(echo "$MATCHED" | jq -r '.[0].number')
+ EXISTING_BODY=$(echo "$MATCHED" | jq -r '.[0].body')
+ NEW_BODY=$(jq -n \
+ --arg existing "$EXISTING_BODY" \
+ --arg run_url "$RUN_URL" \
+ '$existing + "\n\n---\n\nUpdated by run: " + $run_url')
+ PAYLOAD=$(jq -n --arg body "$NEW_BODY" '{"body": $body}')
+ curl -sf -X PATCH \
+ -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "$PAYLOAD" \
+ "${GITEA_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" > /dev/null
+ echo "Updated tracking issue #${ISSUE_NUMBER}"
+ else
+ # Closed prior issue that recurs → new issue (not reopened).
+ # A re-opened issue would obscure when the advisory was re-discovered.
+ PAYLOAD=$(jq -n \
+ --arg title "$MARKER" \
+ --arg body "$ISSUE_BODY" \
+ '{"title": $title, "body": $body}')
+ CREATED=$(curl -sf -X POST \
+ -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "$PAYLOAD" \
+ "${GITEA_URL}/api/v1/repos/${REPO}/issues")
+ NEW_NUMBER=$(echo "$CREATED" | jq -r '.number')
+ echo "Opened new tracking issue #${NEW_NUMBER}"
+
+ # Labels are ignored on issue create in Gitea — add in a follow-up call.
+ LABEL_IDS=$(curl -sf \
+ -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
+ "${GITEA_URL}/api/v1/repos/${REPO}/labels?limit=50" \
+ | jq '[.[] | select(.name == "security" or .name == "devops" or .name == "P1-high") | .id]')
+ curl -sf -X POST \
+ -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{\"labels\": $LABEL_IDS}" \
+ "${GITEA_URL}/api/v1/repos/${REPO}/issues/${NEW_NUMBER}/labels" > /dev/null
+ fi
+
+ exit "$AUDIT_EXIT"
+
+ else
+ # --- Heartbeat: proves the job ran and found nothing ---
+ # "No issue created" is only meaningful evidence when paired with a
+ # visible positive signal. Without this, a never-ran job is
+ # indistinguishable from a clean run.
+ #
+ # $GITHUB_STEP_SUMMARY availability is unproven on this runner
+ # (act_runner populates it, but this is the first run to verify it).
+ # Guard before use so an unset variable does not fail the clean-path.
+ MSG="✅ npm audit clean $(date -u)"
+ if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
+ echo "$MSG" >> "$GITHUB_STEP_SUMMARY"
+ fi
+ echo "$MSG"
+ fi
diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml
new file mode 100644
index 00000000..da9e454d
--- /dev/null
+++ b/.gitea/workflows/renovate.yml
@@ -0,0 +1,44 @@
+name: Renovate
+
+# Runs Renovate daily to surface newly-published advisories via OSV.dev
+# (osvVulnerabilityAlerts) and open routine update PRs on a weekly batch
+# schedule (see renovate.json §schedule). Security/vulnerability PRs are
+# raised immediately regardless of the weekly schedule window.
+#
+# Required Gitea secrets (see docs/adr/041-renovate-runner-setup.md):
+# RENOVATE_TOKEN — PAT with scopes: contents + pull_request + issues
+# Belongs to a dedicated bot account. Branch protection
+# on main must forbid this bot pushing directly.
+#
+# Platform config is injected via env vars below; the renovate.json in the
+# repo root carries only dependency rules (no platform/endpoint/repos).
+#
+# Digest pin: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd
+# corresponds to release v46.1.15. Update by bumping both the digest and the
+# renovate-version when Renovate publishes a new release. Renovate itself
+# will open a PR to bump this digest once it runs.
+
+on:
+ schedule:
+ - cron: "0 3 * * *" # daily at 03:00 UTC — cuts OSV-alert latency to ≤1 day
+ workflow_dispatch:
+
+jobs:
+ renovate:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Run Renovate
+ # Pinned by digest — this action holds contents+pull_request+issues
+ # scopes; an unpinned tag is a supply-chain risk (see ADR-041).
+ uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15
+ with:
+ configurationFile: renovate.json
+ token: ${{ secrets.RENOVATE_TOKEN }}
+ renovate-version: "46.1.15"
+ env:
+ RENOVATE_PLATFORM: gitea
+ RENOVATE_ENDPOINT: https://git.raddatz.cloud
+ RENOVATE_REPOSITORIES: '["marcel/familienarchiv"]'
+ LOG_LEVEL: info
diff --git a/docs/adr/041-renovate-runner-setup.md b/docs/adr/041-renovate-runner-setup.md
new file mode 100644
index 00000000..41a17fcd
--- /dev/null
+++ b/docs/adr/041-renovate-runner-setup.md
@@ -0,0 +1,123 @@
+# 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 (`@`) 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.
diff --git a/docs/infrastructure/ci-gitea.md b/docs/infrastructure/ci-gitea.md
index 6d99f694..871eec78 100644
--- a/docs/infrastructure/ci-gitea.md
+++ b/docs/infrastructure/ci-gitea.md
@@ -462,3 +462,82 @@ jobs:
name: e2e-results
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).
diff --git a/docs/infrastructure/self-hosted-catalogue.md b/docs/infrastructure/self-hosted-catalogue.md
index fc9a1c61..c197db0c 100644
--- a/docs/infrastructure/self-hosted-catalogue.md
+++ b/docs/infrastructure/self-hosted-catalogue.md
@@ -151,7 +151,7 @@ receivers:
name: Renovate
on:
schedule:
- - cron: '0 3 * * 1' # every Monday at 3am
+ - cron: '0 3 * * *' # daily at 03:00 UTC — cuts OSV-alert latency to ≤1 day
workflow_dispatch:
jobs:
@@ -160,32 +160,58 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run Renovate
- uses: renovatebot/github-action@v40
+ # Pin by digest — this action holds contents+pull_request+issues token;
+ # an unpinned tag is a supply-chain risk. Update digest + renovate-version
+ # together when Renovate publishes a new release.
+ uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15
with:
configurationFile: renovate.json
- token: ${{ secrets.GITEA_TOKEN }}
- renovate-version: latest
+ token: ${{ secrets.RENOVATE_TOKEN }}
+ renovate-version: "46.1.15"
+ env:
+ RENOVATE_PLATFORM: gitea
+ RENOVATE_ENDPOINT: https://gitea.example.com # replace with your Gitea URL
+ RENOVATE_REPOSITORIES: '["org/repo"]' # replace with your repo slug
+ LOG_LEVEL: info
```
+> **Token:** `RENOVATE_TOKEN` must be a PAT on a dedicated bot account with scopes
+> `contents` + `pull_request` + `issues`. **Do not reuse** `GITEA_TOKEN` — that variable
+> is not auto-provided on self-hosted Gitea runners and must be manually created anyway;
+> using a single broad token violates least-privilege. See ADR-041.
+
### Renovate Configuration
+The `renovate.json` in the repo root carries only dependency rules — platform and
+endpoint config is injected via `env:` in the workflow above. Keep the two concerns
+separate so the config file remains portable.
+
```json
-// renovate.json
{
- "platform": "gitea",
- "endpoint": "https://gitea.example.com",
- "repositories": ["org/familienarchiv"],
- "automerge": true,
- "automergeType": "pr",
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "osvVulnerabilityAlerts": true,
+ "dependencyDashboard": true,
+ "schedule": ["before 6am on monday"],
+ "vulnerabilityAlerts": {
+ "labels": ["security", "P1-high"]
+ },
+ "lockFileMaintenance": {
+ "enabled": true,
+ "schedule": ["before 6am on monday"]
+ },
"packageRules": [
{
- "matchUpdateTypes": ["patch"],
- "automerge": true
+ "matchPackageNames": ["com.example:my-dep"],
+ "automerge": true,
+ "matchUpdateTypes": ["patch"]
}
]
}
```
+> **Do not add `automerge: true` at the root.** Security and digest-bump PRs should
+> always be reviewed manually. Per-rule `automerge` on patch-level routine deps is fine.
+
---
## Secrets Management -- age + git-crypt
diff --git a/renovate.json b/renovate.json
index 2b4af645..bae03932 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,5 +1,15 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "osvVulnerabilityAlerts": true,
+ "dependencyDashboard": true,
+ "schedule": ["before 6am on monday"],
+ "vulnerabilityAlerts": {
+ "labels": ["security", "P1-high"]
+ },
+ "lockFileMaintenance": {
+ "enabled": true,
+ "schedule": ["before 6am on monday"]
+ },
"packageRules": [
{
"description": "bucket4j-core is manually pinned outside the Spring BOM — track patch auto-merge, minor/major as PRs.",
@@ -9,13 +19,13 @@
"matchUpdateTypes": ["patch"]
},
{
- "matchPackagePatterns": ["^@tiptap/"],
+ "matchPackageNames": ["/^@tiptap/"],
"groupName": "tiptap",
"automerge": false
},
{
"description": "Digest bumps for images used in privileged CI steps (--privileged --pid=host) must be reviewed manually — a compromised image has root-equivalent host access. Covers .gitea/actions/** too: the reload-caddy alpine digest now lives in a composite action (#603).",
- "matchPaths": [".gitea/workflows/**", ".gitea/actions/**"],
+ "matchFileNames": [".gitea/workflows/**", ".gitea/actions/**"],
"matchUpdateTypes": ["digest"],
"automerge": false,
"reviewersFromCodeOwners": false