Skip to content
Back to skills

Renovate

ASecurity

Audit, write, or revise renovate.json. Use when adding Renovate, troubleshooting unexpected (or missing) update PRs, hardening against supply-chain attacks, or evolving an existing config.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 1, 2026
datagojavabashdockergitdatabasesecurity

Security analysis

A100/100

Scanned October 7, 2026

npx -y skills add alunduil/alunduil-chezmoi --skill renovate --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Renovate?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Renovate
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/alunduil-renovate/badge)](https://www.skillsdirectory.com/skills/alunduil-renovate)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: renovate
description: Audit, write, or revise renovate.json. Use when adding Renovate, troubleshooting unexpected (or missing) update PRs, hardening against supply-chain attacks, or evolving an existing config.
---

A **silent stall** is a dependency that can never update yet looks current: no PR, no error, no `[Updates: ...]` on the dashboard.

## Defaults

```json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:best-practices",
    "security:openssf-scorecard",
    "customManagers:githubActionsVersions"
  ],
  "timezone": "Europe/London",
  "reviewers": ["alunduil"],
  "labels": ["dependencies"],
  "pre-commit": { "enabled": true }
}
```

- This block is identical in every repo and is a standing candidate for a shared preset (`github>alunduil/renovate-config`, a `default.json`); Renovate also checks `<owner>/renovate-config` when onboarding a new repo. Until that repo exists, keep the block inline and change it everywhere at once.
- Omit `baseBranchPatterns`. It is auto-detected, and hard-coding it is the one field that differs per repo (`main` vs `master`) — the thing that would block sharing.
- Omit `schedule` unless a repo wants batching. With a bake period the PR is already delayed, and Renovate's default `prHourlyLimit` of 2 rate-limits the rest.
- `packageRules` and `customManagers` are `mergeable: true`, so preset arrays concatenate with a repo's own rather than replacing them. `labels` and `reviewers` are not mergeable — setting them in a repo replaces the inherited value. `addLabels` merges and appends; `labels` in a shared preset is dropped wholesale by the first repo setting its own.
- `labels` — default `[]`, so Renovate labels nothing without this. A stale sweep exempting Renovate PRs by label depends on it: closing a Renovate PR tells Renovate the version is unwanted and it will not re-offer it. `vulnerabilityAlerts` takes no `labels`.
- `reviewers` — without it, Renovate PRs land silent. Use `assignees` instead for a creation-time ping with no rebase notifications.
- `pre-commit: { enabled: true }` — opt-in manager; enable unconditionally. No-op without `.pre-commit-config.yaml`; replaces pre-commit.ci where the file exists.
- `config:best-practices` = `config:recommended` + `docker:pinDigests` + `helpers:pinGitHubActionDigests` + `:configMigration` + `:pinDevDependencies` + `abandonments:recommended` + `security:minimumReleaseAgeNpm` + `:maintainLockFilesWeekly`. It does *not* include OpenSSF scorecard; that is `security:openssf-scorecard`, which adds a badge column to PR bodies.

## Supply-chain hardening

`config:best-practices` bakes npm releases for 3 days (`security:minimumReleaseAgeNpm`) but nothing else. Widen it to every datasource:

```json
{
  "minimumReleaseAge": "3 days",
  "minimumReleaseAgeBehaviour": "timestamp-optional",
  "osvVulnerabilityAlerts": true,
  "packageRules": [
    {
      "matchUpdateTypes": ["pin", "pinDigest", "digest", "lockFileMaintenance",
                           "lockfileUpdate", "rollback", "bump", "replacement"],
      "minimumReleaseAge": null
    }
  ]
}
```

- `minimumReleaseAge` — 3-7 days catches the common attack shape (publish → community flags → upstream yanks within a day or two).
- A release with no timestamp is a silent stall. The default `timestamp-required` treats it as never stable, and the default `internalChecksFilter: "strict"` cuts no branch while `renovate/stability-days` pends. Two causes, one fix each:
  - **Update type** — the eight types in the carve-out never carry a timestamp. `config:best-practices` pulls in `:maintainLockFilesWeekly` and both digest-pinning presets, so `lockFileMaintenance`, `digest`, and `pinDigest` are always in scope.
  - **Datasource** — some datasources return no timestamp for any release; with `hackage`, every cabal dependency stalls. These are ordinary major/minor/patch updates the carve-out cannot reach. `timestamp-optional` lets a timestamp-less release through with a Dependency Dashboard warning; releases that carry a timestamp still bake.
  - Keep both. The carve-out removes the age check from the eight types, so the dashboard warning names only datasources that lack timestamps.
- A **bake bypass** is a lockfile refresh that resolves past the bake. Renovate cannot hand the package manager an exact version, so a manifest range resolves to its newest release, baked or not. The artifact update fails, and Renovate commits a lockfile holding a version the PR title does not name. `:pinDevDependencies` pins only the depTypes `devDependencies`, `dev-dependencies`, and `dev`.
  - **Non-runtime** — a Poetry group's depType is its name (`test`, `typing`). PEP 621 manifests yield `dependency-groups`, `tool.uv.dev-dependencies`, and `tool.pdm.dev-dependencies`. Pin each one:

    ```json
    {
      "matchManagers": ["poetry"],
      "matchDepTypes": ["test"],
      "rangeStrategy": "pin"
    }
    ```

  - **Runtime** — a published library's runtime ranges stay ranges; downstream resolvers need them.

- `osvVulnerabilityAlerts: true` — widens alerts beyond GitHub's advisory database to OSV. Defaults to `false`. Haskell repos are the exception: OSV fixes arrive as an open range (`>= <fixed>`), `pvp` accepts only two-bound ranges, and the generated rule aborts every run. Treat a `false` there as deliberate and look for its tracking issue.
- Do *not* write `internalChecksFilter: "strict"` or `vulnerabilityAlerts: { "minimumReleaseAge": "0 days" }`. Both are already the defaults (`lib/config/options/index.ts`: `internalChecksFilter` default `strict`; the `vulnerabilityAlerts` default object contains `minimumReleaseAge: null`, force-applied over the top-level bake).
- `minimumReleaseAge: "0 days"` is identical to `null` as of Renovate 42.19.5. Prefer `null`.

## Version pins in scripts

Annotate the pin; do not write a manager per pin. One generic manager reads them all, and a new pin needs no config change:

```bash
# renovate: datasource=github-releases depName=mikefarah/yq
YQ_VERSION="v4.53.2"
```

```json
{
  "customType": "regex",
  "managerFilePatterns": ["script/install/*", ".chezmoiscripts/*.tmpl"],
  "matchStrings": [
    "# renovate: datasource=(?<datasource>[a-zA-Z0-9-._]+?) depName=(?<depName>[^\\s]+?)(?: packageName=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION=\"(?<currentValue>.+?)\""
  ]
}
```

- Annotation field order is fixed by the regex: datasource, depName, packageName, versioning, extractVersion, registryUrl. `datasource`, `versioning`, `extractVersion` and `registryUrl` are recognised capture-group names — no `*Template` fields needed. An out-of-order annotation does not match — a silent stall.
- Set `versioning=loose` on every plain pin whose upstream is not semver. The default, `semver-coerced`, reads a four-component version (Haskell PVP, some .NET and Java tools) as unstable, and `ignoreUnstable` drops every release — a silent stall. Two-component versions work by accident: `3.10` coerces to `3.10.0`.
- `pvp` updates only two-bound cabal ranges under `rangeStrategy: "widen"`; a plain pin never updates. `datasource=hackage` defaults to `pvp`, so write `versioning=loose` on a hackage pin.
- For workflow YAML (`X_VERSION: "v1"` under `env:`) extend `customManagers:githubActionsVersions` instead of writing this yourself; add `extractVersion=^v(?<version>.+)$` when the consumer wants the tag without its leading `v`.
- Hoist a `with:` version input to `env:` only when its action is missing from the [known-actions table](https://docs.renovatebot.com/modules/manager/github-actions/#updating-with-values-in-github-actions). Renovate reads a listed action's input natively, `actions/setup-*` included; hoisting it removes a working dependency.
- Upstream ships equivalents for Dockerfiles, Makefiles, `*.tfvars`, `pom.xml`, and several CI formats — check `customManagers:*` before writing a regex.
- Keep a bespoke manager only where an annotation cannot go: a version embedded in a URL (capture `depName` and `currentValue` from the URL itself), or a snippet in user-facing docs where a `# renovate:` line would be copy-pasted by a reader.
- `managerFilePatterns` (renamed from `fileMatch`): bare strings are globs; wrap in `/.../` for a regex. Prefer the glob.
- Validate `matchStrings` against the real files after adding a manager or editing an annotation — the regex is ECMAScript/RE2 (no lookahead, no backreferences), and a non-matching manager is a silent stall.

## Validation

Wire `renovate-config-validator` as a pre-commit hook so schema typos, deprecated fields, and malformed custom-manager regex fail at commit time instead of surfacing as Repository Problems on the next Renovate run.

```yaml
- repo: https://github.com/renovatebot/pre-commit-hooks
  rev: <latest>
  hooks:
    - id: renovate-config-validator
      args: [--strict, --no-global]
```

- `--strict` — fail on configs that need migration (e.g. `fileMatch` → `managerFilePatterns`), not only on outright errors. Neither flag is upstream default; both go in `args`.
- `--no-global` — treat the file as repo-level config. Without it the validator interprets it as global self-hosted config and misreports repo-only fields.
- The validator does not resolve remote presets, so it cannot tell you an `extends` target is missing or that a shared preset changed under you.
- Upstream docs: <https://docs.renovatebot.com/config-validation/>.

## Liveness

A hosted Renovate job that dies is never retried and reports nowhere, so a repo with a valid `renovate.json` can stop getting updates unnoticed. Self-hosted Renovate reports its failures in the repo's Actions log and needs no check.

Renovate edits the open Dependency Dashboard issue as updates come and go, so a dashboard left untouched means Renovate has stopped. Add this job, taken from [genshin.dungeon.studio `daily.yml`](https://github.com/dungeon-studio/genshin.dungeon.studio/blob/main/.github/workflows/daily.yml), to the repo's `daily.yml`; create that workflow if the repo has none.

```yaml
renovate-liveness:
  name: Check Renovate liveness
  runs-on: ubuntu-latest
  timeout-minutes: 5
  permissions:
    issues: read
  steps:
    - uses: actions/github-script@<sha> # <version>
      with:
        script: |
          const STALE_DAYS = 10;
          const MS_PER_DAY = 86_400_000;
          const query = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open in:title "Dependency Dashboard" author:app/renovate`;
          const { data } = await github.rest.search.issuesAndPullRequests({ q: query });
          const dashboard = data.items[0];
          if (!dashboard) {
            core.setFailed('No open Dependency Dashboard: Renovate is uninstalled or disabled on this repository. Reinstall the app or re-enable the repository.');
            return;
          }
          const ageDays = (Date.now() - new Date(dashboard.updated_at)) / MS_PER_DAY;
          if (ageDays > STALE_DAYS) {
            core.setFailed(`Dependency Dashboard #${dashboard.number} last updated ${ageDays.toFixed(1)} days ago: Renovate runs are dying. Read the Mend job log, then trigger a run by hand.`);
          }
```

- Set `STALE_DAYS` to about twice the longest healthy quiet spell.
- The failed run is the alert: GitHub emails it to whoever created the workflow or last edited its cron. Passing runs are silent, so a daily check adds no noise.

## Dashboard reading

Renovate opens a "Dependency Dashboard" issue. Read it before assuming a bug:

- **Detected Dependencies** without `[Updates: ...]` = current or silently stalled; the dashboard cannot tell them apart.
- **Pending Status Checks** — the bake period holding an update back. A resident older than the bake points to a missing carve-out or a datasource with no timestamps.
- **Repository Problems** — investigate. "Base branch does not exist" usually means a stale config reference or a transient mid-run state.
- **Config Migration Needed** — Renovate offers an automated PR for field renames (e.g. `fileMatch` → `managerFilePatterns`, `baseBranches` → `baseBranchPatterns`). Tick the checkbox or hand-migrate.
- **Open** — pending PRs; the per-row checkboxes force a rebase/retry.

## Procedure

1. Confirm any field name, default, or preset body you plan to rely on against the source, not memory: defaults in `lib/config/options/index.ts`, preset bodies in `lib/config/presets/internal/*.preset.ts`. Docs summaries and prior commits drift — several fields once worth writing are now defaults.
2. Read `renovate.json` if present, and any preset it extends.
3. **Greenfield** — write the Defaults and Supply-chain hardening blocks. Add `customManagers` only for pins the annotation convention cannot reach. Add the `renovate-config-validator` pre-commit hook (see Validation).
4. **Audit existing** — flag drift:
   - Restated defaults: `internalChecksFilter`, `vulnerabilityAlerts.minimumReleaseAge`, `baseBranchPatterns`.
   - Silent-stall causes: a no-timestamp carve-out missing or narrower than the eight update types, missing `minimumReleaseAgeBehaviour: "timestamp-optional"`, a non-semver plain pin without `versioning=loose`, unannotated `*_VERSION=` pins, a literal `with:` version on an action missing from the known-actions table.
   - Bake bypass: a non-runtime depType on a lockfile manager with no `rangeStrategy: "pin"` rule.
   - Structure: one manager per pin where an annotation would do, a listed action's `with:` input hoisted to `env:`, deprecated `fileMatch`/`baseBranches`, missing validator hook, missing `labels` where a workflow exempts Renovate PRs by label.
   - Liveness: a repo on hosted Renovate with no dashboard-staleness job in a daily workflow.
5. **Cross-check** — compare each detected dependency against its upstream latest release, each Pending Status Checks resident against its release date, and each tool's pins across workflows; diverging versions of one tool mean one pin is unmanaged. A config can pass step 4 and still hold a silent stall; only these comparisons show it.
6. Surface findings before editing. Apply only after scope is agreed.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.