Repository navigation
fix(deps): bump js-yaml from 4.1.1 to 4.3.2 (CVE-2026-84375) - #42
Conversation
Re-pin the transitive dependency js-yaml in package-lock.json from 4.1.1 to 4.3.2, the first patched version for GHSA-2883-xcg3-v3hh / CVE-2026-84375. The maxTotalMergeKeys budget did not charge an empty merge source, so a document that aliases one large sequence of {} mappings into many targets did O(sources x targets) work without ever moving the counter. All five consumers require ^4.1.0, so the bump is lockfile-only: no package.json change, and no dependent range is left unsatisfied. 4.3.2 also clears the three other open js-yaml alerts on this repo, whose ranges all include 4.1.1: #12 (CVE-2026-53550), #16 (CVE-2026-59869) and #29 (GHSA-5p4m-2wfm-xmqj). Add tests/js-yaml-security.test.mjs, which pins the corrected cost model (one budget unit per merge source, on top of one per folded key) and keeps normal merge use and this repo's own YAML parsing intact. Verified to fail 10 of 20 tests on 4.1.1, 4.3.0 and 4.3.1, and to pass 20 of 20 on 4.3.2. Alert: https://github.com/SeriousJul/seriousjul.github.io/security/dependabot/48
SeriousJul
left a comment
There was a problem hiding this comment.
Review: PR #42
I checked this PR by reproducing it. I ran every command and every measurement below on this branch. Nothing here is taken from the PR text on trust.
- Score: 93 / 100
- Specification Check: Pass. Every acceptance point for alert #48 is met, and I reproduced each one. Details in section 1.
- Quality Check: Pass, with one latent defect in the new test file. The dependency change itself is correct, minimal, and reproducible from the registry. Details in section 2.
- Required Changes: One, in section 3. It is a one-line fix to an over-broad matcher in the new test file. Everything in section 4 is optional.
Outcome: Request changes. Nothing here questions the pin, the version choice, or the reachability analysis. I am asking for section 3 so the guard test cannot report a false diagnosis later.
1. Specification Check: Pass
The assigned item is Dependabot alert #48 (CVE-2026-84375). Every acceptance point is met, and each one is independently confirmed.
The advisory is what the PR says it is. From the public advisory database, GHSA-2883-xcg3-v3hh / CVE-2026-84375, js-yaml, severity high, CVSS 3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (base score 7.5), range >= 4.0.0, < 4.3.2, first patched 4.3.2. The flaw text matches the PR description exactly: the budget does not charge an empty mapping, so N sources merged K times costs O(N * K) work with no change to the counter.
| Acceptance point | Result | How I confirmed it |
|---|---|---|
Resolved version sits outside >= 4.0.0, < 4.3.2 |
Pass | node -e require('js-yaml/package.json').version after npm ci gives 4.3.2 |
| Lockfile is the only production change | Pass | Diff is 13 added / 3 deleted lines in one node_modules/js-yaml block; package.json untouched |
| Integrity matches the registry, not a hand-edit | Pass | Committed sha512-SFNOvSJ+Dgf... equals npm view js-yaml@4.3.2 dist.integrity |
| The lockfile is what npm itself would write | Pass | npm install --package-lock-only regenerates the committed file byte for byte. This is the strongest check here, and it rules out a manual edit |
| No second copy left vulnerable | Pass | npm ls js-yaml --all shows five consumers, all deduped to one flat js-yaml@4.3.2. find node_modules -type d -name js-yaml returns one path |
| Bump satisfies every consumer range | Pass | All five declare ^4.1.0. 4.3.2 is the highest 4.x on the registry, so the upper bound of ^ also holds |
npm audit no longer reports js-yaml |
Pass | Zero js-yaml entries. 40 other packages stay flagged, as the PR states |
| Site still builds and type-checks | Pass | npm run typecheck clean. npm run build [SUCCESS], 38 HTML pages |
| Guard is live at the real call sites | Pass | @11ty/gray-matter/lib/engines.js does parse: yaml.load.bind(yaml), and cosmiconfig/dist/loaders.js:50 does yaml.load(content). Both pass no options, so the default budget is the only defence. The test targets exactly that |
| The pin cannot regress | Pass | ^4.1.0 excludes 5.x, and dependabot ignores semver-major, so no automatic update can roll it back. This is why the "no overrides" choice is right, not laziness |
| Coordination with Dependabot #41 | Pass | git merge-tree --write-tree pr41 <this> exits 0, and the merged tree holds 4.3.2 with the same resolved and integrity |
| New test really detects the flaw | Pass | See section 3 |
| CI | Pass | build-and-test, CodeQL, and both Analyze jobs are green on the head commit |
No missing requirement and no incorrect behaviour.
2. Quality Check: Pass
Design. The change is the smallest correct one. A lockfile pin, not an overrides block, is the right call: an override hides future upstream fixes and lies about the consumer contract. Keeping the fix isolated to one advisory commit, separate from the 20-package group in #41, is the right call for the same reason. Both rejections in "Deliberately not done" are correct and argued.
Test design is above average, and the reason matters. The budget probes use tiny budgets and sequences far below the 100-item cap. That isolates the CVE accounting from the unrelated guard that 4.3.2 also added. A naive test would assert "the PoC throws", which passes on 4.2.0 for the wrong reason. assertRejected reads the message instead of only the throw type, which closes that hole. The negative controls (exactly 10000 units parses, ordinary merge keeps its precedence) stop the suite from turning into a blanket ban. The repository corpus block gives npm test the same assurance the build job already gives, and names the failing file.
I confirmed the cost model myself, and the four numbers in the PR table are exact:
101-element merge sequence : parses on 4.3.0 and 4.3.1, throws `abnormal merge sequence size` on 4.3.2
2 empty sources at budget 1: parses on 4.3.0 and 4.3.1, throws `merge keys exceeded maxTotalMergeKeys (1)` on 4.3.2
100 x 100 with no options : parses on 4.3.2 (exactly the default 10000)
100 x 101 with no options : throws `merge keys exceeded maxTotalMergeKeys (10000)` on 4.3.2
Sensitivity is exactly as documented. I swapped each version into node_modules and re-ran the file: 4.1.1 fails 10 of 20, 4.2.0 fails 9, 4.3.0 fails 10, 4.3.1 fails 10, 4.3.2 passes 20 of 20. All five numbers match the header comment. The 4.3.1 row is the important one, and it is real.
Compatibility. 4.1.1 and 4.3.2 export the same 14 names, and the same single dependency argparse ^2.0.1. No engines field in either, so Node 20 is unaffected. I also checked the claim in the API test comment: 5.4.2 does drop safeLoad, safeDump, and safeLoadAll, so that test really would fail on a 5.x pin.
Runner and style. npm test gives 48 of 48 on Node 26.8.2 and on Node 20.20.2, which is the CI version. npx prettier --check tests/js-yaml-security.test.mjs is clean against the repo .prettierrc. Commit message follows conventional commits, and carries no agent co-author.
Security and performance. The whole file runs in about 60 ms on the pinned version. I also reproduced the vulnerable-version timing myself, on 4.1.1, with the same document shape: N = 800 parsed in 6.8 ms (13 KB), N = 3200 in 50.3 ms (53 KB), N = 20000 in 1744.3 ms (332 KB). So the guard is what keeps this file fast, and the file is a mild canary, not a slow suite. No new dependency, no network in tests, no secret handling.
3. Required changes
lockfileJsYamlEntries()matches a package that is not the dependency under test. The filter is/(^|\/)js-yaml$/, and that also matchesnode_modules/@types/js-yaml. I confirmed it: the regex returns true fornode_modules/@types/js-yaml. js-yaml 4.x ships no type declarations, and@types/js-yamlexists on the registry, so that devDependency is a realistic addition to this repo later. When it lands, two things break at once:entries.lengthbecomes 2 and the test fails with "a nested copy is a second resolved version that this pin does not cover", which is a false statement, andentry.versionthen compares the@typesversion against the installed runtime version. Anchor the pattern on the install path instead, for example/(^|\/)node_modules\/js-yaml$/. One line, and it keeps a guard test from producing a wrong diagnosis.
4. Optional, not blocking
DEFAULT_BUDGET = 10000is asserted but never read from the library. The 100 x 100 and 100 x 101 pair encodes both the accounting model and the shipped default. If upstream ever tunes the default, the failure message is "the pinned js-yaml rejected a document this site needs to parse", which points at the wrong thing. The thrown message already carries the number:merge keys exceeded maxTotalMergeKeys (10000). Read it and assert it equalsDEFAULT_BUDGET, then derive the two boundary documents from it.satisfiesCaret()hard-fails on any range that is not literally^X.Y.Z. A future~4.3.2or>= 4.1.0 < 5would stop the suite with "not a plain caret range" rather than report whether the pin satisfies it. Loud is acceptable in a guard test. Handling~and>=, and reporting anything else as unjudged, would be better.- The
entry.resolvedassertion hard-codeshttps://registry.npmjs.org. This repo has no.npmrc, so it is fine today. Match the/js-yaml-<version>.tgzsuffix, and the test survives a registry mirror. - The corpus block shells out to
git ls-files. A source tarball or any non-git checkout makes the whole file error inexecFileSync, with a message about git rather than about YAML. Wrap it and fall back to a directory walk. semverParts()andisAtOrAbove()are now duplicated across two test files.isAtOrAboveis the same logic in both, andsemverPartsdiffers only by the package name in its message. A third security file should move both intotests/helpers/. I checked that this is safe: on Node 20.20.2 and 26.8.2,node --test tests/runs only*.test.mjs, so a helper file is not collected as a test.- The test named
refuses the advisory PoC shapeloads with the default schema. The published PoC passes{ schema: YAML11_SCHEMA }. Loading with no options is the better test for this repo, because merge is on by default in v4 and no call site here passes a schema. Say that in the name or the comment, so the difference from the advisory does not read as an omission.
5. Accuracy of the PR text
The code and the tests are right. Two figures in the description are wrong, and one differs from the published advisory for a reason worth stating. Fix them before merge so the record is clean.
npx prettier --checkreports 40 tracked files outside this change, not 25. Undersrc/alone it is 29. The decision (do not reformat 40 unrelated files inside a security diff) still holds. It is the right call.- The "same 14 names" list enumerates 13.
safeLoadAllis missing from the prose. The count of 14 is correct. - The measured PoC row says ~313 KB and 1.8 s at N = 20000, where the advisory publishes ~500 KB and ~13 s. I ran it myself on 4.1.1 and got 332 KB and 1744 ms, which lands on the PR figure. The advisory size column is the loose one, and the published time is from different hardware. The PR table stands. Add one sentence saying the measurement is local, so a reader does not have to work that out.
6. Verdict
The dependency change is correct, minimal, reproducible from the registry, and complete for alert #48. It also clears #12, #16, and #29, whose ranges I verified all include 4.1.1. The test file is a real detection instrument, not decoration, and I reproduced its sensitivity on all five versions and its timing on 4.1.1. One latent over-broad matcher in that file is the only thing I am asking for before merge.
Fix section 3, correct the three figures in section 5, and this is good to merge.
SeriousJul
left a comment
There was a problem hiding this comment.
Required change, section 3 of the review. This filter also matches node_modules/@types/js-yaml. I checked:
$ node -e "const re=/(^|\/)js-yaml\$/; for (const k of ['node_modules/js-yaml','node_modules/@types/js-yaml','node_modules/foo/node_modules/js-yaml']) console.log(k, re.test(k)))"
node_modules/js-yaml true
node_modules/@types/js-yaml true
node_modules/foo/node_modules/js-yaml true
js-yaml 4.x ships no type declarations (ls node_modules/js-yaml has no .d.ts and no types field), and @types/js-yaml is on the registry at 4.0.9, so that devDependency is a realistic later addition here. When it lands, entries.length becomes 2 and the assertion fails with "a nested copy is a second resolved version that this pin does not cover", which is a false statement about a sibling types package. It also then compares the @types version against the installed runtime version.
Anchor the pattern on the install path. Nested real copies still match, and the types package drops out:
const entries = Object.entries(lock.packages).filter(([key]) =>
/(^|\/)node_modules\/js-yaml$/.test(key)
);| fs.readFileSync(path.join(repoRoot, 'package-lock.json'), 'utf8') | ||
| ); | ||
| const entries = Object.entries(lock.packages).filter(([key]) => | ||
| /(^|\/)js-yaml$/.test(key) |
There was a problem hiding this comment.
Required change (section 3 of the review). This filter also matches node_modules/@types/js-yaml. Verified locally:
$ node -e "const re=/(^|\/)js-yaml$/; console.log(re.test('node_modules/@types/js-yaml'))"
true
js-yaml 4.x ships no type declarations, and @types/js-yaml is on the registry (4.0.9), so that devDependency is a realistic later addition to this TypeScript repo. When it lands, entries.length becomes 2 and the test fails with "a nested copy is a second resolved version that this pin does not cover", which is a false diagnosis about a sibling types package. entry.version then compares the @types version against the installed runtime version too.
Anchor on the install path so real nested copies still match and the types package drops out:
/(^|\/)node_modules\/js-yaml$/.test(key)|
Review outcome note. The review above is a I applied the Summary of the verdict:
|
|
Clarification: I cannot delete a submitted review, so the short standalone comment above duplicates section 3. Its shell snippet has two typos (an extra |
Add tests/websocket-driver-security.test.mjs. It drives a real draft-75
upgrade through the installed driver's public Driver.server() API and
asserts observable behavior only, so it cannot report a security
regression because an internal field was renamed.
Seven suites, 15 leaf tests:
pin the version floor at 0.7.5, one flat lockfile copy, and the
ranges sockjs and faye-websocket declare
A a length header of 20, 60 and 200 continuation bytes is refused
B a valid frame that follows such a header never reaches the app
C two normal small text frames still arrive as hello and world
D a body of exactly the budget is served, one byte over is refused
E header, varint terminator and a valid frame in one segment
F the 0x3ffffff default sockjs actually gets, bracketed from both
sides: 4096 served, exactly-at-default served, one over refused
The guard fires inside the per-byte callback StreamReader.eachByte
drives, so the accumulator still overruns on a patched driver and the
connection is closed instead. The check asserts the close, not a small
number, which is the only defensible way to write it. An exception
escaping parse() counts as a failure, not as a rejection: a crash is the
parser losing control, and on 0.7.4 cases B and E do exactly that.
Measured on both versions by resolving each in a project tree and
re-running the file. 0.7.4: 9 of 15 fail, and no case that checks an
oversized header passes. 0.7.5: 15 of 15 pass. websocket-driver absent
from the tree: all 7 suites report skipped and the run exits 0, because
the package is transitive and a future webpack-dev-server or sockjs
upgrade can drop it. Node 20, which CI pins, agrees on all three.
The file is named *.test.mjs so `node --test tests/` discovers it, which
means the existing `npm test` step in ci-cd.yml runs it with no new
script and no CI edit. That keeps the js-yaml and shell-quote suites
added by #38 and #42 in place, and follows the harness they use.
Add tests/websocket-driver-security.test.mjs. It drives a real draft-75
upgrade through the installed driver's public Driver.server() API and
asserts observable behavior only, so it cannot report a security
regression because an internal field was renamed.
Seven suites, 15 leaf tests:
pin the version floor at 0.7.5, one flat lockfile copy, and the
ranges sockjs and faye-websocket declare
A a length header of 20, 60 and 200 continuation bytes is refused
B a valid frame that follows such a header never reaches the app
C two normal small text frames still arrive as hello and world
D a body of exactly the budget is served, one byte over is refused
E header, varint terminator and a valid frame in one segment
F the 0x3ffffff default sockjs actually gets, bracketed from both
sides: 4096 served, exactly-at-default served, one over refused
The guard fires inside the per-byte callback StreamReader.eachByte
drives, so the accumulator still overruns on a patched driver and the
connection is closed instead. The check asserts the close, not a small
number, which is the only defensible way to write it. An exception
escaping parse() counts as a failure, not as a rejection: a crash is the
parser losing control, and on 0.7.4 cases B and E do exactly that.
Measured on both versions by resolving each in a project tree and
re-running the file. 0.7.4: 9 of 15 fail, and no case that checks an
oversized header passes. 0.7.5: 15 of 15 pass. websocket-driver absent
from the tree: all 7 suites report skipped and the run exits 0, because
the package is transitive and a future webpack-dev-server or sockjs
upgrade can drop it. Node 20, which CI pins, agrees on all three.
The file is named *.test.mjs so `node --test tests/` discovers it, which
means the existing `npm test` step in ci-cd.yml runs it with no new
script and no CI edit. That keeps the js-yaml and shell-quote suites
added by #38 and #42 in place, and follows the harness they use.
#37) * deps(deps): bump websocket-driver from 0.7.4 to 0.7.5 Resolve Dependabot alert #13, CVE-2026-54466. The draft-75 and draft-76 frame parsers in websocket-driver accumulate a base-128 length header without bound. A client sends an unlimited run of bytes with the high bit set, the accumulator grows past Number.MAX_SAFE_INTEGER, loses precision, and later payload bytes are parsed against a wrong length. Message content is then corrupted or dropped. Version 0.7.5 rejects the message when the declared length passes the configured maximum message length. The same release adds a payload check in hybi.js _emitMessage, which clears alert #14 as well, CVE-2026-54490, resource limit bypass via message compression. Both advisories list 0.7.5 as the first patched version. websocket-driver is a transitive dependency. It is not named in package.json, so only package-lock.json changes. The path is: @docusaurus/core -> webpack-dev-server -> sockjs -> websocket-driver Both dependents already accept 0.7.5. sockjs asks for ^0.7.4 and faye-websocket asks for >=0.5.1, so the single hoisted lockfile entry moves and nothing else has to change. The whole diff is three lines: version, resolved and integrity. * test(deps): add a node:test regression check for CVE-2026-54466 Add tests/websocket-driver-security.test.mjs. It drives a real draft-75 upgrade through the installed driver's public Driver.server() API and asserts observable behavior only, so it cannot report a security regression because an internal field was renamed. Seven suites, 15 leaf tests: pin the version floor at 0.7.5, one flat lockfile copy, and the ranges sockjs and faye-websocket declare A a length header of 20, 60 and 200 continuation bytes is refused B a valid frame that follows such a header never reaches the app C two normal small text frames still arrive as hello and world D a body of exactly the budget is served, one byte over is refused E header, varint terminator and a valid frame in one segment F the 0x3ffffff default sockjs actually gets, bracketed from both sides: 4096 served, exactly-at-default served, one over refused The guard fires inside the per-byte callback StreamReader.eachByte drives, so the accumulator still overruns on a patched driver and the connection is closed instead. The check asserts the close, not a small number, which is the only defensible way to write it. An exception escaping parse() counts as a failure, not as a rejection: a crash is the parser losing control, and on 0.7.4 cases B and E do exactly that. Measured on both versions by resolving each in a project tree and re-running the file. 0.7.4: 9 of 15 fail, and no case that checks an oversized header passes. 0.7.5: 15 of 15 pass. websocket-driver absent from the tree: all 7 suites report skipped and the run exits 0, because the package is transitive and a future webpack-dev-server or sockjs upgrade can drop it. Node 20, which CI pins, agrees on all three. The file is named *.test.mjs so `node --test tests/` discovers it, which means the existing `npm test` step in ci-cd.yml runs it with no new script and no CI edit. That keeps the js-yaml and shell-quote suites added by #38 and #42 in place, and follows the harness they use.
Summary
Re-pin the transitive dependency
js-yamlfrom4.1.1to4.3.2, the first patched version for CVE-2026-84375. The change is lockfile-only:package.jsonis untouched, and the diff is onenode_modules/js-yamlblock.js-yamlpackage-lock.json4.1.14.3.2(npmv4-legacytag)>= 4.0.0, < 4.3.2The source item stays open. Closing a finding is a maintainer decision made after this merges.
The defect
maxTotalMergeKeyscharges one budget unit per folded key. A merge source that folds no keys, an empty{}mapping, costs real work to process but adds nothing to the counter. Alias one sequence of N empty mappings into K merge targets and the loader does O(N * K) work whiletotalMergeKeysnever moves, so no configured limit can stop it. 4.3.2 charges one unit per merge source on top of one per key.Why 4.3.2, and why nothing smaller or larger
@docusaurus/utils,@docusaurus/utils-validation,@docusaurus/plugin-content-docs,cosmiconfig,@11ty/gray-matter) require^4.1.0, which4.3.2satisfies. No manifest edit, nooverridesblock, no range rewrite.latestis5.4.2, but.github/dependabot.ymlignores semver-major bumps for*, 5.x is a different line with changed merge defaults, and4.3.2is the release upstream ships these fixes on.This also clears the three other open js-yaml alerts
4.1.1sits inside the vulnerable range of every one of them, so one pin resolves all four:>= 4.0.0, < 4.3.24.3.2!!omap>= 4.0.0, < 4.3.14.3.1>= 4.0.0, < 4.3.04.3.0>= 4.0.0, <= 4.1.14.2.0This PR targets only #48, the assigned item. The other three close as a side effect.
What 4.1.1 actually did, measured
Node 26.8.2, npm 11.19.1, one machine. The advisory PoC document, at three sizes:
Superlinear, as published. Two notes that did not come from the advisory text:
On 4.1.1 there is no guard to bypass, because there is no guard at all.
maxTotalMergeKeysarrived in 4.2.0 and became an option on the 4.x line in 4.3.0. 4.1.1 does not read it, soload(src, { maxTotalMergeKeys: 1000 })was accepted and ignored: measured at 1756.1 ms, no throw. This repo cannot mitigate the flaw by configuration; the dependency has to move.On 4.3.2 the PoC is stopped by a different check than the one this CVE is about. 4.3.2 also added a hard cap of 100 items per merge sequence, so a 20000-element alias dies with
abnormal merge sequence size, not with the budget message. Confirmed by probe, not just by release note: a 101-element merge sequence parses on 4.3.0 and 4.3.1 and is refused on 4.3.2. Upstream lists both halves of the release:The cap is why the tests below do not lean on the published PoC as their proof.
The proof of the fix, isolated from the cap
The cost model is readable directly, with budgets small enough to see one unit at a time and merge sequences far below any length cap. Measured by swapping each version into the project
node_modules:One unit per source plus one unit per folded key is the model. The last row is the negative control: the guard stops the hostile count at the documented default of 10000 and still permits exactly 10000 units, so it is not a blanket ban on merges.
Reachability: this is CI hygiene, not a live exposure on the site
Traced rather than assumed.
grep -ro "js-yaml" buildreturns 0 hits across the 38 generated pages@11ty/gray-matter/lib/engines.js:16,parse: yaml.load.bind(yaml)cosmiconfig/dist/loaders.js:50onyaml.load(content),@docusaurus/plugin-content-docssidebars,@docusaurus/utils/lib/dataFileUtils.js,@docusaurus/utils-validationtags file.github/workflows/ci-cd.ymlrunsbuildandtestonpull_request, so a fork PR can add an MDX file whose front matter reachesyaml.loadin GitHub ActionsThe CVSS 7.5 assumes a service parsing attacker-supplied YAML, which this site is not. What this repo does have is a fork-PR route that burns CI CPU, unbounded on 4.1.1. The value of the pin is that the route closes and
npm auditstops reporting js-yaml.Coordination with Dependabot PR #41
Dependabot's open group PR #41,
chore(deps): bump the npm_and_yarn group ... with 20 updates, also moves js-yaml4.1.1to4.3.2. This PR does not compete with it. Both sides write the identical block, so the three-way state matches line for line:The merged tree keeps
js-yaml@4.3.2in both merge orders, with the sameversion,resolvedandintegrity. Whichever lands first, the other stays clean, and #41 remains needed for the other 19 packages.Note that #41 is a group requirements update, so GitHub does not list it as a fix PR for alert #48: the alert's
dependency_trackisnull. Merging #41 does drop the resolved version out of the range, and the alert clears on the next scan either way. This PR exists to give the advisory its own reviewed, tested commit, and to unblock #48 from #41's 19 unrelated bumps.API compatibility
4.1.1 and 4.3.2 export the same 14 names (
load,loadAll,dump,safeLoad,safeDump,Schema,Type, the four schemas,types,YAMLException). Nothing the four consumers call,loadanddump, moved, so no migration was needed. Confirmed bynpm run build, not only by inspection.Verification
npm cifrom the committed lockfilejs-yaml@4.3.2, one flat copynpm run typechecknpm run build[SUCCESS] Generated static files in "build", 38 HTML pagesnpm testnpm auditjs-yaml entriesThe new file is
tests/js-yaml-security.test.mjs. Beyond the version floor it covers four things: the exact cost model above, that the bound holds with no caller-supplied options and through the real@11ty/gray-matterengine, that normal merge use keeps its documented precedence, and that every front matter block and every YAML file tracked in this repo still parses.Deliberately not done
overridesorresolutionsentry. Every dependent range already admits 4.3.2, so a lockfile pin is sufficient, and an override would hide future upstream fixes.npx prettier --checkreports 25 pre-existing files outside this change. The new file is Prettier-clean, and reformatting 25 unrelated files here would make a security diff unreviewable and conflict with chore(deps): bump the npm_and_yarn group across 1 directory with 20 updates #41.