Summary
Automated dependency updates currently have two opposite problems, and they have to be fixed together because each one's fix is the other's exception.
Routine updates are adopted the moment a release is published. That is exactly the window in which a compromised package release is live and not yet yanked - the pattern behind recent package-registry supply-chain incidents, where malicious versions were published and pulled within hours.
Security fixes, meanwhile, are adopted too slowly. Advisory-driven updates are treated as ordinary updates and inherit the schedule of whichever group they fall into, so a published advisory for a dependency in use can wait up to a week before a pull request appears.
The fix is one change with two halves: quarantine new releases for a minimum age, and exempt advisory-driven updates from that quarantine so they are raised immediately. Landing either half alone makes things worse - a quarantine on its own delays security fixes further, and immediate advisory updates on their own leave the compromised-release window wide open.
Details
renovate.json puts every PHP package behind before 2am on Sunday, Drupal core behind before 2am, JavaScript packages behind before 2am on Sunday, and container images and Actions behind before 3am. There is no vulnerability-alert configuration and no minimum release age. The GitHub Actions group is configured with automerge: true, so an Actions release can be adopted and merged automatically within minutes of publication with no human in the loop.
The security audit job already tells a project it is exposed - composer audit --locked fails the build as soon as an advisory lands against a resolved dependency. What is missing is the other half: the remedy does not start until the schedule allows it. The result is a repository that knows it is vulnerable and then waits up to a week for the bot to offer the fix, or has someone bump it by hand.
Setting a minimum release age changes the risk profile cheaply. Routine updates arrive a few days later than they do today, and releases that are pulled, yanked or superseded inside that window never reach the project at all. The cost is bounded and predictable; the benefit is that the project is never the first consumer of a compromised release.
Proposed implementation
1. Add the quarantine as a top-level setting
A top-level minimumReleaseAge in renovate.json applies to every manager at once, so no per-group rules need touching and the existing grouping and schedules stay exactly as they are:
"minimumReleaseAge": "3 days",
Three days is the proposal, not a fixed requirement: long enough to cover the observed window in which malicious releases were pulled, short enough that a weekly grouped update is unaffected in practice, since the group only runs once a week anyway. For the container-image and GitHub Actions groups, which run nightly, this is the only group where the delay is actually visible.
2. Add the advisory exemption as a vulnerabilityAlerts block
vulnerabilityAlerts is the config object Renovate applies specifically to pull requests raised in response to an advisory, so it is the right place to carve out the exception:
"vulnerabilityAlerts": {
"enabled": true,
"minimumReleaseAge": null,
"schedule": [],
"labels": ["Dependencies", "Security"],
"prPriority": 10,
"prConcurrentLimit": 0
},
minimumReleaseAge: null lifts the quarantine for advisory-driven updates, which is the whole point of pairing the two halves.
schedule: [] means no schedule restriction, so these are raised as soon as the advisory is known rather than waiting for the group's window.
labels reuses the repository's existing Security label, so advisory pull requests are distinguishable at a glance with no new label to create.
prPriority and prConcurrentLimit: 0 stop an advisory fix from queueing behind routine updates - the repository sets prConcurrentLimit: 10, and a security fix should not be held by a full queue.
3. Decide how advisories are sourced
This is the part that needs a decision rather than a config line. Renovate can learn about advisories two ways, and the template supports two Renovate deployment modes through the installer's dependency-updates provider handler, which behave differently:
- Hosted app (
renovatebot_app): reads the repository's own alerts natively, so the block above is sufficient.
- Self-hosted via CI (
renovatebot_ci): runs from .github/workflows/update-dependencies.yml with token: ${{ secrets.RENOVATE_TOKEN }}. That token must be able to read the repository's Dependabot alerts, or vulnerabilityAlerts silently does nothing and the feature appears to work while doing exactly nothing. The token permission requirement has to be documented as part of this change.
osvVulnerabilityAlerts: true is the fallback for the self-hosted case, since it sources advisories from the OSV database instead of requiring alert-read permission. It is flagged experimental upstream, so it should be evaluated rather than adopted blindly.
4. Make quarantined updates visible
With a quarantine in place, an update that is waiting out its window is reported as pending in the Dependency Dashboard. The self-hosted workflow sets RENOVATE_DEPENDENCY_DASHBOARD: ${{ vars.RENOVATE_DEPENDENCY_DASHBOARD || 'false' }}, so the dashboard is off unless a project opts in - which means quarantined updates would be invisible by default and the delay would look like the bot having stopped working.
Either the dashboard default flips for projects using the quarantine, or the documentation states plainly that the dashboard is how pending updates are seen. This needs deciding as part of the change, not after someone reports the bot as broken.
5. Verify with a dry run before merging
The workflow already supports this: set the RENOVATE_DRY_RUN repository variable to true and inspect the run log. That is the verification path for the two behaviours that cannot be confirmed by reading config alone - that vulnerabilityAlerts genuinely overrides a schedule set inside packageRules rather than merely merging with it, and that minimumReleaseAge: null is accepted as an override rather than needing internalChecksFilter. Both should be confirmed against a real run rather than assumed.
Scope limitation worth knowing up front
This repository does not commit a root composer.lock. Advisory detection works from a resolved dependency graph, so the template repository itself will get little or nothing from the PHP side of this - the alerts have nothing precise to match against. Consumer projects, which do commit a lock file, are where the benefit actually lands.
The practical consequence is that this cannot be fully validated on the template and needs checking against a consumer project. It also means the JavaScript side, which does commit package-lock.json, is the only part observable here.
Acceptance criteria
- A top-level minimum release age is configured, so a release is not proposed until it has been published for at least that long.
- The chosen window is documented with the reasoning, so projects can lengthen or shorten it deliberately.
- Updates raised in response to a published security advisory are exempt from the quarantine and are not subject to the group schedules, appearing as soon as the advisory is available.
- Advisory-driven pull requests carry the existing
Security label so they are distinguishable from routine grouped updates.
- An advisory fix is not blocked by the routine concurrent-pull-request limit.
- Existing grouping and schedules for routine updates are unchanged.
- Automerged groups remain automerged, but only for releases that have cleared the quarantine window.
- The advisory source is settled for both the hosted and the self-hosted provider, and the self-hosted token permission requirement is documented.
- Pending quarantined updates are discoverable, either by the dashboard default or by documentation that states how to see them.
- Both halves land together, since neither is an improvement on its own.
- The behaviour is verified with a dry run, confirming that the advisory exemption overrides the group schedules and that the quarantine override is accepted.
- The configuration passes the existing Renovate config validation step in the dependency-update workflow.
- Installer fixtures and snapshots are updated, since
renovate.json is processed by the installer's dependency-updates provider handler and shipped to consumers.
- The documentation covers the interaction between the quarantine, the advisory exemption and the update schedules, and how a project turns either half off if its policy requires batching.
Summary
Automated dependency updates currently have two opposite problems, and they have to be fixed together because each one's fix is the other's exception.
Routine updates are adopted the moment a release is published. That is exactly the window in which a compromised package release is live and not yet yanked - the pattern behind recent package-registry supply-chain incidents, where malicious versions were published and pulled within hours.
Security fixes, meanwhile, are adopted too slowly. Advisory-driven updates are treated as ordinary updates and inherit the schedule of whichever group they fall into, so a published advisory for a dependency in use can wait up to a week before a pull request appears.
The fix is one change with two halves: quarantine new releases for a minimum age, and exempt advisory-driven updates from that quarantine so they are raised immediately. Landing either half alone makes things worse - a quarantine on its own delays security fixes further, and immediate advisory updates on their own leave the compromised-release window wide open.
Details
renovate.jsonputs every PHP package behindbefore 2am on Sunday, Drupal core behindbefore 2am, JavaScript packages behindbefore 2am on Sunday, and container images and Actions behindbefore 3am. There is no vulnerability-alert configuration and no minimum release age. The GitHub Actions group is configured withautomerge: true, so an Actions release can be adopted and merged automatically within minutes of publication with no human in the loop.The security audit job already tells a project it is exposed -
composer audit --lockedfails the build as soon as an advisory lands against a resolved dependency. What is missing is the other half: the remedy does not start until the schedule allows it. The result is a repository that knows it is vulnerable and then waits up to a week for the bot to offer the fix, or has someone bump it by hand.Setting a minimum release age changes the risk profile cheaply. Routine updates arrive a few days later than they do today, and releases that are pulled, yanked or superseded inside that window never reach the project at all. The cost is bounded and predictable; the benefit is that the project is never the first consumer of a compromised release.
Proposed implementation
1. Add the quarantine as a top-level setting
A top-level
minimumReleaseAgeinrenovate.jsonapplies to every manager at once, so no per-group rules need touching and the existing grouping and schedules stay exactly as they are:Three days is the proposal, not a fixed requirement: long enough to cover the observed window in which malicious releases were pulled, short enough that a weekly grouped update is unaffected in practice, since the group only runs once a week anyway. For the container-image and GitHub Actions groups, which run nightly, this is the only group where the delay is actually visible.
2. Add the advisory exemption as a
vulnerabilityAlertsblockvulnerabilityAlertsis the config object Renovate applies specifically to pull requests raised in response to an advisory, so it is the right place to carve out the exception:minimumReleaseAge: nulllifts the quarantine for advisory-driven updates, which is the whole point of pairing the two halves.schedule: []means no schedule restriction, so these are raised as soon as the advisory is known rather than waiting for the group's window.labelsreuses the repository's existingSecuritylabel, so advisory pull requests are distinguishable at a glance with no new label to create.prPriorityandprConcurrentLimit: 0stop an advisory fix from queueing behind routine updates - the repository setsprConcurrentLimit: 10, and a security fix should not be held by a full queue.3. Decide how advisories are sourced
This is the part that needs a decision rather than a config line. Renovate can learn about advisories two ways, and the template supports two Renovate deployment modes through the installer's dependency-updates provider handler, which behave differently:
renovatebot_app): reads the repository's own alerts natively, so the block above is sufficient.renovatebot_ci): runs from.github/workflows/update-dependencies.ymlwithtoken: ${{ secrets.RENOVATE_TOKEN }}. That token must be able to read the repository's Dependabot alerts, orvulnerabilityAlertssilently does nothing and the feature appears to work while doing exactly nothing. The token permission requirement has to be documented as part of this change.osvVulnerabilityAlerts: trueis the fallback for the self-hosted case, since it sources advisories from the OSV database instead of requiring alert-read permission. It is flagged experimental upstream, so it should be evaluated rather than adopted blindly.4. Make quarantined updates visible
With a quarantine in place, an update that is waiting out its window is reported as pending in the Dependency Dashboard. The self-hosted workflow sets
RENOVATE_DEPENDENCY_DASHBOARD: ${{ vars.RENOVATE_DEPENDENCY_DASHBOARD || 'false' }}, so the dashboard is off unless a project opts in - which means quarantined updates would be invisible by default and the delay would look like the bot having stopped working.Either the dashboard default flips for projects using the quarantine, or the documentation states plainly that the dashboard is how pending updates are seen. This needs deciding as part of the change, not after someone reports the bot as broken.
5. Verify with a dry run before merging
The workflow already supports this: set the
RENOVATE_DRY_RUNrepository variable totrueand inspect the run log. That is the verification path for the two behaviours that cannot be confirmed by reading config alone - thatvulnerabilityAlertsgenuinely overrides a schedule set insidepackageRulesrather than merely merging with it, and thatminimumReleaseAge: nullis accepted as an override rather than needinginternalChecksFilter. Both should be confirmed against a real run rather than assumed.Scope limitation worth knowing up front
This repository does not commit a root
composer.lock. Advisory detection works from a resolved dependency graph, so the template repository itself will get little or nothing from the PHP side of this - the alerts have nothing precise to match against. Consumer projects, which do commit a lock file, are where the benefit actually lands.The practical consequence is that this cannot be fully validated on the template and needs checking against a consumer project. It also means the JavaScript side, which does commit
package-lock.json, is the only part observable here.Acceptance criteria
Securitylabel so they are distinguishable from routine grouped updates.renovate.jsonis processed by the installer's dependency-updates provider handler and shipped to consumers.