Skip to content

[SECURITY] resolve dependency vulnerabilities - #413

Open
anagperal wants to merge 7 commits into
feature/test_dependency_trackfrom
fix/resolve-dependency-vulnerabilities
Open

[SECURITY] resolve dependency vulnerabilities#413
anagperal wants to merge 7 commits into
feature/test_dependency_trackfrom
fix/resolve-dependency-vulnerabilities

Conversation

@anagperal

@anagperal anagperal commented Aug 4, 2026

Copy link
Copy Markdown

📌 References

  • Issue: Closes #869e9vzf7 analyze dependency issues
  • Findings on this branch: open code-scanning alerts — filter by tool OWASP Dependency-Track (yarn4); the other tools in that view report different scopes.
  • Base branch: this PR targets feature/test_dependency_track (Add dependency track checks #405), not development, because Add dependency track checks #405 is what forwards DTRACK_API_KEY to the workflow. Without it the scan jobs are skipped and the workflow still goes green, so development has no analyses at all and there is nothing to measure against.
  • Reference implementation: dhis2-app-skeleton#120 — open and not approved. Used as a reference and re-verified against this tree, not copied; two of its decisions did not survive that check and are noted below.
  • Process notes are in the internal wiki (draft, for team discussion).

📝 Implementation

This is a dedicated remediation PR, so the bar applied was fix everything that has a published fix, not introduce nothing new.

Resolves every critical and high vulnerability finding on this branch, as reported by Dependency-Track — the tool the CI gate reads, and the only one quoted here. yarn npm audit scores several of these packages differently, so the two are never mixed.

What remains is medium-severity only, and each one is accounted for below. Check the current alerts rather than a figure quoted here: a count in a PR description is only true on the day it is written, and the scanner is the authority.

Adds RESOLUTIONS.md, which this repository did not have.

Fixed without adding any pin

The lockfile was holding these below the patched release even though every parent already accepted it. A refresh was enough — no maintenance debt added. Most of the criticals were a stale lockfile, not an unfixable path.

Package From To Parent range Findings
handlebars 4.7.6 4.7.9 @dhis2/d2-i18n-generate wants ^4.0.11 3 critical, 4 high
@babel/traverse 7.9.5 / 7.14.0 / 7.16.8 7.29.8 ^7.7.0, ^7.13.0, ^7.16.7 3 critical
pbkdf2 3.0.17 3.1.6 parse-asn1 wants ^3.0.3 2 critical
minimist 1.2.5 1.2.8 seven parents, all ^1.1.0^1.2.5 1 critical
sha.js 2.4.11 2.4.12 ^2.4.0 / ^2.4.8 1 critical
cipher-base 1.0.4 1.0.7 ^1.0.0^1.0.3 1 critical
@babel/plugin-transform-modules-systemjs 7.16.7 7.29.8 via @babel/core 1 high
postcss, semver, json5, picomatch, tar, flatted, ansi-regex, braces, cross-spawn, decode-uri-component, follow-redirects, js-yaml, ws, ip-address various patched existing ranges remainder of the highs

Decayed pins bumped to ^ ranges

This repository carried resolutions copied from the skeleton's February 2026 security commit. Being exact versions, no patch could ever be selected, and they had decayed into the findings they were added to fix.

Resolution From To Resolves to Fixes
axios 1.13.5 ^1.18.0 1.19.0 10 high
lodash, lodash-es 4.17.23 ^4.18.0 4.18.1 2 high
qs 6.14.2 ^6.15.3 6.15.3 medium — the exact pin sat inside the affected range

axios is the clearest case for the whole change: pinned at an exact version to fix a vulnerability, it had accumulated 10 open high-severity advisories because no patch could ever land. lodash: 4.17.23 was the last vulnerable version of lodash. With ^ ranges both resolve to a patched release on their own.

⚠️ These cannot simply be dropped. @eyeseetea/d2-api@1.21.0 requests exact axios@1.6.4, lodash@4.17.21 and qs@6.9.7, and @eyeseetea/d2-ui-components@2.12.0 requests exact lodash@4.17.21 — so removing a constraint resolves downwards into the affected range. They are required as floors, which is why they are ranges rather than exact versions.

The direct axios and lodash declarations in dependencies were updated to the same floors; they still said 1.13.5 and 4.17.23, contradicting the resolutions.

Four more constraints that turned out not to be needed at all

form-data, jszip, minimatch and brace-expansion were bumped from exact versions to ranges earlier in this PR, on the same reasoning as the three above. Re-testing them afterwards — remove, re-install, compare the resolved versions — showed that none of them was doing anything the parents were not already doing. All four have been retired.

Retired Parents declare Resolves to, with or without it
form-data: ^4.0.6 ^4.0.0 (×2), ^4.0.6 4.0.6
jszip: ^3.8.0 ^3.2.2, 3.10.1 3.10.1
minimatch: ^3.1.4 ^3.0.4 (×7) — already admits the patched 3.1.5 3.1.5, plus 10.2.6
brace-expansion: ^1.1.17 only minimatch@3.x at ^1.1.7 — already admits 1.1.18 1.1.18, plus 5.0.9

The minimatch pair is the interesting one, and it is a correction to the skeleton's approach rather than a copy of it. Its own drop-when condition — "every consumer resolves to a patched minimatch on its own line without it" — turned out to be satisfied already: the seven parents that request minimatch declare ^3.0.4, which admits the patched 3.1.5 unaided, and brace-expansion only existed on the 1.x line because minimatch was being held at 3.x. Removing both leaves minimatch at 10.2.6 and 3.1.5, and brace-expansion at 1.1.18 and 5.0.9 — all four outside every advisory affecting them.

Removing the minimatch constraint also lets glob@13.0.6 have the ^10.2.2 it declares, instead of being pulled down seven majors.

Worth recording before someone re-investigates it: forcing glob@13 onto minimatch@3.1.5 looks like it should break, because glob@13 references minimatch.escape and minimatch.unescape and neither exists in 3.1.5. It does not break — glob@13's default entry point is a bundle with minimatch inlined, which never requires the external copy at runtime. So the constraint was neither helping nor breaking anything.

The test matters as much as the result. Removing minimatch and brace-expansion does change the lockfile — new lines appear — so a byte-identical comparison would have reported them as load-bearing and kept them. What settles it is that no resolved version ends up anywhere an advisory reaches.

Scoped pins added

Resolution Why the range cannot reach the fix Findings
react-linkify/linkify-it: ^5.0.2 react-linkify@1.0.0-alpha wants ^2.0.3; the fix is on 5.x. The parent is requested at an exact version by @eyeseetea/d2-ui-components, so it cannot be moved either. Same approach as #120. react-linkify is unmaintained and written against the linkify-it 2 API, so compatibility was checked rather than assumed — see the tester notes 2 high
i18next-conv/node-gettext: ^3.0.1 i18next-conv@6.1.1 wants ^2.0.0. The advisory records no patched version, but its affected range is <= 3.0.0 and 3.0.1 is published and outside it — the dead end came from advisory metadata, not from the registry 1 high

Toolchain migration — vite and vitest

vitest 0.32.4 carried the last critical (GHSA-5xrq-8626-4rwp), patched only in 3.2.6+, which needs vite 5+. There was no smaller fix.

Package From To
vite ^4.2.0 (4.5.14) ^7.3.6
vitest ^0.32.2 (0.32.4) ^3.2.7
@vitejs/plugin-react ^3.1.0 ^5.1.0 (5.2.0)
vite-plugin-checker ^0.6.2 ^0.11.0
esbuild (transitive) 0.18.20 0.28.1

Clears vitest GHSA-5xrq-8626-4rwp (critical) and vite GHSA-c27g-q93r-2cwf and GHSA-fx2h-pf6j-xcff (high).

vite 7 rather than 6, because the esbuild fix is only reachable there: vite@6.4.3 requests esbuild@^0.25.0 and cannot select the patched 0.28.1, while vite@7.3.6 requests ^0.27.0 || ^0.28.0 and reaches it natively — which is why no esbuild pin is needed.

The esbuild advisory that motivated part of this, GHSA-gv7w-rqvm-qjhr, was withdrawn on 2026-06-17 and does not describe a real defect. The vite 7 upgrade is still the right call on the vitest critical alone, but the esbuild move should not be counted as a fix.

⚠️ No ESLint migration was needed, and that is a correction to #120, which bundled one in. vite-plugin-checker@0.11.0 supports vite >=5.4.20 while still accepting eslint >=7; only 0.12.0 raises that to eslint >=9.39.1. Stopping at 0.11.0 reaches vite 7 without the ESLint 8 → 9 flat-config port, so eslint and typescript are unchanged here.

Two peer warnings are expected and benign:

  • vite-plugin-node-stdlib-browser@0.2.1 declares vite ^2 || ^3 || ^4. 0.2.1 is its latest release and it has not been updated for vite 5+. It works: the production build succeeds and vm-browserify is still bundled, which is the polyfill doing its job. Worth replacing with a maintained equivalent if it ever breaks, but it is not broken.
  • @types/node@17.0.8 does not satisfy the range vite requests. tsc reports zero errors, so it was left alone rather than bumped inside a security change.

Medium-severity pass

A second pass cleared the remainder, almost all of it the same staleness pattern — patched versions already admitted by the ranges their parents request:

micromatch 4.0.8 · browserslist 4.28.7 · word-wrap 1.2.5 · path-parse 1.0.7 · bn.js 4.12.5 · @sideway/formula 3.0.1 · joi 17.13.4 · yaml 1.10.3 · ajv 6.15.0 · @tootallnate/once 2.0.1 · @babel/helpers and @babel/runtime-corejs3 7.29.7.

Plus two direct-dependency changes:

  • react-router-dom → 6.30.4, the patched release on the 6.x line (clears GHSA-2j2x-hqr9-3h42).
  • @babel/core and @babel/preset-typescript from exact 7.16.7 to ranges. The tree carried two Babel versions, because babel-preset-react-app and eslint-config-react-app request ^7.16.0 and the exact pin could not reach it. They now collapse onto 7.29.7.

One finding was reported by Dependency-Track as once@2.0.0, a version that does not exist on npm. It is @tootallnate/once — the scoped prefix is dropped in the report. Worth knowing before chasing a package that looks unpublished.

Removed

  • babel-core@6.26.3 — a direct devDependency and the only path to babel-traverse@6.26.0, a critical with no published fix on any version. Nothing required it: babel-eslint depends on @babel/traverse@^7.7.0, not Babel 6, and it appears in no source file or config.
  • postcss-rtl@1.7.3 — a direct devDependency and the only path to rtlcss@2.5.0postcss@6.0.23 (2 high). There is no PostCSS config file in the repository and no reference to it in the build.
  • path-to-regexp: 1.9.0 resolution — inherited from the skeleton. No path-to-regexp entry existed in this yarn.lock at all, so the constraint matched nothing and removing it left the lockfile byte-identical. That is conclusive here, but it is the strong case: as the four retirements above show, a constraint can change the lockfile and still change nothing installed, so the general test is whether a resolved version moves. (The skeleton has since retired this pin too, having found it inert in its own tree as well.)
  • form-data, jszip, minimatch and brace-expansion resolutions — see above.

The inherited exact versions, reopened into ranges

@babel/runtime, i18next, glob-parent, moment, nanoid, node-fetch, diff, debug and ua-parser-js were added as exact versions by the copied February 2026 commit with no explanation. By their own descriptions elsewhere, several are security floors — and a floor written as a fixture cannot receive a patch, which is exactly how axios: 1.13.5 and qs: 6.14.2 above became the findings they were added to prevent.

None of them was inside a live advisory range, checked against the published advisory database, so this is not a remediation — it closes a decay path before it opens. Eight were converted; a caret keeps every consumer on the major the exact version already forced it onto, so nothing changes line.

Resolution Now resolves to Resolution Now resolves to
@babel/runtime: ^7.26.10 7.29.7 node-fetch: ^2.6.7 2.7.0
moment: ^2.29.4 2.30.1 diff: ^5.2.2 5.2.2
nanoid: ^3.3.8 3.3.17 debug: ^4.3.4 4.4.3
glob-parent: ^5.1.2 5.1.2 ua-parser-js: ^0.7.24 0.7.41

⚠️ i18next is deliberately left exact. It sits far below the current release line and a resolution on it can break application startup, so it changes only with an app-startup check behind it.

Two things surfaced while doing this. nanoid: 3.3.8 was holding postcss below the ^3.3.16 it declares, and node-fetch: 2.6.7 was holding a consumer below its declared ^2.7.0a constraint holding a package beneath its own parent's range is worth treating as a signal in itself, because it looks like protection and is the opposite. And the direct moment dependency still said 2.29.4 while its resolution allowed more, the same contradiction axios and lodash had.

Pruning these remains open and has to be judged one at a time. glob-parent is the likeliest candidate: it is a global entry and the tree has consumers declaring ^3.1.0 and ^6.0.1 that it pulls onto the 5.x line.

Install policy

.yarnrc.yml declared only nodeLinker, so three of the four supply-chain settings fell back to yarn's defaults — looser than what the other repositories in this family run. They were never decided; they were simply never declared.

Setting Before (effective) After
npmMinimalAgeGate 0 7d
enableScripts true false
enableHardenedMode null true
checksumBehavior throw throw (already matched)

The lockfile comes back byte-identical, so this costs nothing today. Two notes for whoever works here next, both now in RESOLUTIONS.md:

  • The age gate is what will make a reachable fix look unreachable. It refuses releases published in the last week, and yarn up -R reports success while selecting one patch short rather than failing. Compare the version you got against the version the advisory names.
  • enableScripts: false produces YN0004 for esbuild and three core-js variants. Neither breaks — esbuild ships its binary as a platform-specific optional package rather than fetching it in a postinstall, verified by running a transform through it. A package that genuinely needed its postinstall would fail, so a new YN0004 is worth checking rather than ignoring.

Tooling and references

Findings were triaged with /sca-triage, which fetches the SARIF for the branch and classifies every finding into buckets. It turns a raw alert list into a worklist quickly, and that is worth having — but its classification is a starting point, not a verdict:

  • It classified the three @babel/traverse criticals and the @babel/plugin-transform-modules-systemjs high as unfixed-upstream. They are not: GHSA-67hx-6x53-jw92 is patched in @babel/traverse@7.23.2, and the "no patched version" row of that advisory belongs to babel-traverse, the Babel 6 package.
  • It reported form-data's fix as 2.5.6, a different major line from the installed 4.x.

Every bucket was re-checked against the dependency paths, the advisory's affected range and the published version list before acting.

The reference implementation is dhis2-app-skeleton#120, and the approach and the shape of RESOLUTIONS.md follow metadata-synchronization#1061, which established the why / fixes / drop-when contract. Three of #120's decisions did not survive re-verification here — the ESLint 9 migration, which the vite 7 fix does not require; the path-to-regexp pin, which binds nothing in this tree; and the minimatch/brace-expansion approach, which this tree does not need at all. All three have been fed back to that PR, and the skeleton has since retired the path-to-regexp pin in its own tree.

Documentation

RESOLUTIONS.md (new) documents every resolution with why / fixes / drop-when, split into Security floors and Compatibility fixtures with no recorded rationale. It also holds the decisions that leave no trace in the manifest at all, the ones a future maintainer cannot recover by reading the tree: why the archived i18n packages are still here and what should trigger revisiting that, each removed resolution and the evidence for removing it, and the findings with no remediation available.

Remaining findings

All medium, and none with a remediation available inside this PR's scope.

Finding Package Why it is still here
GHSA-jjmj-jmhj-qwj2 react-router-dom@6.30.4 Affected range is >= 6.30.2, <= 6.30.4, and 6.30.4 is the final release of the 6.x line — it is still inside the range. No fix exists below v7
GHSA-337j-9hxr-rhxg react-router@6.30.4 Affected >= 6.4.0, < 7.18.0, patched in 7.18.0
GHSA-wrjc-x8rr-h8h6 react-router@6.30.4 Affected >= 6.0.0, < 7.18.0, patched in 7.18.0
GHSA-848j-6mx2-7j84 elliptic@6.6.1 The advisory covers every published version (<= 6.6.1) and 6.6.1 is the latest release. Nothing to upgrade to
GHSA-p5wg-g6qr-c7cg eslint@8.7.0 This advisory was withdrawn on 2026-02-03. It does not describe a real defect and should be dismissed rather than remediated

The three react-router findings share one fix path: migrating to react-router v7, which is an API change on a direct dependency used throughout the routing layer. Bumping to 6.30.4 within this PR did clear a fourth finding (GHSA-2j2x-hqr9-3h42, patched in 6.30.4), but the 6.x line ends there. The migration belongs in its own PR with its own testing, not bundled into a dependency pass.

elliptic and the withdrawn eslint advisory are recorded in RESOLUTIONS.md under Findings with no remediation available, so neither is investigated again from scratch.

🔥 Notes for the reviewer

Run nvm use first.

Check Result
yarn install --immutable pass — lockfile consistent
yarn lint pass, 0 errors
yarn localize pass — all five languages generated
yarn test pass — 16 files, 137 tests, under vitest 3.2.7
tsc --noEmit 0 errors
yarn build pass — bundle, manifest.webapp and bulk-load.zip produced under vite 7
Dev server pass — vite 7.3.6 boots, app served, ESLint/TypeScript overlays clean
yarn npm audit -R --severity high no findings — an indication only, not the number that counts
Dependency-Track scan of the head branch no critical or high findings remain
esbuild transform, run directly pass — confirms the binary works with build scripts disabled
moment formatting with a Spanish locale pass under 2.30.1, the version the reopened range selects

What to check by hand:

  • Template generate/import round trip through the UI. moment moved 2.29.4 → 2.30.1 and this repository formats dates in generated templates, so this is the check that exercises it.
  • yarn install && yarn start — the app loads and a screen is usable. A dependency fix that clears the scanner and breaks the app is the failure mode this change is guarding against.
  • yarn localize — translations regenerate. This still runs through d2-i18n-generate, unchanged, but handlebars moved underneath it.
  • yarn lint, yarn test, yarn build including the zip step — the dev toolchain moved from vite 4 to vite 7, and minimatch is now unconstrained, so these are what exercise both.
  • yarn why axios and yarn why lodash — expect 1.19.0 and 4.18.1, selected by the ^ ranges. If either resolves lower, a parent is requesting an exact version and the floor is not doing its job.
  • yarn why minimatch and yarn why brace-expansion — expect 10.2.6 alongside 3.1.5, and 5.0.9 alongside 1.1.18, with no resolution forcing any of them. Multiple major lines coexisting is the expected state here, not a regression.
  • Any screen rendering <Linkify>linkify-it was forced across a major. It was checked (callable CJS export, .tlds(), .match(), .test() unchanged, and <Linkify> producing the expected <a href> for URLs and mailto: addresses), but it is worth one look in the running app.

📹 Screenshots/Screen capture

No user-facing change — dependencies, build tooling and configuration only.

📑 Others

Follow-up, none of it in scope here:

  • development is not being scanned. It has zero Dependency-Track analyses, because its main.yml does not forward DTRACK_API_KEY — the scan jobs are skipped and the workflow still reports success. Add dependency track checks #405 is the change that fixes this. Until it merges there is no baseline for the CI gate, so the numbers here come from a scan of the head branch, not a base-to-head comparison.
  • Migrate to react-router v7, which is the only route to the three remaining medium findings. Its own PR — it is an API change on a direct dependency, not a dependency bump.
  • Prune the inherited resolutions, now that they are ranges rather than exact versions. That is a separate question from reopening them and has to be judged one entry at a time: remove, re-install, and compare resolved versions rather than lockfile bytes. glob-parent is the likeliest to go — it is global, and the tree has consumers on ^3.1.0 and ^6.0.1 that it pulls onto the 5.x line.
  • Dismiss the withdrawn eslint advisory (GHSA-p5wg-g6qr-c7cg) in code scanning, so it stops being re-triaged. Withdrawn advisories are not remediated, and upgrading eslint on account of it would be work for nothing.

…tions

- Re-resolve stale transitives within the ranges their parents already
  request: handlebars 4.7.9, @babel/traverse 7.29.8, pbkdf2, minimist,
  sha.js, cipher-base, postcss, semver, json5, picomatch, tar and others.
- Convert the inherited exact-version pins to floors: axios ^1.18.0,
  lodash/lodash-es ^4.18.0, form-data ^4.0.6, minimatch ^3.1.4,
  brace-expansion ^1.1.17, qs ^6.15.3, jszip ^3.8.0. An exact version
  cannot select a patch, so each had stopped doing its job. They cannot
  simply be dropped: @eyeseetea/d2-api requests exact axios@1.6.4,
  lodash@4.17.21 and qs@6.9.7, so removing a constraint resolves downwards.
- Add scoped resolutions react-linkify/linkify-it ^5.0.2 and
  i18next-conv/node-gettext ^3.0.1, where the parent ranges cannot reach
  the fix and the parents are themselves pinned by their consumers.
- Migrate vite ^4.2.0 to ^7.3.6 and vitest ^0.32.2 to ^3.2.7, with
  @vitejs/plugin-react ^5.1.0 and vite-plugin-checker ^0.11.0, pulling
  esbuild to 0.28.1. vite 7 is required because 6.4.3 caps esbuild at
  ^0.25.0. Stopping at vite-plugin-checker 0.11.0 keeps eslint 8.
- Remove babel-core@6.26.3 and postcss-rtl@1.7.3: both unused, and the sole
  paths to babel-traverse@6.26.0 and postcss@6.0.23.
- Remove the path-to-regexp resolution, which matched no descriptor.

Add RESOLUTIONS.md, documenting every resolution with why/fixes/drop-when,
the inherited pins whose rationale was never recorded, and the reason the
archived @dhis2/d2-i18n-* pair is not yet replaced.
- Re-resolve within the ranges parents already request: micromatch 4.0.8,
  browserslist 4.28.7, word-wrap 1.2.5, path-parse 1.0.7, bn.js 4.12.5,
  @sideway/formula 3.0.1, joi 17.13.4, yaml 1.10.3, ajv 6.15.0,
  @tootallnate/once 2.0.1, @babel/helpers and @babel/runtime-corejs3 7.29.7.
- Bump react-router-dom to 6.30.4, the patched release on the 6.x line.
- Open @babel/core and @babel/preset-typescript from exact 7.16.7 to ranges.
  babel-preset-react-app and eslint-config-react-app already request ^7.16.0,
  so this collapses the two babel versions in the tree onto one.

Record in RESOLUTIONS.md the two findings with no remediation available:
elliptic, whose advisory covers every published version, and a withdrawn
eslint advisory that should be dismissed rather than fixed.
The entry cited GHSA-g8qh-9h27-crr2, which does not exist. The advisory is
GHSA-g974-hxvm-x689 — same package, same affected range (<= 3.0.0), same
absence of a recorded patched version, so the reasoning in the entry is
unchanged.
The three remaining react-router advisories had no entry in RESOLUTIONS.md,
so the reasoning lived only in the PR description and would have been lost
once the PR closed.

Records that the 6.x line has no fix — two advisories are patched in 7.18.0
and the third covers 6.30.4, the final 6.x release — that the bump to 6.30.4
still cleared a fourth advisory, and that anyone picking up the migration
should go straight to v7 rather than stopping on 6.x.
@anagperal
anagperal marked this pull request as ready for review August 5, 2026 07:37
@anagperal
anagperal requested a review from MiquelAdell August 5, 2026 07:37
Each was tested by removing it, re-installing and comparing the resolved
versions. None of the four changes what is installed in a way that
matters, and all remaining versions are outside every advisory that
affects them.

form-data and jszip were inert. Every parent range already reaches the
patched release - form-data stays at 4.0.6 and jszip at 3.10.1 with or
without the entry. The original reasoning noted that d2-api requests
form-data at ^4.0.0 'which admits affected 4.0.x releases', which is
true but incomplete: ^4.0.0 admits the patched 4.0.6 as well.

minimatch and brace-expansion met the drop-when condition the minimatch
entry itself recorded. The seven parents that request minimatch declare
^3.0.4, which already admits the patched 3.1.5, and brace-expansion only
existed on the 1.x line because minimatch was held at 3.x. Removing both
gives minimatch 10.2.6 alongside 3.1.5, and brace-expansion 5.0.9
alongside 1.1.18 - all four patched.

Removing the minimatch entry also lets glob@13.0.6 have the ^10.2.2 it
declares, instead of being pulled down seven majors.

Records why that downgrade never broke glob@13 - its default entry is a
bundle with minimatch inlined - so the next audit does not re-investigate
it, and notes the compatibility checks behind the linkify-it entry.

Also restates the retirement test as 'does a resolved version move'
rather than 'is the lockfile byte-identical'. The latter proves a
constraint did nothing, but the reverse does not hold.

Verified: yarn install --immutable, tsc --noEmit, lint, 137 tests,
localize, build.
Eight resolutions inherited from the copied February 2026 security commit
were written as exact versions. By their own descriptions several are
security floors, which is the shape that cannot receive a patch and
eventually becomes the finding it was added to prevent - the same decay
that had already happened to axios and qs in this repository.

None of them was inside a live advisory range, checked against the
published advisory database, so this is not a remediation. It removes a
decay path before it opens.

  @babel/runtime  7.26.10 -> ^7.26.10   resolves 7.29.7
  glob-parent     5.1.2   -> ^5.1.2     resolves 5.1.2
  moment          2.29.4  -> ^2.29.4    resolves 2.30.1
  nanoid          3.3.8   -> ^3.3.8     resolves 3.3.17
  node-fetch      2.6.7   -> ^2.6.7     resolves 2.7.0
  diff            5.2.2   -> ^5.2.2     resolves 5.2.2
  debug           4.3.4   -> ^4.3.4     resolves 4.4.3
  ua-parser-js    0.7.24  -> ^0.7.24    resolves 0.7.41

A caret keeps every consumer on the major the exact version already
forced it onto, so no package changes line. All eight resulting versions
are outside every advisory affecting them.

i18next is deliberately left exact: it sits far below the current release
line and a resolution on it can break application startup, so it changes
only with an app-startup check behind it.

The direct moment dependency was reopened to the same range; it still
said 2.29.4 while the resolution allowed more, which is the same
contradiction axios and lodash had.

Also records that nanoid was holding postcss below the ^3.3.16 it
declares, and node-fetch below the ^2.7.0 one consumer declares - a
constraint holding a consumer beneath its own parent's range is worth
treating as a signal in itself.

Verified: install --immutable, tsc --noEmit, lint, 137 tests, localize
with no content change, full build, and moment 2.30.1 formatting with a
Spanish locale.
.yarnrc.yml declared only nodeLinker, so three of the four supply-chain
settings fell back to yarn's defaults, which are looser than what the
other repositories in this family run:

                      before (effective)  after
  npmMinimalAgeGate   0                   7d
  enableScripts       true                false
  enableHardenedMode  null                true
  checksumBehavior    throw               throw (already matched)

None of these was a decision - they were simply never declared - so this
repository was the most permissive of the three while carrying the same
kind of dependency risk.

Alignment costs nothing here: the lockfile comes back byte-identical, and
install, typecheck, lint, tests, localize and the full build all pass
unchanged.

enableScripts: false is the one worth knowing about. Yarn now reports
YN0004 for esbuild and three core-js variants. Neither breaks - esbuild
ships its binary as a platform-specific optional package rather than
fetching it in a postinstall, verified by running a transform through it,
and core-js's script only prints a funding message. RESOLUTIONS.md now
carries that note, along with the warning that the age gate is what makes
a reachable fix look unreachable.

Verified: install --immutable, tsc --noEmit, lint, 137 tests, localize,
full build, and an esbuild transform executed directly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant