chore(deps): take Dependabot to zero — every alert resolved, none introduced - #60
Conversation
Clears every open critical/high advisory on the repo (1 critical, 20 high)
plus the mediums and lows that ride along on the same package bumps —
composer audit and yarn npm audit are both clean afterwards.
composer.lock (root)
guzzlehttp/guzzle 7.10.0 -> 7.15.3, guzzlehttp/psr7 2.9.0 -> 2.13.0,
symfony/http-kernel 8.0.8 -> 8.0.15, symfony/http-foundation 8.0.8 -> 8.1.4
and their transitive deps. No composer.json constraint changed, so nothing
changes for Packagist consumers — they resolve against their own tree.
demo/laravel/composer.lock
laravel/framework 13.1.1 -> 13.24.0, league/commonmark 2.8.2 -> 2.9.0,
guzzle/psr7 as above, symfony/{http-kernel,mime,routing,http-foundation,
cache,polyfill-intl-idn} to their patched releases.
packages/Utils/composer.lock — untracked
.gitignore already lists /packages/*/composer.lock; this one predates the
rule and stayed tracked, so it kept reporting a dependency set the package
no longer has. It was stale enough that `composer validate` failed on it:
phpunit locked at 10.5.45 against a "^11.0" constraint, and
convertcom/php-sdk-types missing from the lock entirely. Eleven sibling
packages carry no lockfile; libraries should not.
yarn.lock
handlebars 4.7.8 -> 4.7.9 (the one critical), lodash-es -> 4.18.1,
js-yaml -> 4.3.1, ip-address -> 10.4.0, tar -> 7.5.22,
picomatch -> 2.3.2/4.0.5, brace-expansion -> 5.0.9.
sigstore needed 4.1.1 but sat behind a "^3.0.0" range, so no in-range bump
could reach it. It arrives via semantic-release -> @semantic-release/npm ->
npm -> libnpmpublish, so semantic-release moves 24 -> 25 (and its peer
@semantic-release/github 11 -> 12), which pulls libnpmpublish 11 and
sigstore ^4.0.0. Verified: the custom rollover-version plugin loads and all
six of its bump cases behave identically under 25, and `semantic-release
--dry-run` loads every configured plugin.
CI was not installing what yarn.lock says
The release job ran `yarn install --immutable` against the runner's
preinstalled Yarn 1.22.22. Classic Yarn cannot read a Yarn 4 lockfile and
does not recognise `--immutable` (it spells it `--frozen-lockfile`), so it
re-resolved every dependency from the package.json ranges and rewrote the
lockfile on each run — "success Saved lockfile" in the job log. The lockfile
was decorative. Pinning packageManager to yarn@4.18.0 and enabling Corepack
makes the install reproducible and makes lockfile drift fail the job.
Supply-chain minimum release age
Adds npmMinimalAgeGate: 4320 (3 days), matching convertcom/javascript-sdk.
php-sdk never got that hardening. No npmPreapprovedPackages entry is needed
here — this project has no internal npm dependencies, so nothing in our own
release chain can stall on a freshly-published package. Every version in the
lockfile is at least 3.5 days old, and re-resolving under the gate shifts
nothing.
Supersedes the open Dependabot PRs #40 (handlebars, picomatch) and #36
(phpunit in packages/Utils).
Verified: composer validate, monorepo-builder validate, php-cs-fixer,
phpstan, 648 unit tests, 474 cross-SDK parity tests, composer audit on both
locks, yarn npm audit, and a clean `yarn install --immutable` under Yarn 4.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same fix as convertcom/php-sdk#60, applied to this repo. CI has never run Yarn 4. `release.yml` already ran `corepack enable` — which is not enough on its own. With no `packageManager` field in `package.json`, corepack has nothing to resolve and falls through to its own default, Yarn 1.22.22, which it then downloads. From the last real release run (30637880488, 2026-07-31): ! Corepack is about to download .../yarn-1.22.22.tgz yarn install v1.22.22 [1/4] Resolving packages... warning Workspaces can only be enabled in private projects. success Saved lockfile. Three consequences, all live until now: 1. The committed `yarn.lock` was never used. Classic Yarn cannot read a `__metadata: version: 10` lockfile, so it re-resolved every release from the `package.json` semver ranges against whatever the registry served at that moment. "success Saved lockfile." is Yarn 1 writing its own. 2. `--immutable` was a silent no-op — classic spells it `--frozen-lockfile`, does not error on the unknown flag, and saved a lockfile anyway. 3. `.yarnrc.yml` was never read. `nodeLinker: node-modules` did nothing, and an age gate added there would have done nothing either. So the `packageManager` pin is what makes the gate exist, not a nicety alongside it. Written with `corepack use yarn@4.18.0`, never by hand; 4.18.0 is the version that wrote the lockfile in the previous commit, and Yarn 4.10.3 rejects that lockfile as needing modification — the pin has to match. `npmMinimalAgeGate: 4320` (3 days) matches javascript-sdk and php-sdk. No `npmPreapprovedPackages`: unlike javascript-sdk, this project has zero `@convertcom/*` npm packages in `package.json` or `yarn.lock` (the SDK ships via PyPI; the only Node packages here are release tooling), so an exemption list would be dead config. Yarn 4.18 already defaults the gate to 1440, so this raises it to the house value rather than introducing it. Verified after the change, per the known migration trap: `yarn config --json` shows `npmMinimalAgeGate` = 4320 sourced from `.yarnrc.yml`, and `enableScripts` / `approvedGitRepositories` still at their secure defaults — no `YN0087` migration fired and no hardening opt-out was written. Cold-cache `yarn install --immutable` (no node_modules, empty YARN_GLOBAL_FOLDER, all 418 packages fetched fresh with the gate active) passes, and `yarn release:dry-run` loads every plugin and correctly declines to publish off main. All 122 resolutions the previous commit adds were separately checked against the 3-day floor; the youngest, semantic-release@25.0.9, is 3.5 days old. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Checked this out into a clean worktree and re-ran every claim in the description — all of it reproduces: Two things worth adding to the record: Coverage is better than the description claims. I pulled all 79 open alerts and compared each Provenance checks out. Since this is 95% lockfile and checksums only verify against the lock itself, I looked Also confirmed the #36 reasoning rather than taking it on faith — applying that exact One change I'd want before merge
Node stopped distributing Corepack in 25.0.0 — nodejs/node#57617, It's the same shape as the bug this PR is fixing — the release job inheriting whatever toolchain the runner happens - name: Enable Corepack
run: |
npm install -g corepack@latest
corepack enablePinning Two non-blocking notes1. The
So it covers 2. FYI, not asks
|
Addresses all three findings from @DmytroConvert's review on #60. 1. The Corepack cliff (blocking). Confirmed and the date is exact: Node stopped distributing Corepack as of 25.0.0 — nodejs/node doc/contributing/distribution.md, "History": "corepack ... is no longer distributed as of Node.js 25.0.0" — and nodejs/Release schedule.json puts v26 at lts: 2026-10-28. So `node-version: 'lts/*'` plus `corepack enable` was a release job with ~2.5 months of life left. (One correction to the review: nodejs/node#57617 is closed, not merged; the doc is the citation that holds.) Fixed by removing the Corepack dependency rather than reinstalling it. `yarn set version 4.18.0 --yarn-path` commits the 4.18.0 binary under .yarn/releases and points yarnPath at it in .yarnrc.yml. Every yarn honours yarnPath, including the Yarn 1.22.x GitHub runners preinstall, so `yarn` resolves to 4.18.0 with no reference to Corepack and no coupling to which Node `lts/*` picks. The `corepack enable` step is deleted. Measured against the runner's actual classic Yarn 1.22.22, in this repo: yarn --version -> 4.18.0 (1.22.22 without yarnPath) install --immutable, cold -> exit 0 install --immutable, drifted -> exit 1, YN0028 lockfile would have been modified So --immutable is now genuinely enforced through the same entry point that used to silently rewrite the lockfile. Preferred over `npm install -g corepack@latest` because that fetches an unpinned global on every release run — new floating surface in a PR whose point is removing exactly that. Preferred over pinning node-version: '24' because that reschedules the failure to 2028-04-30 rather than removing it. The vendored binary is byte-identical to upstream 4.18.0 from three independent sources — this file, Corepack's npm-sourced cache, and a fresh fetch of repo.yarnpkg.com/4.18.0/packages/yarnpkg-cli/bin/yarn.js: sha256 fb8b1d20be72a0b544a35bcec4c7ed0ff55a9b173c01f191b02ba164b2051db5 .gitignore needed `.yarn/*` rather than `.yarn/` — git cannot re-include a path whose parent directory is excluded, so `!.yarn/releases` was inert under the old pattern and the binary would have been silently left out of the commit. .yarn/cache and friends stay ignored. A new .gitattributes marks `.yarn/ export-ignore` so the 3.6 MB does not reach consumers through Composer's dist archive; it is scoped to .yarn/ only, since excluding anything else would change what consumers receive. 2. The .yarnrc.yml comment overstated the gate. Correct — npmMinimalAgeGate is enforced at resolution, not install, which the review verified in both directions. Reworded to say "refuse to RESOLVE", and to spell out what it does not cover: a frozen `yarn install --immutable` never consults it, and a Dependabot PR arrives with the lockfile already resolved. It guards the act of choosing a version, not CI. 3. @semantic-release/release-notes-generator was locked twice. Correct — package.json asked for ^14.0.0 (14.1.0) while semantic-release 25 wants ^14.1.0 (14.1.1). Bumped the devDependency to ^14.1.0; the lock now carries one copy at 14.1.1. Also restores two things `yarn set version` silently changed: it rewrote packageManager without the +sha512 integrity hash, and stripped the comment from .yarnrc.yml. Both are back. Still zero vulnerabilities: all 88 alerts (79 open + 9 auto-dismissed) re-checked against the new lockfile, none remain. Verified: composer validate, monorepo-builder validate, php-cs-fixer, phpstan, 648 unit tests, 474 cross-SDK parity tests, composer audit on both locks, yarn npm audit, semantic-release --dry-run loading every plugin, and `install --immutable` driven by classic Yarn as CI will drive it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks @DmytroConvert — all three reproduced, all three fixed in The Corepack cliff is real and your date is exact. Confirmed independently: I went with a third option rather than either you named, because both leave the release job depending on something it shouldn't. Measured in the repo, driving the runner's actual classic Yarn 1.22.22: Which also means Why not your two: Since it's a 3.6 MB binary going into the repo, provenance: byte-identical to upstream 4.18.0 from three independent sources — the vendored file, Corepack's npm-sourced cache, and a fresh fetch of One trap worth recording: Both cosmetic ones fixed as described. The gate comment now says "refuse to resolve" and spells out that a frozen install never consults it and Dependabot arrives with the lockfile already resolved. Also worth flagging: Two things from your review I want to pick up separately rather than here:
Still zero: all 88 alerts (79 open + 9 auto-dismissed) re-checked against the post-review lockfile, dependency-review still reports 0 added dependencies carrying an advisory, and all 11 checks are green. Back to you. |
Takes https://github.com/convertcom/php-sdk/security/dependabot to zero. Every alert on the repo is resolved — not just the critical and high ones — and nothing this PR adds carries an advisory. Two commits, all checks green.
Zero, measured three ways
1. Every alert clears. 88 alerts exist on
main: 79 open and 9 auto-dismissed. Each was evaluated against this branch by matching the installed version against the advisory's ownvulnerable_version_range(not just "is it newer than the fix", which gets the two separate picomatch lines wrong).The auto-dismissed nine are easy to miss: GitHub had filtered them off the Security tab, but the vulnerable versions were still in the tree. One is a critical —
tar≤ 7.5.18, GHSA-23hp-3jrh-7fpw — which never appeared as an open alert at all. So this clears two criticals: that one and handlebars GHSA-2w6w-674q-4c4q.2. Nothing new is introduced. The branch adds 189 dependencies and removes 261, so "did we trade one set of advisories for another" is a fair question. GitHub's dependency-review API answers it against the same database Dependabot uses:
3. Both native auditors agree.
composer auditon each lock andyarn npm audit --recursive --allreport no advisories.What changed, per manifest
composer.lock— guzzle7.10.0 → 7.15.3, psr72.9.0 → 2.13.0, symfony/http-kernel8.0.8 → 8.0.15, symfony/http-foundation8.0.8 → 8.1.4, plus transitives. Nocomposer.jsonconstraint moved, so nothing changes for Packagist consumers — they resolve against their own tree.demo/laravel/composer.lock— laravel/framework13.1.1 → 13.24.0, league/commonmark2.8.2 → 2.9.0, guzzle/psr7 as above, and symfony{http-kernel, mime, routing, http-foundation, cache, polyfill-intl-idn}to their patched releases.yarn.lock— handlebars→ 4.7.9, lodash-es→ 4.18.1, js-yaml→ 4.3.1, ip-address→ 10.4.0, tar→ 7.5.22, picomatch→ 2.3.2and4.0.5, brace-expansion→ 5.0.9, sigstore→ 4.1.1.packages/Utils/composer.lock— removed from version control.Three things that need real eyes
Removing
packages/Utils/composer.lockfixes rather than hides. Deleting a manifest makes its alerts disappear, so this deserves proof rather than assertion..gitignorealready lists/packages/*/composer.lock; this file predates the rule and stayed tracked, so it kept reporting a dependency set the package no longer has — stale enough thatcomposer validatefails on it (phpunit locked at10.5.45against a^11.0constraint, andconvertcom/php-sdk-typesmissing from the lock entirely). Resolving the package fresh from itscomposer.json, which is what a clean CI checkout does:No vulnerable version exists in the real tree — the lockfile was the only thing claiming otherwise. The other eleven packages carry no lockfile, and libraries should not.
semantic-release 24 → 25, and
@semantic-release/github11 → 12 with it. This is the release path for the SDK, so it is the riskiest line in the diff. It was not optional:sigstoreneeds4.1.1but sits behind a^3.0.0range, so no in-range bump reaches it. It arrives viasemantic-release → @semantic-release/npm → npm → libnpmpublish, and only the major bump pullslibnpmpublish11 andsigstore ^4. The customscripts/rollover-version-plugin.mjswas exercised directly across all six of its bump cases — patch below 9, patch rollover to minor,2.9.9rollover to major, refactor, breaking change, chore-only — with identical results to 24, andsemantic-release --dry-runloads every configured plugin.CI was never installing what
yarn.locksays. The release job runsyarn install --immutable, but nothing pinned the package manager, so the runner's preinstalled Yarn 1.22.22 serviced it. Classic Yarn cannot read a Yarn 4 lockfile and does not recognise--immutable(it spells it--frozen-lockfile), so it re-resolved every dependency from the semver ranges and rewrote the lockfile on every run. The release log says it outright:The lockfile was decorative. Fixed by committing the Yarn 4.18.0 binary under
.yarn/releasesand pointingyarnPathat it — see the review round below for why this rather than Corepack. Without this, theyarn.lockhalf of this PR would not have taken effect in CI.Review round — what changed after @DmytroConvert's review
All three findings were reproduced and all three are fixed in
00e00ee.1. The Corepack cliff — confirmed, and the date is exact. Node stopped distributing Corepack as of 25.0.0 (nodejs/node
doc/contributing/distribution.md, History: "corepack … is no longer distributed as of Node.js 25.0.0"), andnodejs/Releaseschedule.jsonputs v26 atlts: 2026-10-28. Sonode-version: 'lts/*'pluscorepack enablewas a release job with about ten weeks of life left. (One correction to the review: nodejs/node#57617 is closed, not merged — the doc is the citation that holds.)Fixed by removing the Corepack dependency rather than reinstalling it.
yarn set version 4.18.0 --yarn-pathcommits the binary under.yarn/releasesand setsyarnPathin.yarnrc.yml. Every yarn honoursyarnPath, including the Yarn 1.22.x runners preinstall, soyarnresolves to 4.18.0 with no reference to Corepack and no coupling to which Nodelts/*picks. Thecorepack enablestep is deleted.Measured in this repo, driving the runner's actual classic Yarn 1.22.22:
So
--immutableis now genuinely enforced through the same entry point that used to silently rewrite the lockfile.Chosen over the two alternatives in the review:
npm install -g corepack@latestfetches an unpinned global on every release run, which is new floating surface in a PR whose point is removing exactly that; and pinningnode-version: '24'reschedules the failure to 2028-04-30 rather than removing it.The vendored binary is byte-identical to upstream 4.18.0 from three independent sources — this file, Corepack's npm-sourced cache, and a fresh fetch of
repo.yarnpkg.com/4.18.0/packages/yarnpkg-cli/bin/yarn.js:Two details worth flagging.
.gitignoreneeded.yarn/*rather than.yarn/— git cannot re-include a path whose parent directory is excluded, so!.yarn/releaseswas inert under the old pattern and the binary would have been silently left out of the commit. And a new.gitattributesmarks.yarn/ export-ignoreso the 3.6 MB never reaches consumers through Composer's dist archive; it is scoped to.yarn/only, since excluding anything else would change what consumers receive.2. The
.yarnrc.ymlcomment overstated the gate — correct.npmMinimalAgeGateis enforced at resolution, not install. Reworded to "refuse to resolve", and to spell out what it does not cover: a frozenyarn install --immutablenever consults it, and a Dependabot PR arrives with the lockfile already resolved. It guards the act of choosing a version, not CI.3.
@semantic-release/release-notes-generatorwas locked twice — correct.package.jsonasked for^14.0.0(→ 14.1.0) while semantic-release 25 wants^14.1.0(→ 14.1.1). Bumped the devDependency to^14.1.0; the lock now carries one copy, at 14.1.1.Also restored two things
yarn set versionsilently changed: it rewrotepackageManagerwithout the+sha512integrity hash, and stripped the comment from.yarnrc.yml. Both are back.Adjacent, not fixed here: the same Corepack cliff exists in
convertcom/python-sdkrelease.yml(two jobs,lts/*+corepack enable) andconvertcom/javascript-sdkqa.yml(corepack enable+corepack prepare yarn@stable). Both will break the same way on 2026-10-28 and want the sameyarnPathtreatment.Also in here
npmMinimalAgeGate: 4320(3 days) in.yarnrc.yml, matching convertcom/javascript-sdk#421 — php-sdk never got that hardening. NonpmPreapprovedPackagesentry is needed: this project has no internal npm dependencies (the PHP packages ship via Packagist), so nothing in our own release chain can stall on a freshly-published package. Every version in the lockfile is at least 3.5 days old, and re-resolving under the gate shifts nothing.Worth knowing about the gate: Yarn's own lockfile migration writes hardening opt-outs — on first install it rewrote
.yarnrc.ymlwithnpmMinimalAgeGate: 0,enableScripts: trueandapprovedGitRepositories: ["**"], against Yarn 4.18 defaults of1d,falseand[]. Those writes were discarded; the committed value is the deliberate 3-day one.Verification
Locally on this branch:
composer validate,composer build:validate(monorepo-builder),composer cs-check(0 of 130 files),composer analyze(phpstan, no errors), 648 unit tests, 474 cross-SDK parity tests,composer auditon both locks,yarn npm audit,semantic-release --dry-runloading every plugin, thepackages/Utilsfresh-resolve above, andinstall --immutabledriven by classic Yarn exactly as CI drives it — passing cold and failing on drift.On CI: the full matrix across PHP 8.2 / 8.3 / 8.4 in both
prefer-lowestandprefer-stable, plus Code Style, Monorepo Validation, Generated Types Guard, PHPStan and CodeQL.All 88 alerts were re-checked against the post-review lockfile. Still zero.
Supersedes — both can be closed, nothing is lost
#40 —
handlebars,picomatch. Fully covered: this PR takes handlebars to the same4.7.9and picomatch further (2.3.2/4.0.5), plus the other npm advisories #40 does not touch.#36 —
phpunitinpackages/Utils. Its alert (GHSA-vvj3-c3rp-c85p, phpunit< 10.5.62) is resolved here, and the fresh-resolve above shows the package landing on phpunit11.5.56.Its changes are deliberately not folded in, and that is worth stating rather than leaving it to look like an oversight. #36 is not only a lockfile bump — it also widens
packages/Utils/composer.json:"require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.0 || ^10.0" },That is Dependabot bending the constraint backwards so the stale locked phpunit
10.5.45becomes legal, rather than updating the lock to 11. It has two problems: it re-admits the phpunit 10 range the advisory is about, and it breaks monorepo validation. Applying that exact diff on this branch and runningcomposer build:validate:which is presumably why it has sat open since April. The root cause is that the lock was generated against an older
composer.jsonand never regenerated, so the lock and the constraint disagree. Untracking the lock removes the disagreement instead of legalising it — and the repo's own.gitignorealready asked for that.Neither PR cuts a release, and neither does this one: all three are
chore, andscripts/rollover-version-plugin.mjsreturnsnullforchore.🤖 Generated with Claude Code