On this page
Supported tools
What depcurfew configures and checks for each tool, from its knowledge base, where every value links to the official documentation or changelog it was verified against.
| Tool | Release-age delay | Exceptions | Install-script allow-list (Python: packages built from source; Composer: plugins) | Dependency sources | Verified |
|---|---|---|---|---|---|
| npm | min-release-age in .npmrc (days), since 11.10.0 | min-release-age-exclude, since 11.17.0 | allowScripts in package.json | git: allow-gitremote tarball: allow-remotelocal tarball: allow-filelocal directory: allow-directory | 2026-10-28 – 2026-11-11 |
| pnpm | minimumReleaseAge in pnpm-workspace. (minutes), since 10.16.0 | minimumReleaseAgeExclude, since 10.16.0 | allowBuilds in pnpm-workspace. | git: blockExoticSubdepsremote tarball: blockExoticSubdeps | 2026-10-28 – 2026-11-09 |
| Yarn (Berry) | npmMinimalAgeGate in .yarnrc.yml (minutes), since 4.10.2 | npmPreapprovedPackages, since 4.10.0 | dependenciesMeta. in package.json | git: approvedGitRepositories | 2026-10-28 – 2026-11-03 |
| Bun | install. in bunfig.toml (seconds), since 1.3.0 | install., since 1.3.0 | trustedDependencies in package.json | none | 2026-10-28 – 2026-11-11 |
| Deno | minimumDependencyAge in deno.json (minutes) or min-release-age in .npmrc (days), since 2.5.5 / 2.6.0 ⚠️ | minimumDependencyAge., since 2.5.5 | allowScripts in deno.json | none | 2026-10-28 – 2026-11-11 |
| uv | exclude-newer in uv.toml, pyproject.toml (duration string), since 0.9.17 | exclude-newer-package, since 0.9.25 | no-binary-package in pyproject.toml | none | 2026-10-30 – 2026-11-09 |
| pip | uploaded-prior-to (days) on the command line or in the environment, no project file, since 26.1.0 | none | --no-binary in requirements.txt | none | 2026-10-30 – 2026-11-11 |
| Poetry | solver. in poetry.toml (days), since 2.4.0 | solver., since 2.4.0 | installer. in poetry.toml | none | 2026-10-30 – 2026-11-03 |
| PDM | exclude-newer in pyproject.toml (duration string), since 2.26.9 | exclude-newer-override, since 2.29.1 | no-binary in pyproject.toml | none | 2026-10-30 – 2026-11-03 |
| Pipenv | cool-down-period in Pipfile (duration string), since 2026.6.2 | none | none | none | 2026-10-30 – 2026-11-11 |
| Composer | config. in composer.json (duration string), since 2.11.0 | config., since 2.11.0 | config. in composer.json | none | 2026-11-02 – 2026-11-11 |
| Bundler | cooldown in Gemfile, gems.rb (days), since 4.0.13 | none | none | none | 2026-11-03 |
| Cargo | registry. in . (duration string), since 1.100.0 | none | none | none | 2026-11-12 – 2026-11-13 |
| Renovate | minimumReleaseAge in renovate.json, renovate.jsonc … (duration string), since 35.46.0 | packageRules: matchPackageNames + minimumReleaseAge: null, since 35.46.0 ❓ | none | n/a | 2026-10-28 – 2026-11-03 |
| Dependabot | cooldown in ., . (days), since 2025-07-01 | cooldown.exclude, since 2025-07-01 ❓ | none | n/a | 2026-10-28 – 2026-11-03 |
| Cargo (bots only) | registry. in ., since Cargo 1.100.0 (depcurfew does not write it) ❓; the delay comes from the bots: Dependabot cargo, Renovate cargo | n/a | n/a | n/a | 2026-11-03 |
| Go modules (bots only) | none ❓; the delay comes from the bots: Dependabot gomod, Renovate gomod | n/a | n/a | n/a | 2026-11-03 |
| Maven (bots only) | none ❓; the delay comes from the bots: Dependabot maven, Renovate maven | n/a | n/a | n/a | 2026-11-03 |
| Gradle (bots only) | none ❓; the delay comes from the bots: Dependabot gradle, Renovate gradle | n/a | n/a | n/a | 2026-11-03 |
| NuGet (bots only) | minPublishAgeHours in nuget.config, since .NET SDK 11.0.100, only for the versions dotnet package add and dotnet package update choose (depcurfew does not write it); the delay comes from the bots: Dependabot nuget, Renovate nuget | n/a | n/a | n/a | 2026-11-03 – 2026-11-11 |
⚠️ official sources disagree (depcurfew takes the safer reading) · ❓ official sources are incomplete. Every value with its source: docs/knowledge.md.
Yarn Classic (1.x) is detected but not supported: apply writes nothing for it. doctor still looks up the versions its yarn.lock locks for known malicious versions (known-malicious, with --osv or the local data of depcurfew osv update): the registry tarball of each entry's resolved, which Yarn Classic fetches, read as the parser of Yarn 1.22 reads the file.
The Yarn that runs. Every Yarn from 1.18 runs the release that yarnPath in .yarnrc.yml names instead of itself, with or without Corepack, so a yarnPath to a release in the repository named yarn-<version>.cjs, as yarn set version names it, is the version of the project and wins over packageManager. apply writes only the settings that release supports, so a Yarn 3 project gets no release-age delay and no allow-list of git repositories, settings Yarn 3.6.4 would stop at as unknown, and its version-too-old points to yarnPath and names yarn set version. doctor checks that release (manager-version-too-old and the other rules), and setting-ignored-by-version warns when packageManager pins another release, which then has no effect. A yarnPath whose file name gives no version, a path outside the repository or a missing file leaves the version as before: packageManager, or unknown.
pnpm: include, pnpmfiles and plugins. From pnpm 12.15, include in pnpm-workspace.yaml can give settings to local YAML files and to config dependencies, each setting with one owner. depcurfew reads every setting where pnpm takes it from: apply writes it into the file that owns it (the file whose list names it, also when the file does not set it yet; a '*' file that sets it; otherwise pnpm-workspace.yaml) and never into a second place, which pnpm rejects with ERR_PNPM_DUPLICATE_SETTING, and doctor reports a weakening setting in such a file with its file and line. A setting a config dependency owns (or a file outside the directory) can be neither read nor written: apply says so (cooldown-not-written, setting-owned-elsewhere) and doctor warns (pnpm-config-unverifiable), as it does for an include pnpm rejects. The updateConfig hook of a pnpmfile (.pnpmfile.mjs, .pnpmfile.cjs, or the files the pnpmfile and globalPnpmfile settings name) and of the pnpmfile pnpm loads by itself from a config dependency named pnpm-plugin-*, @*/pnpm-plugin-* or @pnpm/plugin-* can change every setting after pnpm has read its files. depcurfew does not run them, so doctor warns about each one (an error for a plugin the knowledge base knows to set a shorter delay than the policy, such as @pnpm/plugin-better-defaults 0.3.0) and explain lists them (pnpm-hook). ignoreScripts does not stop them; ignorePnpmfile: true does, and pnpmfile: [] keeps pnpm from loading a new .pnpmfile.mjs by itself. Versions before 12.15 read pnpm-workspace.yaml alone, as before.
Tarballs of private npm registries. In package-lock.json, a tarball URL counts as a registry package (for lockfile-forbidden-source and known-malicious) when it is in npm's layout, in the layout of GitHub Packages (https://npm.pkg.github.com/download/@scope/name/<version>/<digest>) or of GitLab and Artifactory (…/@scope/name/-/@scope/name-<version>.tgz), or below the registry that the .npmrc next to package-lock.json sets for the package, which npm 12 installs as a registry package too; a dependency that asks for it by URL makes it a tarball URL all the same. A tarball URL in another layout that package.json asks for by version stays an error, and its fix says to set that registry in .npmrc, not to allow tarball URLs.
Where Node.js and Python come from. doctor also reads the files that choose the runtime: .nvmrc, .node-version, engines.node, Volta, .tool-versions, devEngines.runtime, [tools] of mise.toml (also .mise.toml, mise/config.toml, .mise/config.toml, .config/mise.toml, .config/mise/mise.toml and .config/mise/config.toml, the first file that sets a tool winning, as mise reads them; a list gives its first entry, a table its version), actions/setup-node in workflows (with node-version-file, in whose mise.toml it reads tools.node, and matrix values, also inside more text, ${{ matrix.node }}.x, or as a property of the matrix objects, ${{ matrix.cfg.node }}, with the values include adds and those exclude removes), jdx/mise-action (the Node.js of the mise configuration of its working directory or of its mise_toml input, unless install: false), and Dockerfiles (Dockerfile, *.Dockerfile, Dockerfile.<name> and Containerfile in the root, in direct subdirectories without a manifest of their own and in workspace packages, with FROM node:<tag> or FROM python:<tag>), and the image of a job container (container: node:20, read like a FROM). An unpinned npm is the one that comes with that Node.js: Node.js 22 comes with npm 10, which ignores the release-age setting without a warning. With installScripts.default: "deny", apply writes strict-allow-scripts=true when any of these places runs npm 11.16 to 11.x (the pinned npm, an npm a CI job or Dockerfile installs, or the npm of a declared Node.js), also when another place runs npm 12, and says what that means for npm 12; doctor offers the setting with "which depcurfew apply writes" only where apply does. manager-version-too-old reports such a runtime where its npm (or the pip of the official python image) lacks a setting in use, unless the CI job or the build stage installs another one first (npm install -g npm@<version>, pip install pip==<version>), which is then checked the same way. npm installs the newest release of a range that runs on the Node.js there, so a range (npm@11, npm@^11.10.0), npm@latest and a bare npm count as the newest such release depcurfew knows (a warning when it lacks a setting, since a newer release may be out), never as the lowest version of the range; npm@${NPM_VERSION} takes the value of NPM_VERSION in the env of the workflow, the job and the step, or in the ENV and ARG of the stage; a spec depcurfew still cannot read (npm@next, a variable without a value, a range with none of the releases it knows) counts as an npm that runs every dependency install script. apply names the places that run an npm without a setting it writes for the release-age delay or the dependency sources (version-in-use-too-old), from the same list as doctor: .nvmrc with 24 can come with npm 11.3.0, which has no release-age delay. A place whose installs are all npm ci needs no release-age setting, because npm ci installs exactly the versions of package-lock.json; one whose installs run with scripts off (--ignore-scripts, npm_config_ignore_scripts=true in env:, ENV or on the command) needs no script blocking and counts for no npm that runs scripts. When the npm a fix names does not run on the Node.js of the place (npm 11 needs Node.js ^20.17.0 || >=22.9.0), the fix says to move to such a Node.js first. A pip install in a CI job or in a stage of the python image needs PIP_UPLOADED_PRIOR_TO (in env:, ENV or on the command) or --uploaded-prior-to with at least the days of the policy, because pip reads no configuration from the project; a job or stage that sets PIP_CONFIG_FILE, runs pip config set or writes a pip.conf is not checked. A pip.conf of the repository that PIP_CONFIG_FILE names is read where it does, so python-config-ignored does not report it: in a CI job as its path from the workspace (pip.conf, ${{ github.workspace }}/pip.conf), in a Dockerfile stage as a path with its name in a stage that copies it (COPY pip.conf ./ with ENV PIP_CONFIG_FILE=/app/pip.conf). A stage whose image tag does not tell its Node.js or Python (a digest alone, an unknown tag) gives a note with a fix. runtime-end-of-life reports Node.js lines past their end of life. corepack-unavailable reports a packageManager or devEngines.packageManager pin of Yarn or npm, which only Corepack reads, where a declared Node.js is 25 or later: Node.js 25.0.0 stopped distributing Corepack, so the pin has no effect there until Corepack is installed (npm install -g corepack, then corepack enable, or corepack enable npm for npm). pnpm 10 and later switches to its pinned version itself and npm checks devEngines.packageManager itself, so those pins are not reported, and neither is a CI job or Dockerfile stage that installs or runs Corepack, or installs the pinned npm release itself (npm install -g npm@<version>), before it installs with the pinned tool; one that installs another npm release first gets the warning with that release named. depcurfew explain lists what it found.
Settings that CI and Dockerfiles turn off. Package managers take settings from the command line and from environment variables over their configuration files, so a workflow or a Dockerfile can undo what apply wrote for every install it runs. The rule ci-weakens-policy reads the variables of the env of a workflow, a job, its container and a step, export NAME=value and NAME=value before a command in a run step, NAME=value lines a step writes to $GITHUB_ENV with echo or printf (which the later steps of the job get, over the env of the workflow and the job and under that of a step), ENV and the defaults of ARG in a Dockerfile stage (an ARG before the first FROM only where a stage declares it again), and the options on the commands of run steps and RUN instructions, for npm, pnpm, Yarn, Bun, Deno, uv, Poetry and PDM: NPM_CONFIG_MIN_RELEASE_AGE=0 (in any case), npm_config_strict_allow_scripts=false where .npmrc sets it, npm_config_allow_git=all or root while the policy denies git sources, pnpm_config_minimum_release_age=0, YARN_ENABLE_SCRIPTS=true, UV_EXCLUDE_NEWER with a shorter delay, POETRY_SOLVER_MIN_RELEASE_AGE=0, and npm install --min-release-age=0 --allow-git=all --no-strict-allow-scripts, pnpm install --config.minimum-release-age=0 --config.dangerously-allow-all-builds=true, uv lock --exclude-newer "1 hour" or pdm lock --exclude-newer 1h. Each one is an error at its line, with a fix; a value with $ or ${{ }}, and a delay depcurfew cannot compare (a date), give a warning. The options and variables come from the knowledge base (those the install guard asks about, the option of each tool's delay, and for npm the settings of install scripts and sources it writes into .npmrc); npm_config_ignore_scripts=false and --ignore-scripts=false are not reported, since apply never writes ignore-scripts. pip has python-config-ignored, Composer composer-config-weakens and Bundler bundler-config-weakens, which read the same places. Not read: a run step in PowerShell ($env:NAME), { ...; } >> "$GITHUB_ENV" and multiline values (NAME<<EOF), npm config set, pnpm config set, yarn config set, poetry config and pdm config in CI, abbreviated options (--min-rel=0) and the commands of a script that sh -c runs.
Python projects
depcurfew configures uv, Poetry, PDM, Pipenv and pip from the same depcurfew.json, and the bots for Python packages: Dependabot pip and uv entries and Renovate.
- Detection. A Python tool counts as used when its lockfile is there, when
pyproject.tomlhas its table, or when its own configuration file or a CI setup step shows it;requirements.txtmeans pip.depcurfew explainlists what it found. - Package names. Python packages on the policy's package lists (the cooldown exceptions,
installScripts.allowandinstallScripts.deny) take the prefixpypi:, for example"pypi:docopt". Names without a prefix stay npm names, so an exception for an npm package never applies to a Python package of the same name. Ruby gems, JSR packages, Rust crates and NuGet packages have no names on these lists, since no setting depcurfew writes takes one: an entry such as"gem:rails"or"cargo:serde"is an error that says to remove it, and an entry with another prefix is an error that lists the prefixes. - Install scripts are builds. A wheel installs without a build step, so no build code runs; a source distribution, a git repository or a local directory is built, which runs its build code. Forbidding builds does not keep an installed package's own code from running later, whenever Python imports it.
installScripts.default: "deny"forbids building, and the packages ininstallScripts.alloware the exceptions. None of the three tools can only allow a build, so these packages are always built from source, even where a wheel exists;applysays so, anddoctorlists the packages in the lockfile that would be built. Packages ininstallScripts.denymust not be built:doctorreports one that a tool would still build (Poetry, PDM, pip and Pipenv build git, local and URL dependencies whatever their settings say), and one without wheels that cannot be installed while building is blocked. - pip reads no project configuration.
applywrites the build options intorequirements.txt. The release-age delay cannot live in the project, soapplyprints the environment variable to set where pip runs. pip reads the options of the files a requirements file includes with-rand-c, and the options after a requirement on its line, into the same install, so an option there that makes pip build packages from source again (a package name or:all:for building from source,--only-binary=:none:) undoes--only-binary=:all::applycomments such option lines out in the included files inside the repository (they come back when the policy stops forbidding builds), and warns with the file and line where it cannot (build-switch-undonefor an option after a requirement or next to other options,requirements-include-unreadfor a URL, a path outside the repository or a missing file).doctorreports them (config-drift, andunreadable-configfor an include it cannot read), and follows the options in pip's order when it decides whether building is forbidden. - Lock again after
apply.uv.lockrecords the release-age delay, and PDM checkspdm.lockagainst all of its resolution settings, the delay and the build ban included. Afterapplychanges them,uv sync --lockedanduv lock --checkfail until you runuv lock, andpdm lock --checkandpdm install --checkuntil you runpdm lock;applysays so (lockfile-out-of-date), and so doesdoctor(the rulelockfile-out-of-date) while the lockfile is not locked again: it compares whatuv.lockrecords with the settings as uv does, and the hash inpdm.lockwith the one PDM computes frompyproject.toml.poetry.lockandPipfile.lockdo not depend on the settingsapplywrites. - PDM and Pipenv have no setting that pins their own version. Where nothing pins it,
applyconfigures for the latest version depcurfew knows and names that version (version-not-pinned). What CI jobs and Dockerfiles install counts as a version, anddoctorchecks it (manager-version-too-old): theversioninput ofpdm-project/setup-pdm(headnames none);pdm==<version>,pipenv==<version>,uv==<version>andpoetry==<version>inpip install,pipx installoruv tool install, with a variable read from theenvof the job or theENVandARGof the stage (pip install "poetry==${POETRY_VERSION}"); and the uv of the official uv image a Dockerfile copies from or starts from (COPY --from=ghcr.io/astral-sh/uv:0.8.22 /uv /uvx /bin/,FROM ghcr.io/astral-sh/uv:0.8.22-python3.12-bookworm;latestand a base name alone name no version). Pipenv takes the delay in whole days only (rounded up), has no exceptions and no project setting that forbids building;applysays what to set where Pipenv runs instead.doctorreadspdm.lockandPipfile.lock, and reports settings that turn the delay or the build ban off without a word: Pipenv's pure-Python resolver and PDM's uv mode. - Hatch has no settings of its own. Its environments install with uv or pip, so a Hatch project is covered by the uv configuration (add
"uv"totargetswhen its environments use the uv installer) or by the environment variables of pip. - Running depcurfew. depcurfew is a Node.js program; a Python project without
package.jsonruns it as In a repository without package.json describes.
Update bots
apply gives Renovate and Dependabot the policy's delay, rounded up to their unit and at least as long as the delay of every package manager after rounding, so that they never propose a version the package managers would refuse. It edits only bot configuration files that exist and adds no entries to them.
- Dependabot gets
cooldownin eachupdatesentry of an ecosystem depcurfew configures (npm,bun,deno,pip,uv,composer,bundler), with the policy's exceptions for that registry underexclude(abundlerentry keeps its own, since the policy names no gems), and in the entries of the ecosystems updated by bots only (below). Anincludelist in such acooldownstays, but it limits the cooldown to the dependencies it names, soapplywarns (bot-rule-shortens-delay, with the entry and the line) unless it is"*"; so it does when an entry keeps its ownexclude(actions, gems, the ecosystems updated by bots only) and that list matches every dependency ("*"), since Dependabot then waits for none of them.doctorreports both as before (bot-cooldown-mismatch,broad-exclusion). - GitHub Actions. When the repository has workflows in
.github/workflowsandtargetsis"auto"or names the bot, the actions they run wait as long as the packages. Thegithub-actionsentries of Dependabot get the samedefault-days(GitHub Actions reads nosemver-*-days), and theirexcludestays as written, because the policy's exceptions name packages, not actions. The delay at the top level of the Renovate configuration covers itsgithub-actionsmanager too.doctor(bot-cooldown-mismatch) compares the bot's delay for the actions withcooldown.minimum. - Cargo, Go modules, Maven, Gradle and NuGet are updated by bots only. Their package managers have no release-age delay that depcurfew configures (the rows "bots only" in Supported tools say what each has), so depcurfew delays their new versions through the bots alone. When
depcurfew.jsonhas acooldownandtargetsis"auto"or names the bot, the Dependabot entriescargo,gomod,maven,gradleandnugetget the samedefault-daysas the packages, lowersemver-*-daysare raised to it, and theirexcludestays as written, because the policy's exceptions name npm, Python and Composer packages. The delay at the top level of the Renovate configuration covers itscargo,gomod,maven,gradleandnugetmanagers;packageRulesentries for them by manager, category (rust,golang,java,dotnet) or datasource (crate,go,maven,nuget) count as for the package managers, and the presetsecurity:minimumReleaseAgeCrategives Cargo dependencies 3 days.doctor(bot-cooldown-mismatch) compares the bot's delay for them withcooldown.minimum, and gives one note per ecosystem when the repository has their files but no update bot, since nothing delays their new versions then. depcurfew looks for these files (Cargo.toml,go.mod,pom.xml,settings.gradle, a.csproj…) in the root, in its direct subdirectories that are not hidden, in workspace packages and in the directories listed inprojects;depcurfew explainlists what it found under "Updated by bots only". Other dependencies that come in with the bot's update of a lockfile (Cargo.lock,go.sum…) are not held back, because the package manager has no delay of its own to apply to them. - What NuGet's
minPublishAgeHourscovers. From .NET SDK 11.0.100 (NuGet 8.0, Visual Studio 18.12) on, the attributeminPublishAgeHoursof a source innuget.configholds back new versions only wheredotnet package addanddotnet package updatechoose the version themselves; the 10.0 SDKs and earlier ignore it, and depcurfew does not write it. The NuGet documentation says that an explicit version bypasses it, that restore does not check it (for transitive dependencies too), and that with an age set, restore fails withNU1020for a floating version such as1.2.*. The Visual Studio Package Manager UI and Package Manager Console,dotnet package listanddotnet package searchare not documented as covered (their NuGet issues are open), nor aredotnet tool,dnxanddotnet new install.check, the Claude Code hook anddepcurfew mcpdo not look NuGet packages up, so depcurfew delays NuGet dependencies only through the bots. - Renovate gets the delay at the top level of its configuration and a
packageRulesentry for the exceptions of each registry, limited to its datasource:matchDatasources: ["npm"]for npm names,["pypi"]forpypi:names and["packagist"]forcomposer:names, so an exception never reaches a crate, a Go module, a gem or an action of the same name. A built-in preset ofextendsthat gives a datasource in use a shorter delay than the policy (config:best-practicesandsecurity:minimumReleaseAgeNpm3 days fornpm,security:minimumReleaseAgePypiforpypi,security:minimumReleaseAgeCrateforcrate) wins over the top-level value, since a matching rule wins over the top level;applythen writes a rule with that datasource,matchUpdateTypes: ["major", "minor", "patch"]and the policy's delay at the front ofpackageRules, which Renovate applies after the rules of the presets and before the repository's own rules and the exception rules, and removes it when the preset goes or no longer waits less. The deprecatedrenovatekey ofpackage.jsonand.renovatercget it in the snippet.doctorreports a rule of the repository that gives npm, Python or Composer packages of some names a shorter delay thancooldown.minimum, or none, while the policy's exceptions do not exempt them (broad-exclusion, at the line of the rule), and a rule an earlierapplywrote withoutmatchDatasources: ["npm"].applyanddoctorread the otherpackageRulesentries the way Renovate matches them, also bymatchManagersandmatchCategories:npmcovers the packages of npm, pnpm and Yarn,bunanddenothose of Bun and Deno,pep621those of uv and PDM,poetry,pip_requirementsandpipenvthose of Poetry, pip and Pipenv,composerthose of Composer,bundlerthose of Bundler, andgithub-actionsthe actions of the workflows; the categories arejs,python,php,rubyandci. Globs,/regex/,!and a single string instead of a list work as in Renovate. A rule that shortens the delay of a package manager in use (or of the actions) givesbot-rule-shortens-delayinapplyandbot-cooldown-mismatchindoctor, and one that turns it off givesbroad-exclusion. For the actions, a rule withmatchFileNamesthat matches every workflow file of the repository (read as Renovate reads it:*within one directory,**across them, any case, or/regex/) and a rule withmatchDepTypesthat matchesaction, the depType Renovate gives an action of a workflow step, count too; one that matches only some workflow files, or only reusable workflows (workflow), leaves the other actions their delay and does not count. A rule with a glob matcher depcurfew cannot read (braces, character classes,?, or extglobs such as@(a|b)and+(…)) does not count, anddoctorgives a note when it may set a shorter delay. depcurfew does not run a/regex/that a crafted file could make take minutes: one with a quantified group, a backreference, a lookaround, more than one*,+or{n,}, or more than 256 ways to match through?,{n,m}and|(16 next to a*,+or{n,}), nor the patterns of a file past a fixed amount of matching work. Such a pattern counts as matching where the rule shortens or turns off the delay, sobot-cooldown-mismatch,broad-exclusionandbot-rule-shortens-delayreport the rule and name the pattern; where the rule would make the delay longer, it does not count; so does a rule with a matcher depcurfew does not evaluate (matchBaseBranches,matchJsonata,matchRepositories,matchCurrentAge, andmatchFileNamesormatchDepTypesfor the packages of a package manager), unless a package matcher already limits it to some packages. When rules give the update types different delays,doctorreports each one that is shorter, not only the shortest. - Numbers in
dependabot.ymlare read as Dependabot's YAML library, Ruby's Psych, reads them, which follows YAML 1.1:default-days: 010is 8 days (octal),1_0and1,0are 10,1:30is 5400, and0o12is text.doctorcompares the number Dependabot reads with the policy and says how it read it ("default-days: 010 in .github/dependabot.yml, which Dependabot reads as 8"), andapplywrites the policy's number in place of one Dependabot reads otherwise. Words are read the same way:yes,no,onandoffin any case are booleans and~andnullin any case are null, soapplywrites an exception such asyes,off,1_0or2024-01-01in double quotes (- "yes"), which Dependabot reads as the name, and treats an unquoted- yesin the file as the boolean it is. - YAML aliases. Dependabot does not accept YAML aliases (
*name) in.github/dependabot.yml(dependabot-core loads the file without them), sodoctordoes not read a delay or an exception through one:bot-cooldown-mismatchgives a note at the first alias instead of a result,applywrites thecooldownof an entry out in place of an alias and warns that Dependabot reads nothing from the file (bot-config-unreadable), andinitnotes it, since it does not keep a longer delay from such a file incooldown.minimum. GitHub Actions workflows may use aliases:doctorand detection read theenvmaps, jobs and steps of a workflow through them, at most 100 aliases per file. - How old an action is comes from git. Renovate takes the commit date of a tag, the tagger date of an annotated tag, or the date of a GitHub release of that version when that is newer, and holds an update without a date; Dependabot takes the date of the GitHub release, then of the tag, then of the commit, and lets an update without a date through. Whoever makes a commit or a tag sets its git date, so the delay protects actions less than packages from a registry, which records when a version was published.
Composer projects
depcurfew configures Composer (PHP packages from Packagist) from the same depcurfew.json, and the bots for Composer packages: Dependabot composer entries and Renovate.
- Detection.
composer.jsonorcomposer.lockin the directory means Composer. Only the rootcomposer.jsoncounts: Composer readsconfigandrepositoriesfrom it alone. Composer readscomposer.jsonas strict JSON, soapplydoes not edit one with a comment or a trailing comma (cannot-edit, with the line). - Package names. Composer packages on the policy's package lists take the prefix
composer:, for example"composer:monolog/monolog"or"composer:acme/*". Composer names are lowercasevendor/name; a name without a prefix stays an npm name. Composer reads the keys ofallow-pluginsandignorewithout regard to case, and its*there matches/too, so depcurfew compares them the same way:"Symfony/Flex": trueis the plugincomposer:symfony/flex. - The release-age delay needs Composer 2.11.0.
applywrites the delay of the cooldown policy in whole days, rounded up ("36h"becomes"2 days"); a period that is already as long in another form (259200seconds,"72 hours") stays as it is. Composer reads"policy": trueand"cooldown": trueas{}, soapplywrites its object there; below"policy": false,"cooldown": falseor another value that is no object it writes nothing (cooldown-not-written), andexplainshows that Composer cannot apply the delay. Composer counts the delay frompublished-time, the moment Packagist received the version, which the package author cannot set. The delay applies whencomposer updateorcomposer requirepicks versions;composer installinstalls whatcomposer.locklocks, so review lockfile changes as before. Older Composer ignores the setting without a word, and Composer has no setting that pins its own version:doctorreports acomposer.lockwritten by a Composer older than 2.11 (itsplugin-api-version) and otherwise gives a note (manager-version-too-old). - Exceptions.
applywrites thecomposer:exceptions of the policy as theignorelist, adds them to a list that is there, or adds them to anignoreobject with a reason that namesdepcurfew.json. That reason marks an entry asapply's: once the policy drops the name from its exceptions,applyremoves it. Any other entry that exempts a package the policy does not stays, also a name of a list an earlierapplywrote, since a list has no room for a mark:applywarns (exclude-entry-outside-policy) anddoctorreports it (config-drift, a warning) with the line, until you remove it and runcomposer update --lock. - Lock again after
apply. Composer 2.11 hashesconfig.policyintocomposer.lock, so afterapplychanges it,composer installwarns that the lock file is out of date andcomposer validatefails;applysays so (lockfile-out-of-date), only in the run that changes the setting: a laterapplyanddoctordo not compute the hash ofcomposer.lockand do not repeat it. Runcomposer update --lock, which writes the new hash without updating packages, and commitcomposer.lock. Dependabot writescomposer.lockwith Composer 2.10.3, whose hash leavesconfig.policyout, so the same happens on each of its composer pull requests onceconfig.policyis there: runcomposer update --lockon the pull request's branch before merging (doctorsays so in its note on each Dependabotcomposerentry). - Install scripts are plugins. Composer runs the scripts of the root
composer.jsononly; the code of dependencies that runs during Composer commands is plugins (typecomposer-plugin, and the legacycomposer-installer, in any case). WithinstallScripts.default: "deny",applywrites the plugins ofinstallScripts.allowintoallow-pluginsastrueand those ofinstallScripts.denyasfalse(patterns as they are, since Composer takes them). An unlisted plugin makes Composer ask, or fail without a terminal (one that declaresextra.plugin-optionalis skipped there).applyreplaces"allow-plugins": truewith the list,{}when the policy lists no plugins (allow-all-replaced), and never changes an entry it did not write: an earlier key that blocks a plugin the policy allows, such as a final"*": false, givesdeny-conflicts-with-filewith the fix to put the plugin before it.doctorlists the plugins incomposer.lockthatallow-pluginslets run but the policy does not allow, and those it does not list. Acomposer.lockwithoutplugin-api-version(or with one below 2.2.0) next to an empty or missingallow-pluginsmakes Composer 2.2 to 2.9 run every plugin without asking, sodoctorreports each plugin the policy does not allow as an error;composer update --lockrecordsplugin-api-version.applydoes not add a catch-all"*": falsefor this: Composer takes the first key that matches, and the pluginsapplyadds go at the end, where such a key would hide them. - No switch for dependency sources. Composer has no setting that forbids VCS, path, artifact or package repositories (
source-not-enforceable), but only the rootcomposer.jsondeclares them.doctorreports the packages ofcomposer.lockthat come from such a repository, and a repository of a denied kind whose packages it cannot find in the lockfile. A package ofcomposer.lockwithout adist, or with adistwithout a type, is installed from itssource, a VCS checkout, whatevercomposer.jsondeclares, so it counts as a VCS package, and so does one thatconfig.preferred-installofcomposer.jsonmakes Composer check out:"source", a pattern object whose first matching key gives anything but"dist"or"auto", and"auto"for a dev version (dev-main,1.x-dev). Composer's default"dist"takes the archive, also of a dev version. Such packages are one error per entry ofconfig.preferred-install, at its line incomposer.json, naming the packages with their lines incomposer.lock, and the fix is incomposer.json(remove the entry or set it to"dist"), orsources.git: "allow";initnotes them (composer-source-checkouts). An archive fromapi.github.com,gitlab.com,bitbucket.orgorcodeberg.orgnext to a source on another host than its own (github.comforapi.github.com; hosts are compared as URLs compare them, sogithub.com:443isgithub.com, andwww.github.comisgithub.com, as Composer's GitHub driver reads it) gives a warning while the policy denies VCS sources: Packagist.org never writes such an entry, and Composer installs from that source with--prefer-source, withpreferred-installset inCOMPOSER_HOME/config.json, and before 2.10.0 when the archive cannot be downloaded (2.10 does that only withsource-fallback, which 2.11 removed). An archive from a host that neither the archives of Packagist.org (api.github.com,gitlab.com,bitbucket.org,codeberg.org) nor a repository ofcomposer.jsonuse is a source depcurfew does not recognize: one warning per host while the policy denies a source, naming its packages with their lines. A Composer repository ofcomposer.jsonmay keep its archives on another host than its own; depcurfew knows that of WPackagist (https://wpackagist.orgservesdownloads.wordpress.org), and for another one, such aspackages.drupal.orgwith its archives onftp.drupal.org, the warning names the declared repositories and says how to check that one of them serves those archives, sincecomposer.lockdoes not say which repository a package comes from.doctor --osvlooks up every release ofcomposer.lock, also one from such a host and one Composer checks out of its source, but not a package of a VCS repository ofcomposer.json, whose code is not the release on Packagist. - Edited lockfile entries. The rule
lockfile-tamperedreports a package ofcomposer.lock(packagesandpackages-dev) whosetransport-optionshasprevent_url_access_callableorprevent_ip_access_callable, and a Composer or VCS repository ofcomposer.jsonthat has one in itsoptions, or a package a package repository defines that has one in itstransport-options(a path or artifact repository passes no such option to its downloads). Composer 2.11.1 and earlier versions that know these options call them as PHP functions while downloading, when they name one, so an editedcomposer.lockcan run a command duringcomposer install(GHSA-jhfp-4ppm-crmm); Composer 2.11.2 no longer takes them fromcomposer.lock. It is an error with or withoutdepcurfew.json, whatever the Composer version, which depcurfew cannot tell: use Composer 2.11.2 or later before anycomposer installorupdateon such a lockfile, then remove the option or restore the lockfile, and find out who changed it. Withoutdepcurfew.json, acomposer.lockthat does not parse is a note against the default policy, as another lockfile is.vendor/composer/installed.json, which Composer 2.11.1 reads the same way, is not checked. - Settings that turn protections off. The rule
composer-config-weakensreports"policy": false, acooldownorblockturned off, malware blocking turned off or limited to updates, a repository with"require-published-time": falseor"filter": false,"allow-plugins": true(or a key that matches every plugin,"*"or"*/*") while the policy denies plugins,"secure-http": false,"disable-tls": true, andCOMPOSER_POLICY=0,COMPOSER_NO_BLOCKINGorCOMPOSER_NO_SECURITY_BLOCKINGset to1,true,on,falseoroff(any other value makes Composer stop with an error), a shorterCOMPOSER_POLICY_COOLDOWN_PERIOD,COMPOSER_POLICY_COOLDOWN_BLOCK=0orCOMPOSER_POLICY_MALWARE_BLOCK=0in theenvof a CI workflow (of the workflow, a job, its container or a step), exported or set before a command in arunstep, written to$GITHUB_ENV(which the later steps of the job get), or set withENVor as the default of anARGin a Dockerfile stage (Docker givesRUNthe build arguments as variables, underENV). It also reportscomposer config policy.cooldown falseand the other settings above written bycomposer configin arunstep or aRUNinstruction (Composer takestrue,false,1and0there and writes the setting intocomposer.jsonfor the commands after it), and--no-cooldown,--no-blocking,--no-security-blockingor--no-secure-httpon acomposercommand there. - Bots.
applygives Dependabotcomposerentries the cooldown and thecomposer:exceptions, and Renovate a rule for them limited to thepackagistdatasource. Dependabot counts its composer cooldown from the release time the package author writes and updatescomposer.lockwith a Composer without the cooldown, sodoctorgives each such entry a note; Renovate counts frompublished-time. - Checking before an install.
depcurfew check composer:vendor/name(alsocomposer:vendor/name:^3.9, ascomposer requiretakes it), the Claude Code hook (composer require,composer updateof named packages,composer global require,composer create-project) andcheck_packagesofdepcurfew mcplook Composer packages up on Packagist: see Guarding installs. - Not covered.
composer config --global(or--file) in CI writesCOMPOSER_HOME/config.jsonor another file, whichdoctordoes not follow.COMPOSER_HOME/config.jsonandCOMPOSER_HOME/composer.json(Composer's configuration for the whole machine and its global project) are not read or written, so a repository, aminimum-stabilityor apreferred-installthey set is not seen bydoctor,checkor the hook; the hook asks about a command that setsCOMPOSER_HOMEitself, and aboutcomposer global updateof a package named without a constraint, whose constraint is inCOMPOSER_HOME/composer.json. Command-line options in CI steps, such as--prefer-source, are not read either.
Ruby projects
depcurfew configures Bundler (Ruby gems from rubygems.org) from the same depcurfew.json, and the bots for gems: Dependabot bundler entries and Renovate.
- Detection. A
Gemfile,gems.rb,Gemfile.lockorgems.lockedin the directory means Bundler; with bothgems.rbandGemfile, Bundler readsgems.rb, and so does depcurfew (only a regular file counts, as for Bundler: a directory namedgems.rbdoes not). The version is the one underBUNDLED WITHat the end of the lockfile, which Bundler installs and switches to. Thebundlerinput ofruby/setup-ruby(a version;Gemfile.lock,default,latestandnonename none),gem install bundler -v <version>(also-v<version>,--version=<version>orbundler:<version>) andbundle _<version>_ installin a CI job or a Dockerfile count as versions too.depcurfew explainlists what it found. - The release-age delay is the
cooldown:of the source. Bundler 4.0.13 and later hold back gem versions younger than thecooldown:days of thesourcecall that serves them.applywrites the policy's delay in whole days, rounded up ("36h"becomescooldown: 2), intosource "https://rubygems.org"of the Gemfile, which is committed, so every developer and CI run get it. It edits only a plain call with a literal URL on a line of its own that starts a statement at the top level of the Gemfile, outside any block, method, class, condition, loop, lambda orbegin(with or without parentheses, with one existingcooldown:or:cooldown =>, and with a comment after it), and keeps the rest of the line and of the file as they are. Thecooldown:is read as Ruby reads an integer (010is 8 days,0x1016). - The Gemfile is read as Ruby reads it. Bundler runs the Gemfile as Ruby code, so depcurfew reads it as Ruby's lexer does: comments, lines between
=beginand=end, everything after__END__(or after a NUL,^Dor^Zoutside strings and comments), strings and%q,%w,%(...)and other literals over several lines with any delimiter, and the bodies of heredocs (also<<~"END-X", which ends only at a line that is exactlyEND-X) are no calls.\r\nis one newline, forapplyanddoctoralike, as for Ruby. Where Ruby's reading depends on something only Ruby knows, depcurfew stops: after a name that may be a local variable or a method,platforms %w[ruby]is a%wliteral or a modulo, and so with/,<<,?and:after such a name and a space; alsoalias,undefand syntax errors (anendor)without its opening, adoorifwithout itsend). It stops as well where Ruby 3.4 and newer (Prism, the default parser since 3.4) and Ruby 3.3 and older (parse.y) read the same text differently:?before a character beyond ASCII (?€oris a character literal for one and a ternary for the other),=beginfollowed by^Dor^Z, and$0followed by a name. Such a Gemfile is not edited at all (cooldown-not-writtenwith the line and what to change there, such asplatforms(%w[ruby])), anddoctordoes not count a cooldown in it. - What apply does not edit is reported. A source with a block, a modifier (
if,unless), other options, a URL depcurfew cannot read or a call over several lines givescooldown-not-writtenwith its line, and so does a Gemfile withoutsource "https://rubygems.org". So do a source inside a block, method, class, condition, loop, lambda orbegin, which Ruby runs only when that code runs, acooldown:given twice (Ruby keeps the last of two equal keys, and Bundler takes a symbol key over a string key), acooldown:whose value goes on (cooldown: 7 - 7, or a next line that starts with&&,||,andoror, which Ruby 4.0 joins to the call as it joins.method), and a source whose URL depcurfew cannot read also next to a plain call;doctorreports the same place as an error ofconfig-drift, and as a warning when the call already has acooldown:at least as long as the policy, since depcurfew cannot tell whether Ruby runs it. The fix never asks for a shortercooldown:. The Gemfile has no markers, so a longercooldown:of a plain call written by hand is replaced (cooldown-shortened). Other gem servers keep their owncooldown:;cooldown: 0exempts a private one, as Bundler documents. - Bundler 4.0.14 for projects with a lockfile. Bundler 4.0.13 loses the
cooldown:of a Gemfile source onceGemfile.lockexists, which 4.0.14 fixed; older versions accept the keyword and ignore it without a word.applywrites nothing for an older pinned version (version-too-old, which namesbundle update --bundler), anddoctorreportsBUNDLED WITHand the CI versions below what the project needs (manager-version-too-old). - What the delay covers. Bundler applies it when it picks versions:
bundle installwithout a lockfile or for gems added to the Gemfile,bundle update,bundle lock --updateandbundle add. Versions already inGemfile.lockinstall as they are, also younger ones, so review lockfile changes as before. A version without a publication date (a gem server without the v2 compact index) is not held back. When every version that fits is too new, Bundler stops and names--cooldown 0. .bundle/configis not written. Bundler'scooldownsetting (bundle config set cooldown N,BUNDLE_COOLDOWN) applies to every source and takes the place of the Gemfile's, so it would also end the exemption of a private server, and.bundle/configis usually local and git-ignored (bundle gemignores/.bundle/).applyleaves it alone. The rulebundler-config-weakensreportsBUNDLE_COOLDOWNwith fewer days than the policy, or0, in.bundle/config(which Bundler reads before the environment), in theenvof a CI workflow, written to$GITHUB_ENVby an earlier step of the job, before abundlecommand, in a DockerfileENVor as the default of a DockerfileARG(whichRUNgets as a variable, underENV),bundle config set cooldown N(with--localor--global, and the olderbundle config cooldown N) in arunstep or aRUNinstruction, which writes the setting for thebundlecommands after it, and a shorter--cooldownonbundle install,update,lock,addorcachein a CI job or a Dockerfile, also as Bundler's CLI reads them:bundle i,bundle packageandbundle pack, an unambiguous prefix such asbundle inst, a barebundle, which installs, and the hiddenbundle install_or_cli_help(alsoinstall-or-cli-help), which runs install..bundle/configis read as Bundler reads it, line by line and not as YAML: quotes stay unless they enclose the whole value (BUNDLE_COOLDOWN: "14" # two weeksis the text"14", so 0),#starts a comment, the last line of a key counts, and only a key spelled exactlyBUNDLE_COOLDOWNat the top level is the setting. Bundler reads the setting withto_i, so a value that does not start with a digit (an empty one,~,true,0x10) turns the delay off and1e3is 1 day; a number in a workflowenvis read as written.- No exceptions for gems. Bundler has no per-gem exception, and the policy's package lists have no prefix for Ruby gems: the cooldown exceptions of the policy stay npm, Python and Composer names. When the policy has exceptions,
applysays that they do not apply to Bundler (exclude-unsupported). To take a newer version of one gem sooner, review it and runbundle update <gem> --cooldown 0 --conservative. - Install scripts are native extensions. Bundler builds the native extensions of a gem (its
extconf.rb,Rakefile,CMakeLists.txtorCargo.toml) when it installs it, which runs code of the gem, and has no setting that stops this or allows it for some gems only. WithinstallScripts.default: "deny",applyandexplainsay that it cannot be enforced for Bundler (scripts-not-enforceable). - No switch for dependency sources. A Gemfile can name git sources (
git:,github:) and paths (path:), and Bundler has no setting that forbids them (source-not-enforceable);doctorreads only the version ofGemfile.lock, not its gems and sources, so review such sources by hand. - Bots.
applygives Dependabotbundlerentries the cooldown (their ownexcludestays) and Renovate the delay at the top level, which covers itsbundlermanager. The Renovate rule for the policy's npm exceptions is limited to thenpmdatasource, so an npm name does not exempt a gem of the same name.bot-cooldown-mismatchcompares the bots' delay for gems with thecooldown:of the Gemfile, as for the other package managers; Renovate rules by managerbundler, categoryrubyor datasourcerubygemscount. Renovate updatesGemfile, notgems.rb. - Not covered.
check, the Claude Code hook anddepcurfew mcpdo not look gems up on rubygems.org (gem install,bundle add).doctordoes not check the gems ofGemfile.lock(lockfile-forbidden-source,lockfile-install-scriptsandknown-maliciousdo not apply to it) and does not read~/.bundle/config,BUNDLE_APP_CONFIGorBUNDLE_GEMFILE. The Bundler arubyDocker image comes with is not read: Bundler switches to the version ofBUNDLED WITH.