Skip to content

fix(deps): resolve dependency vulnerabilities - #109

Open
anagperal wants to merge 2 commits into
feature/add_githubactionsfrom
fix/dependency-vulnerabilities
Open

anagperal wants to merge 2 commits into
feature/add_githubactionsfrom
fix/dependency-vulnerabilities

Conversation

@anagperal

@anagperal anagperal commented Aug 18, 2026

Copy link
Copy Markdown

📌 References

  • Issue: Closes #869e9w0v1 analyze dependency issues

  • Findings on this branch: open code-scanning alerts — filtered to tool OWASP Dependency-Track (yarn4), which is what this description quotes throughout.

    Why filter. The unfiltered view also lists the syft tool. Its findings against real npm
    packages are valid and are covered here through yarn4, but it additionally reports 41
    alerts against stdlib@go1.20.7
    — the Go standard library, read out of the prebuilt binary
    that esbuild ships. That is not an npm dependency of this project. Those come from NVD
    matching by bare product name (CPE) rather than by package identity, which cannot express "this
    is an npm package"; the matching has since been turned off, and what remains is residue from an
    earlier run being cleared separately.

  • Documentation: RESOLUTIONS.md, added by this PR, following the same structure as the reference repositories.

  • Reference implementations: dhis2-app-skeleton#120 — open and not approved — for the RESOLUTIONS.md structure, plus metadata-synchronization#1264 and Bulk-Load#413 for the floor conventions, and user-extended#532 for the vite 6 line. Used as references and re-verified against this tree, not copied: all are Yarn 4 browser applications and this is a Yarn 1 Node CLI, so one of their shared conventions does not apply — see the note on vite below.

  • Process notes are in the internal wiki (draft, for team discussion).

📝 Implementation

A dedicated remediation pass over the open Dependency-Track findings on this branch, so the bar
applied was fix everything that has a published fix, not introduce nothing new. Everything with a
fix installable on the Node version this project targets is fixed; everything without one is recorded
in RESOLUTIONS.md, which this repository did not have.

Scope note: this PR deliberately contains no toolchain migration — the Node and Yarn versions
are untouched. Two of the six remaining alerts are open only because of this choice.

Check the current alerts
rather than a figure quoted here

Note this repository is on Yarn 1, so the commands are yarn upgrade rather than yarn up -R,
and there is no .yarnrc.yml: no install-policy age gate applies, and no fix in this PR was deferred
by one.

Most of it is re-resolution, not new constraints

The largest part of this change is a lockfile refresh. For 33 packages the declared ranges already
admitted the patched release and the lockfile had simply gone stale, so yarn upgrade reached the
fix with a byte-identical package.json. That includes three of the four criticals:

Package From To Parent range Findings
webpack 5.70.0 5.109.2 direct, ^5.70.0 1 critical (GHSA-hc6q-2mpp-qw7j)
form-data 4.0.0 4.0.6 ^4.0.0 1 critical (GHSA-fjxv-7rqg-78g4)
minimist 1.2.5 1.2.8 ^1.2.0 1 critical (GHSA-xvch-5gv4-984h)
brace-expansion 1.1.11 1.1.18 ^1.1.7 2 high
fast-uri 3.0.6 3.1.5 ^3.0.1 5 high
js-yaml 4.1.0 4.3.1 ^4.1.0 2 high
minimatch 3.1.2 3.1.5 ^3.0.4 3 high
postcss 8.4.38 8.5.26 ^8.4.27 2 high
ws 8.17.0 8.21.3 ^8.13.0 2 high
nanoid 3.3.7 3.3.18 ^3.3.7 3 medium

The rest moved the same way, each inside a range its parents already declared: eslint,
terser, rollup, ajv, braces, micromatch, picomatch, cross-spawn, es5-ext, flatted,
follow-redirects, json5, moment, semver, word-wrap and @tootallnate/once.

Three packages left the tree entirely rather than being upgraded, which is worth recording
because none of them could have been fixed by a version bump:

  • ip@2.0.0socks-proxy-agent declares socks: ^2.7.1, and socks 2.8.x replaced ip with
    ip-address. GHSA-2p57-rm9w-gvfp (high) affects ip <= 2.0.1 with no patched version
    ip is unmaintained, so upgrading it could never have closed this. Eviction was the only route.
  • got@9.6.0 and http-cache-semantics@4.1.0 — reached only through nodemon's update-notifier
    chain, which the nodemon 3 line replaced.

Three exact pins in @eyeseetea/d2-api needed floor resolutions

axios@1.6.4 alone was the single largest item on the branch: 29 live advisories.

"@eyeseetea/d2-api" dependencies: { "axios": "1.6.4", "lodash": "4.17.21", "qs": "6.9.7" }

The ladder's upgrade the parent rung is genuinely closed here, and this was checked rather than
assumed: every published d2-api release — 1.18.0, 1.19.0, 1.19.1, 1.20.0, 1.21.0 and
42.22.0-beta.2 — carries the same three exact pins, so no d2-api version moves them.

Resolution Resolves to Live advisories against it
axios: ^1.18.0 1.19.0 0
lodash: ^4.18.0 4.18.1 0
qs: ^6.15.3 6.15.3 0

Each was verified by removing it, reinstalling and comparing resolved versions — not lockfile
bytes. All three are load-bearing: without them the tree resolves back to 1.6.4, 4.17.21 and 6.9.7
respectively. That evidence is recorded per entry in RESOLUTIONS.md.

⚠️ These cannot be dropped once d2-api is upgraded — they can only be dropped when d2-api declares
them as ranges. Removing them resolves all three downwards into the affected ranges.

One toolchain upgrade closed the last critical and the vite group together

  • vitest@0.32.4 carried GHSA-5xrq-8626-4rwp (critical). Its ranges are < 3.2.6 → 3.2.6
    and >= 4.0.0, < 4.1.0 → 4.1.0; 0.32.4 falls in the first, so 3.2.7 is sufficient. The
    advisory summary alone would have invited a needless move to vitest 4.
  • vite@4.5.3 carried 7 further advisories, and esbuild@0.18.20 came only from vite 4.

The cost was near zero, because this project does not use vite to build. There is no
vite.config.ts — the build is webpack — and vite is present only as vitest's engine.
vite-plugin-checker is not used, so the coupling that forced an ESLint flat-config port in other
repositories does not exist here. ESLint is untouched.

⚠️ Deviation from the shared convention: vite 6, not vite 7

Four reference PRs standardise on vite: ^7.3.6. That is uninstallable on this project's Node
version:

vite@7.3.6  engines.node = ^20.19.0 || >=22.12.0
.nvmrc      = v18.20.8      .github/workflows/test.yml → node-version: "18"

vite@6.4.3 is the newest line that installs on Node 18 and has 0 live advisories, so this is
a complete fix rather than a compromise.
user-extended#532, which is on vite 6.4.3,
is the applicable precedent. The vite: ^6.4.3 resolution is required because vitest declares a
range reaching vite 7 and would otherwise break the install outright.

The same Node 18 ceiling shapes two more decisions below, so it is worth stating once: Node 18 caps
vite at 6, nodemon at 3.1.11, serialize-javascript at 6.0.2 and copy-webpack-plugin at 13.

nodemailer needed a major

Direct runtime dependency. Re-resolution within ^6.7.5 reaches 6.10.1 and still leaves 8 findings
open; only the 9.x line clears them. engines.node is >=6.0.0 on nodemailer 9, so Node 18 is not a
blocker. @types/nodemailer moved to ^8.0.1 to match, since nodemailer ships no types of its own.

nodemon was required to avoid introducing a new high

Re-resolution on the 2.x line pulls simple-update-notifier@^1.0.7, which declares semver@~7.0.0
and resolves to semver@7.0.0, carrying GHSA-c2qf-rxjj-qqgw (high) that was not previously in
the tree. The 3.x line uses simple-update-notifier@^2, which declares semver@^7.5.3.

Pinned to 3.1.11 rather than ^3: nodemon 3.1.12+ depends on minimatch@10
brace-expansion@5, which declares engines.node of 20 || >=22. Recorded in RESOLUTIONS.md as a
fixture, with the condition for converting it back to a floor.

Eight devDependencies removed

No reference outside package.json — leftovers from the jest → vitest migration (3742cca,
a5b85b5): vite-plugin-checker, vite-plugin-eslint, vite-plugin-node-stdlib-browser,
vite-bundle-visualizer, fake-indexeddb, react-html-parser-ultimate, flow-remove-types,
core-js@2.6.9.

Two non-dependency changes, both required by the vitest upgrade

Neither is unrelated cleanup.

  1. vitest.config.ts — restores test collection. vitest <1 had two default include
    patterns: ["**/__tests__/**/*.?(c|m)[jt]s?(x)", "**/?(*.){test,spec}.?(c|m)[jt]s?(x)"].
    vitest 3 dropped the first. Six suites here are named *.specs.ts (plural) and matched only
    via that __tests__ pattern, so the upgrade silently cut the run from 14 files / 86 tests to
    8 / 40 — while passing green
    . The pattern is restored explicitly. This is also why the config
    already excluded **/*.data.ts: the same catch-all matched fixture files.
  2. Two test files — type and async fixes. vitest 3 types mock.calls properly, which surfaced
    three TS2532 errors under this repository's noUncheckedIndexedAccess. Fixed by typing the
    mock rather than with a non-null assertion. Separately, vitest 3 warns that an un-awaited
    expect().rejects assertion will stop being auto-awaited; one such assertion is now awaited.

🔥 Notes for the reviewer

Run against the consumer of every package whose version changed, not just yarn install, under
Node 18.20.8 (nvm use) from a clean node_modules:

Check Result
yarn install --frozen-lockfile clean, lockfile reproducible from an empty node_modules
yarn typecheck clean
eslint src/{data,domain,scripts,types,utils} clean, 0 errors — see the note on yarn lint below
yarn test 14 files / 86 tests, all passing — identical to the pre-change baseline
yarn build the real top-level webpack build, not build:dev; webpack 5.109.2 compiled successfully

Needs a manual look — not covered by the checks above:

  • Sending a real email. src/data/NotificationsEmailRepository.ts — the API shape was
    exercised, but not against a live SMTP server.
  • yarn service — nodemon crossed a major.
  • The xlsx export commandsxlsx itself is unchanged, but they are the heaviest runtime
    consumers in the tool.

Remaining findings

Six alerts are open, in three groups. Grouped by what would close them, because the cheapest action here is
not a code change at all.

Group Alerts Severity What would close it Cost
Withdrawn advisories 2 1 high, 1 medium Dismiss in the UI — no code change zero
Blocked by Node 18 2 1 high, 1 medium Raise the project's Node version toolchain change
No fix published to npm 2 2 high Vendor distribution, or replace the library separate decision

The alerts were first raised on 2026-06-22 and GitHub keeps their original text, so the
code-scanning UI still names the versions from that day — serialize-javascript@6.0.0,
eslint@8.11.0, esbuild@0.25.3. The versions below are the ones the tree resolves today; only
the alert titles are stale.

Two need a dismissal, not a code change. Neither describes a real vulnerability, and "fixing"
either would be an upgrade that corrects nothing: GHSA-gv7w-rqvm-qjhr against esbuild@0.25.12,
withdrawn upstream on 2026-06-17, and GHSA-p5wg-g6qr-c7cg against eslint@8.57.1, withdrawn on
2026-02-03.

Two are a published fix that cannot be installed here. serialize-javascript@6.0.2 carries
GHSA-5c6j-r48x-rmvq (high) and GHSA-qj8w-gfj5-8c6v (medium). The fix is 7.0.5, which declares
engines.node >=20.0.0; the copy-webpack-plugin release that requests the 7.x line declares
>=20.9.0. That is a different conclusion from "no fix exists", and it is recorded as such. Build
tooling only — it is not part of the built artifact.

Raising the project's Node version closes this group in one decision — see Follow-up work
below, where it is costed against the other two follow-ups.

Two are a genuine dead end on npm. xlsx@0.18.5 carries GHSA-4r6h-8v6p-xvw6 and
GHSA-5pgg-2g8v-p4x9, both high, both with no patched version recorded. Checking the published
version list rather than only the advisory confirms it: npm view xlsx dist-tags returns
latest: 0.18.5, because SheetJS stopped publishing to npm at that version and moved to their own
CDN. No registry-selectable version escapes the ranges. Runtime-reachable when parsing a workbook
supplied from outside this repository. The two routes out — installing from the SheetJS CDN tarball,
which puts a non-registry URL in the lockfile, or migrating the two call sites to xlsx-js-style,
which this repository already depends on — are both decisions in their own right and are
deliberately not taken here.

Follow-up work

Three changes are out of scope here and each is worth doing on its own. Listed with what it closes,
because two of them are the reason anything remains open at all.

Follow-up What it closes Where the work is
Raise the Node version The 2 serialize-javascript findings, and retires a resolution This repository
Migrate Yarn 1 → 4 No advisory directly — it is preventive This repository
Relax three exact pins in @eyeseetea/d2-api The 3 resolutions in this PR, and the same ones in four other apps d2-api

📹 Screenshots/Screen capture

None

📑 Others

  • Any change in the D2 Api? No change is required for this
    PR to merge, but one is worth making.
    @eyeseetea/d2-api declares axios, lodash and qs
    as exact versions in every published release up to and including 1.21.0 and
    42.22.0-beta.2. axios: 1.6.4 alone had accumulated 29 live advisories and structurally cannot
    receive a patch. Because the pins are exact, every consuming application inherits all three and
    has to write the same three resolutions
    — which is exactly what
    metadata-synchronization#1264,
    Bulk-Load#413,
    home-page-app-dev#71 and this PR have
    each done independently. Relaxing them to floors upstream would retire those entries across every
    consuming repository at once. Not raised yet.

  • Two pre-existing issues found while verifying this branch. Neither is caused by this PR and
    neither is fixed here.

    The Bundlemon check has never passed — it fails on every run since 2026-06-22, including on
    feature/add_githubactions itself, so the red mark on this PR is not new. The cause is a path
    mismatch: the shared .bundlemonrc in EyeSeeTea/github-workflows declares
    "baseDir": "./build", while this project is a Node CLI whose webpack config outputs to dist/,
    so BundleMon finds nothing to measure. The workflow already supports the fix — it looks for a
    .bundlemonrc in the caller repository first and only falls back to the shared one — so adding
    one here with "baseDir": "./dist" resolves it.

    yarn service no longer does anything useful, and nodemon could go with it.
    nodemon.json runs ./src/index with no subcommand, so the CLI prints its usage and exits and
    the watcher reports app crashed on every reload. That dates from 2022, when src/index.ts was
    a single dataset-comparison script, and stopped making sense once the entry point became a
    multi-command dispatcher; it is undocumented in the README. nodemon is the only consumer of
    that script, so removing both would drop 11 packages and retire the manifest's only
    exact-version pin — nodemon is held at 3.1.11 because 3.1.12+ pulls minimatch@10
    brace-expansion@5, which requires Node 20. That pin exists solely to keep a broken script
    installable; yarn start:dev <command> covers the same need.

Re-resolve the lockfile within the ranges already declared, and add the
constraints needed to reach patched versions those ranges cannot select
on their own.

Re-resolution alone (no manifest change required) moves 33 transitive
packages onto patched releases and drops got, http-cache-semantics and
ip from the tree entirely, the last of which has no patched release.

Manifest changes:

- resolutions axios ^1.18.0, lodash ^4.18.0, qs ^6.15.3: @eyeseetea/d2-api
  declares these as exact versions and no published release of it relaxes
  them, so upgrading the parent cannot move them.
- resolution vite ^6.4.3: vitest declares a vite range reaching vite 7,
  which requires Node 20.19 or later and cannot be installed here.
- nodemailer ^9.0.5 with @types/nodemailer ^8.0.1: only the 9.x line is
  outside the open advisory ranges.
- vitest ^3.2.7 with vite ^6.4.3 and vite-tsconfig-paths ^4.3.2, which is
  the newest line still loadable from a CommonJS config file.
- nodemon 3.1.11: the newest release installable on Node 18, since 3.1.12
  requires brace-expansion 5. The 2.x line resolves semver to a vulnerable
  release through its update-notifier chain.
- Remove eight devDependencies that have no reference outside package.json.

vitest 3 dropped the default "**/__tests__/**" include pattern, which
would have silently stopped collecting the *.specs.ts suites. It is
restored explicitly in vitest.config.ts so the same 14 suites run.

Type the exportTranslations mock so the first-call assertions narrow
without a non-null assertion, and await a rejects assertion that vitest 3
no longer auto-awaits.

Add RESOLUTIONS.md recording each constraint, its drop-when condition, and
the two findings that have no fix installable on this Node version.
@anagperal
anagperal requested a review from MiquelAdell August 18, 2026 13:56
@anagperal
anagperal marked this pull request as ready for review August 18, 2026 13:57
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