Findings

What Credda found

1229 defects in 751 published packages, no bug report in hand, 1192 of them unreported. Each was run before it was published. Across 3 sweeps over 25,177 packages read; the three figures below are the 169-row ledger further down, which is the only sweep whose rows carry a disclosure outcome.

  • 132 of 169

    unreported: the finder is the only reason they are known

    bench/discovery-findings.json

  • 150 of 169

    checked against the then-current release on the date recorded beside each, not a historical commit

    bench/discovery-findings.json

  • 30 of 169

    carry a disclosure outcome below, deliberate non-reports included

    bench/discovery-findings.json

01How to read this

What the list counts, and what it does not.

A finding is a defect, not a file. The same undeclared specifier loaded from two hundred modules is one finding. Counted by flagged site instead, the sweeps that record sites come to 8,258 — the same defects, once per file that loads them. This page never publishes that number as a defect count.

7 caveats, shipped with the artifact
  1. EVERY ROW WAS SCORED AT TWO COMMITS. The finder ran at the commit where the defect is present and at the maintainer's fix. A finder that reports both has found the FILE, not the defect, and only the fixed side can tell those apart. Two manufactured findings were caught this way before they shipped and neither is in this file.
  2. `priorReport` SEPARATES THE TWO KINDS OF ROW. 'issue' means a human had already written the bug up and the finder reached it independently. 'none' means nobody had reported it and the finder is the only reason it is known. Both are real; only the second is a discovery, and conflating them would be the easiest lie in this file.
  3. `reproduce` RUNS. Each is a single expression a reviewer can paste to see the defect without trusting anything here.
  4. RECALL IS NOT IN THIS FILE and must not be inferred from its length. These are the finders' hits; the misses are recorded per detector, in each detector's own module header, with counts. On the advisory corpora measured 2026-09-04, prototype pollution was 4 of 15 advisories, ReDoS 1 of 15 plus one discovery, the same-file shapes 4 of 5; the sweeps of 2026-09-10 over roughly 4,600 installed packages added rows, not recall figures.
  5. EVERY ROW WAS RUN AND `verifiedByRunning` SAYS WHEN AND WHERE: the first 54 rows on 2026-09-04 against pinned checkouts, the rest on 2026-09-10 against the npm release named in `liveAsOf`. A reproduction reproduces at a VERSION; where `fixedUpstreamIn` is set, pasting the expression against the current release will correctly do nothing. One row was REMOVED at this step rather than published: semver's unguarded maxSatisfying, which the design study describes and which no checkout here can verify, because the only semver checkout is 7.8.2 where it returns null instead of throwing. It remains a fixture in the detector's tests, where it is labelled as one; it is not evidence.
  6. MOST ROWS ARE LIVE, and `fixedUpstreamIn` says which are not: 8 of 132 are fixed in a later release, 124 reproduce against the release that was current when checked. 13 have been reported to the maintainer and `reported` says where; the rest are unfiled, with drafts in upstream-drafts/ for the ones worth a maintainer's time. 28 are marked `isVulnerability` and those go through the private channel the package names, never a public issue. Checking whether a maintainer already closed it before writing to them is the difference between a useful report and wasting their time on their own closed bug.
  7. PROVENANCE IS RECORDED PER ROW AND THE TWO KINDS MUST NOT BE ADDED TOGETHER SILENTLY. Every row whose `detector` names a module in packages/repository was located by a deterministic finder. Rows with `detector: "model-hunt"` were located by a model reading the package with no report in hand, which is a different capability and is measured separately -- a finder's recall says nothing about a hunt's and the reverse. Both are Credda finding a defect nobody reported, which is why both belong in this file; conflating which one found what would be the second-easiest lie in it.

02The sweeps

3 populations, kept apart.

Each sweep dropped every hit on a package another had already listed, so no defect is counted twice and the sweeps are never merged into one list. Two of them were bounded by whatever happened to be on a disk; the third was not.

Every sweep1229 findings

Packages read is the population, not the hits. A dash is a figure the artifact does not record: the parent corpus walked trees that were already on the machine and counted neither its population nor its sites.

Each discovery sweep with its population, findings, packages and verification.
SweepPopulationPackages readFindingsUnreportedPackages hitFlagged sitesVerifier disagreed
bench/discovery-findings.jsonnode_modules trees already on this machine—169132160——
bench/discovery-findings-shard-scalar-2026-09-19.jsonnine scalar-app repositories' pnpm stores677555——
bench/discovery-findings-shard-registry-2026-09-20.jsonpackages installed deliberately from the npm registry24,500105510555888,258288
bench/discovery-findings-shard-registry-2026-09-20.json · hand-checked sample18 of 20 true on inspection

Both were found by sampling the shard's own output rather than by reasoning about the finders, and both were fixed by making the ADMISSION step stricter rather than by editing a finder -- so scan-installed.ts's published precision figures are untouched and the rule can be argued with in one place. A sample of twenty caught two classes; a shard of a thousand admitted on a finder's own sentence would have shipped both.

  • couchbase@4.7.1, STATEFUL_MATCHER_DIVERGENCE. dist/connspec.js:60 calls hostMatcher.exec inside a while loop. The regex is global and nothing resets lastIndex, both of which the finder and the first draft of the verifier read correctly -- and the advancing lastIndex is the POINT of an exec loop, which ends when exec returns null. The idiom, not the bug. EXEC_LOOP_IS_THE_IDIOM now refuses a .exec with a loop keyword within three lines above it. 9 findings lost, this one among them. The .test rows are untouched: repeated .test on DIFFERENT inputs silently skipping every other match, which is astro's and content-disposition's shape, has no idiomatic reading.
  • @scalar/themes@0.18.0, UNRESOLVABLE_DECLARATION. dist/index.d.ts imports './fonts/fonts.css?inline' and dist/fonts/fonts.css IS shipped. tsc really does fail on the specifier, so the reading is not wrong, but the package has not failed to ship a file -- the ?inline is Vite telling the bundler how to load it. BUNDLER_QUERY_SUFFIX now refuses a declaration import carrying a ?query. 14 findings lost, 13 of them this one package.

03The reachability gate

A gate between the finder and the record, and what it costs.

Three of this shard's eight discoveries were withdrawn on 2026-09-19 and all three failed the same way: a file was judged without resolving whether anything reaches it. neotraverse by entry point, mlly and vitest by call path. 'The bad line is in the shipped bundle' is a claim about bytes; a finding is a claim about behaviour. Two gates now run between a finder speaking and scripts/scan-installed.ts recording it, so this class cannot be admitted again.

What it refuses2026-09-19

Each rule names the finder class it gates and the module it is implemented in, so it can be read rather than believed.

Each reachability rule, the finder class it gates and where it is implemented.
RejectionGatesImplemented in
UNREACHABLE_AT_DECLARED_ENGINEENGINES_BELOW_DELIVERED_SYNTAXcore/packages/repository/src/entry-reachability.ts
NOT_REACHABLE_FROM_PUBLIC_SURFACEDISCARDED_PURE_RESULTcore/packages/repository/src/call-path-reachability.ts
  • 8 rejected in bench/discovery-findings-shard-scalar-2026-09-19.json. Stated hits fell from 89 to 81 over 847 packages, and no class the gate does not govern moved by a row.
  • 392 rejected in bench/discovery-findings-shard-registry-2026-09-20.json. Measured over 24,500 packages.
  • It rejects real defects, and here is how many. 2 of 7 in-scope cases lost every site to the gate and were kept anyway after hand adjudication: 13 of 30 flagged sites were rejected, and 0 cases were withdrawn. Nothing was loosened to fix it. Artifact bench/discovered-reachability-gate-2026-09-19.json.

04The ledger

169 with a disclosure record, under the package each was found in.

Chipped Not a vulnerability: a real defect, not a security issue.

@sentry/core

1 finding

cjs-binding-in-esmNo prior reportNot a vulnerability

CJS_BINDING_IN_DECLARED_ESM

loadModule, a public export, has `existingModule = module` as a default parameter in a build declared "type": "module". A default parameter is evaluated at call entry, so the ReferenceError is raised outside the function's own try/catch.

import { loadModule } from '@sentry/core'; loadModule('node:path') -> ReferenceError: module is not defined
Verified by running
Imported from a real .mjs on Node v22.23.2 with a control in the same file asserting that a bare `module` reference throws, so the scope is genuinely an ES module. Both the installed 10.68.0 and a freshly downloaded 10.73.0 behave identically.
Checked against
@sentry/core 10.73.0, the current release, published 2026-08-31. Checked 2026-09-04.
Reported
https://github.com/getsentry/sentry-javascript/issues/24117, filed 2026-09-04. Verified against the freshly downloaded 10.73.0 tarball with a control asserting the scope, and the open and closed issues were searched first.

lightningcss

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ">= 12.0.0" and the main entry uses logical assignment (??=), which is Node 15. On any version between the declared floor and 15, requiring the package is a SyntaxError.

node/index.js:45 is `res.dependencies ??= [];` and :51 the same. new Function('res','res.dependencies ??= [];') is a SyntaxError before Node 15. node/composeVisitors.js:83 additionally uses ?. which is Node 14.
Verified by running
The parse-level fact was checked directly, and the manifest and both source lines read from the published tarball.
Checked against
lightningcss 1.33.0, the current release, checked 2026-09-04
Reported
https://github.com/parcel-bundler/lightningcss/issues/1326, filed 2026-09-04. Verified against the freshly downloaded 1.33.0 tarball; the open and closed issues were searched first and none is this.

napi-postinstall

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ^12.20.0 || ^14.18.0 || >=16.0.0, and lib/index.js uses optional chaining, which is Node 14. The ^12.20.0 alternative admits a Node the shipped code cannot be parsed by.

lib/index.js:59 is `return process.env.npm_config_user_agent?.startsWith('npm/');` and lib/fallback.js:34 uses ?. as well. Optional chaining is Node 14.0; the ^12.20.0 branch of the range admits 12.20 through 12.x.
Verified by running
Manifest and both source lines read from the installed package; the range algebra was checked against the finder's own parser, which reads this range as a floor of 12.20.
Checked against
napi-postinstall 0.3.4, checked 2026-09-04
Not reported, on purpose
NOT REPORTED. Narrower than the lightningcss row: only one alternative of a three-way range is wrong, the Node 12 line is long dead, and the fix is deleting a branch of a range nobody uses. Real, and not worth a maintainer's attention -- the same judgement as the @babel/helper-* rows.

tinypool

1 finding

cjs-binding-in-esmNo prior reportNot a vulnerability

CJS_BINDING_IN_DECLARED_ESM

The package declares "type": "module" and its static `version` getter reads join(__dirname, '../package.json'). An ES module has no __dirname binding, so reading Tinypool.version throws.

import Tinypool from 'tinypool'; Tinypool.version -> ReferenceError: __dirname is not defined
Verified by running
Executed from a real .mjs on Node v22.23.2, with a control in the same file asserting that a bare __dirname reference throws -- so the scope is genuinely an ES module rather than something the harness leaked. Both the vendored 1.1.1 and the freshly downloaded 2.1.2 behave identically.
Checked against
tinypool 2.1.2, the current release, published 2026-08-23. Checked 2026-09-04.
Reported
https://github.com/tinylibs/tinypool/issues/139, filed 2026-09-04. Verified against the published 2.1.2 tarball and against src/index.ts:1256 on main, and the open and closed issues were searched for an existing report first -- five mention __dirname and none is this.

content-disposition

1 finding

stateful-matcherAlready reportedNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

A module-scope /g regex used as a validator carries lastIndex between calls, so the same call with identical arguments is accepted on every third attempt and rejected otherwise.

contentDisposition('file.txt', { fallback: 'a€b€' }) six times in one process: THREW, THREW, ACCEPTED, THREW, THREW, ACCEPTED. The accepting calls return attachment; filename="a€b€"; filename*=UTF-8''file.txt -- raw UTF-8 in a header the check exists to keep ISO-8859-1.
Verified by running
Executed six identical calls against 1.1.0 on Node v22.23.2 and read the alternating outcomes back, then the same against 3.0.0 and got six consistent rejections. The static finder was then run over both and reported 1 site and 0 sites respectively.
Checked against
content-disposition 1.1.0. NOT present in 3.0.0, the current release.
Fixed upstream in
content-disposition 3.0.0
Note
THE MOST VALUABLE ROW IN THIS FILE, and not because it is a discovery -- it is not. The maintainers found and fixed this themselves in #128, 'Fix stateful NON_LATIN1_REGEXP in createparams'. That makes it the strongest validation a finder here has had: an independent maintainer identified the same defect from the other side, and their repair is the oracle. The finder reports 1 site on 1.1.0 and 0 on 3.0.0, so it discriminates on a real pinned/fixed pair the ecosystem supplied rather than one we constructed. Not reported, because it is already known and already fixed.

prisma

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

package.json exports["."] resolves to ./build/types.js under require, import and default, and the package does not ship it.

require('prisma') -> MODULE_NOT_FOUND. build/ ships index.js, child.js, public/ and the wasm engines; there is no types.js.
Verified by running
require('prisma') executed on Node v22.23.2 and the MODULE_NOT_FOUND read back.
Checked against
prisma 6.19.3, checked 2026-09-04
Note
The package installs cleanly and its own tests pass; a consumer importing it exactly as the manifest instructs fails before a line of their code runs. Not reported: prisma's CLI is normally used through its bin rather than required, so the blast radius is narrower than the manifest suggests, and I would want to understand who actually imports it before spending a maintainer's attention.

@vitest/utils

2 findings

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports["./ast"] names ./dist/ast.js and ./dist/ast.d.ts, and dist/ contains neither.

import('@vitest/utils/ast') -> ERR_MODULE_NOT_FOUND
Verified by running
import('@vitest/utils/ast') executed and the error read back.
Checked against
@vitest/utils 3.2.7, checked 2026-09-04
Note
A whole declared subpath that cannot be imported. Not reported yet; it is the same package as the deepMerge row below and would go in one message if we file either.

prototype-pollutionNo prior reportNot a vulnerability

PROTOTYPE_POLLUTION

deepMerge, a published and typed export, copies a __proto__ key straight onto Object.prototype. Ordinary JSON reaching it mutates every object in the process.

deepMerge({}, JSON.parse('{"__proto__":{"polluted":"yes"}}')) -> ({}).polluted === 'yes'
Verified by running
The probe proved it during a sweep of 581 installed packages, and it was then reproduced standalone in four lines against the published release on Node v22.23.2.
Checked against
@vitest/utils 3.2.7, checked 2026-09-04
Note
NOT REPORTED AS A VULNERABILITY, and the oracle for that is vitest's own. Their published threat model says everything in vitest.config.*, the code it imports and every plugin is 'developer-authored and therefore trusted', and this merge is fed by exactly that. By the maintainers' written standard it is a bug and not a vulnerability, and our opinion does not overrule their threat model. It is a real defect either way: a merge that silently corrupts Object.prototype on ordinary JSON is wrong whoever authored the JSON. deepMerge is exported with types from both the package root and ./helpers, so a consumer can reach it with untrusted input, which is the lodash.merge precedent (CVE-2018-3721) -- but that is the consumer's choice and not vitest's threat model.

string.prototype.repeat

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports["./getPolyfill"] names ./getPolyfill.js and the package ships polyfill.js.

import('string.prototype.repeat/getPolyfill') -> ERR_MODULE_NOT_FOUND
Verified by running
The import executed and the error read back.
Checked against
checked 2026-09-04
Reported
https://github.com/mathiasbynens/String.prototype.repeat/issues/12, filed 2026-09-04 as a plain bug report. Verified against the published 1.0.0 tarball and against main before filing; no prior issue existed.

@babel/helper-validator-identifier

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports["."].types names ./lib/index.d.ts and lib/ holds only .js and .js.map. The same slip ships in helper-compilation-targets, helper-string-parser and helper-validator-option at the same version.

Listing lib/ shows index.js, identifier.js, keyword.js and their maps; no .d.ts of any name.
Verified by running
The finder reported it and the directory was then listed by hand for all four packages.
Checked against
@babel/helper-* 7.29.7, checked 2026-09-04
Not reported, on purpose
A DIFFERENT defect from the other three undelivered rows and the finder says so: the runtime entry beside it resolves, so the package loads perfectly and only TypeScript consumers break, with 'Could not find a declaration file for module'. Four sibling packages carry it, which reads like one generator emitting a types condition for declarations it does not produce. NOT REPORTED, decided 2026-09-04: 7.29.7 is the latest 7.x, but @babel/helper-validator-identifier 8.0.4 is `latest` on npm and DOES ship lib/index.d.ts, so the defect is fixed in the current major and a maintainer's answer would be to upgrade. A search also found an existing issue touching it. Real, live in the 7.x line, and not worth a maintainer's attention -- which is a different judgement from 'not real'.

miniflare

3 findings

unresolvable-declarationNo prior reportNot a vulnerability

UNRESOLVABLE_DECLARATION

dist/src/index.d.ts:25 imports './shared', and dist/src/ ships only index.d.ts, index.js, index.js.map and workers/.

tsc against a file importing miniflare: index.d.ts(25,36): error TS2307: Cannot find module './shared' or its corresponding type declarations.
Verified by running
tsc run against a consumer file, and the TS2307 read back from miniflare's own declaration file.
Checked against
miniflare 5.20260820.0-alpha, checked 2026-09-04
Note
Confirmed by the compiler rather than by our own resolution logic, which matters here: the first version of that logic reported 56 of 265 packages and 50 were its own bugs. An alpha release, which is the honest caveat.

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

dist/src/index.d.ts declares HyperdriveProxyController, NODE_PLATFORM_IMPL and POSTGRES_SSL_REQUEST_PACKET, none of which the package exports.

None of the three is present on the required module; the package exports 236 of its 239 declared values.
Verified by running
Probe proved it; each name then checked by hand against the required module and against its declaration line.
Checked against
miniflare 5.20260820.0-alpha, checked 2026-09-04
Note
An alpha release, which is the honest caveat: a declaration surface still moving is a weaker finding than the same shape in a stable one. Each of the three was confirmed individually rather than trusting the count. Not reported.

prototype-pollutionNo prior reportNot a vulnerability

PROTOTYPE_POLLUTION

mergeWorkerOptions, an exported function, copies a __proto__ key onto Object.prototype.

mergeWorkerOptions({}, JSON.parse('{"__proto__":{"mfPolluted":"yes"}}')) -> ({}).mfPolluted === 'yes'
Verified by running
Proved by the probe in the same sweep, then reproduced standalone against the published release.
Checked against
miniflare 5.20260820.0-alpha, checked 2026-09-04
Note
Same posture as the @vitest/utils row and reported the same way, which is to say not as a vulnerability. The options merged here come from wrangler configuration the developer wrote. Cloudflare has a formal disclosure route and workers-sdk has private reporting enabled, so the channel exists if the reachability picture ever changes.

@babel/types

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

lib/index-legacy.d.ts declares export function buildChildren, which the package does not export at runtime.

require('@babel/types').buildChildren is undefined among 1,356 runtime exports. A consumer writing t.buildChildren(node) typechecks and gets 'is not a function'.
Verified by running
The probe proved it, and it was then confirmed by hand: 'buildChildren' in the required module is false, and the declaration is at lib/index-legacy.d.ts:2698.
Checked against
@babel/types 7.29.8, checked 2026-09-04
Note
The types promise a value the runtime does not have, so the consumer's code typechecks, passes review and throws the first time it runs. Not reported: @babel/types is generated, and whether index-legacy.d.ts is still a supported surface is a question for its maintainers rather than an unambiguous defect.

terser

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

tools/terser.d.ts declares export enum InlineFunctions and export enum OutputQuoteStyle. A non-const enum is a runtime object; neither exists.

require('terser').InlineFunctions.simple_functions -> TypeError: Cannot read properties of undefined (reading 'simple_functions'). Control: Object.keys(require('terser')) is ['_default_options','_run_cli','minify'], so the module loaded and neither enum is among its exports.
Verified by running
Probe proved it; then executed by hand to capture the actual TypeError. Re-run 2026-09-10 on Node v22.14.0 against terser@5.16.9 installed fresh: Object.keys printed [ '_default_options', '_run_cli', 'minify' ] and the read threw TypeError: Cannot read properties of undefined (reading 'simple_functions'). npm's latest that day was 5.51.2, not checked.
Checked against
terser 5.16.9, checked 2026-09-04
Note
The clearest instance of this class found so far: a consumer reading the type definitions has every reason to use these enums, TypeScript accepts it, and it throws. Not yet reported. OBSERVATION REWRITTEN 2026-09-10 to expression -> rendering so the discovered corpus can admit it; the facts are the ones already recorded.

import-meta-resolve

1 finding

discarded-pure-resultNo prior reportNot a vulnerability

DISCARDED_PURE_RESULT

errors.js writes types.slice(pos, 1) where splice was meant, so the 'object' entry it tries to remove stays in the list and the error message names the type twice.

new codes.ERR_INVALID_ARG_TYPE('x', ['object','Date'], 1).message -> 'The "x" argument must be of type object or an instance of Date or Object. Received type number (1)'. The intended message is 'must be an instance of Date or Object'.
Verified by running
Executed the vendored formatter on Node v22.23.2 and read the wrong message back. Node's own source was then fetched and compared, which is what established the port as the origin.
Checked against
import-meta-resolve 4.2.0, the latest published release, and main. Re-verified 2026-09-04 against a fresh npm download.
Reported
https://github.com/wooorm/import-meta-resolve/issues/35, filed 2026-09-04 as a plain bug report. Not a security issue and not filed as one.

vite-node

1 finding

discarded-pure-resultNo prior reportNot a vulnerability

DISCARDED_PURE_RESULT

The published bundle contains two statements that do nothing -- retrieveFileHandlers.slice(0) and retrieveMapHandlers.slice(0) -- left behind when the bundler dropped the assignment they belonged to.

dist/source-map.cjs:897 and :898 are the statements retrieveFileHandlers.slice(0); and retrieveMapHandlers.slice(0);. Neither result is used and slice mutates nothing, so both lines have no effect. Upstream source-map-support writes 'var originalRetrieveFileHandlers = retrieveFileHandlers.slice(0)' and hands that copy to resetRetrieveHandlers; grepping the vendored bundle for a reset finds none.
Verified by running
The finder reported it; the file was then opened and the declaration, the dead statements and the correct clearing were all read directly, and the vendored bundle was grepped to confirm no reset function survives in it.
Checked against
vite-node 6.0.0, the current release, at dist/source-map-BFKsz_pY.mjs:668. Also in 3.2.4.
Not reported, on purpose
NOT REPORTED, AND THE FIRST VERSION OF THIS ROW WAS WRONG ABOUT WHY IT MATTERED. It claimed the missing reset meant handlers leak between runs. Checked before filing: the UPSTREAM SOURCE at antfu-collective/vite-node src/source-map-handler.ts:462 assigns the value correctly and defines resetRetrieveHandlers at :504. But resetRetrieveHandlers appears in that one file and nowhere else -- it is never called and never re-exported from the package entry -- so the bundler tree-shook it, and dropping the now-unused const binding while keeping the call is what left the bare statements. Nothing leaks, because nothing ever called the reset. What remains is true and trivial: two lines of harmless bundler residue in a shipped artifact, with no consequence for any consumer. Martin authorised filing this alongside the import-meta-resolve issue; it was not filed, because the authorisation rested on a consequence that turned out not to exist and a report is worth nothing if its headline claim is wrong.

semver

1 finding

sibling-guardNo prior reportNot a vulnerability

SIBLING_GUARD_DIVERGENCE

minVersion calls new Range(range) on a caller-supplied string while satisfies, maxSatisfying, minSatisfying and validRange in the same file wrap the identical call and answer with a value.

validRange(bad) is null and satisfies('1.0.0', bad) is false, but minVersion(bad) throws TypeError: Invalid comparator, for bad = 'some frogs and sneks-v2.5.6'
Verified by running
Found by the sibling-guard finder across 32,676 files of installed packages, then reproduced against semver 6.3.1 and 7.8.5 on Node v22.23.2.
Checked against
semver 7.8.5, the current release, checked 2026-09-04
Note
NOT REPORTED, deliberately. minVersion already returns null on its normal path to mean 'no version can match', so absorbing an invalid range into null would collide with an answer it already gives, and a maintainer can reasonably hold that throwing is correct. Impact was measured before deciding: 15 callers across the installed corpus, all build tooling reading a range out of a manifest, so the consequence is a raw TypeError where a clean message was wanted. Real divergence, arguable fix, not worth a maintainer's goodwill. See bench/stated-precision.json. Note this is NOT the maxSatisfying case that was removed from this file earlier for being unverifiable -- upstream fixed maxSatisfying; minVersion is still unguarded.

node-fetch

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

On a cross-host redirect node-fetch strips Authorization and Cookie but forwards Proxy-Authorization to the new host. Its confidential-header set is one header short.

fetch('http://localhost:A/s',{headers:{Authorization,Cookie,'Proxy-Authorization'}}) through a 302 to http://127.0.0.1:B/x -> target receives proxy-authorization
Verified by running
Ran against published node-fetch 2.7.0 and 3.3.2 on Node v22.23.2, and against the GHSA-r683-j2x4-v87g checkout at both its vulnerable and fixed commits. Authorization and Cookie stripped in every case; proxy-authorization forwarded in every case.
Checked against
node-fetch 2.7.0 and 3.3.2, checked 2026-09-04; re-run 2026-09-10 against both, identical.
Disclosed privately
Reported privately 2026-09-04 to jimmy@warting.se, the address node-fetch's SECURITY.md names. GitHub private vulnerability reporting is disabled on that repository, so email was the only stated private channel. Nothing published, no CVE requested, and the report says we will follow the maintainers' lead on whether it becomes public.

js-yaml

2 findings

declared-contractNo prior report

ROUND_TRIP_LOSS

yaml.dump(1e21) emits `1e+21`, which YAML 1.1 does not read as a number, so yaml.load hands back the string "1e+21". A number goes in and a string comes out.

const y=require('js-yaml'); typeof y.load(y.dump(1e21)) // 'string'
Verified by running
Ran at worktree 6030fa6c (the js-yaml-117 fix commit): typeof load(dump(1e21)) is "string", value "1e+21".
Fixed upstream in
5.4.1
Note
Found at the commit that FIXED a different defect in the same declaration (GHSA/issue 117, negative zero). The detector fires on both sides of that pair, which is normally the signature of a false positive; it was probed rather than dismissed and is real. Upstream fixed it later — js-yaml 5.4.1 emits `1.e+21` and round-trips as a number — so it was open at that commit and unreported.

declared-contractAlready reported

ROUND_TRIP_LOSS

The integer type's predicate admits -0 and its decimal representer renders it '0', so the round trip its own declaration promises does not hold.

require('js-yaml').dump(-0.0) // '0\n'
Verified by running
Ran at worktree e6bac155 (the js-yaml-117 pinned commit): dump(-0.0) returns "0\n".
Note
Reached without the report: across js-yaml's 148 files the detector measures 32 declarations and reports one, and it is this.

is-my-json-valid

1 finding

regex-blowupNo prior report

SUPERLINEAR_MATCH

The `style` format's pattern is cubic in its input on whitespace. 76ms at 1,000 characters, 539ms at 2,000, 4.3s at 4,000, 34s at 8,000 — a consistent 8x per doubling. An 8KB string of spaces stalls a JSON-schema validator for over half a minute.

const re=/\s*(.+?):\s*([^;]+);?/; console.time('t'); re.test(' '.repeat(8000)+'!'); console.timeEnd('t')
Verified by running
Ran standalone: 76ms / 539ms / 4.3s / 34s at 1k / 2k / 4k / 8k whitespace characters. Re-checked 2026-09-04 across published tarballs: present in 2.16.0, 2.17.2, 2.19.0, 2.20.0, 2.20.1, 2.20.2 and 2.20.3; absent from 2.20.4 onward.
Fixed upstream in
2.20.4
Note
That advisory fixed the `phone` format; `style` was never touched and the cubic pattern shipped in every release from 2.16.0 through 2.20.3. CHECKED 2026-09-04 against npm: it is gone in 2.20.4, which replaces the pattern with /.:\s*[^;]/g, and the current 2.20.6 matches in 0.0ms at every input size the old one took seconds on. So there is nothing to report: the maintainer fixed it without an advisory, and the finder reached it independently.

dot-prop

2 findings

weaker-walk-guardAlready reported

UNGUARDED_SIBLING

`has` tests a walked value only for undefined where `get` and `set` rule out null, so has({foo: null}, 'foo.bar') steps onto null and throws.

require('dot-prop').has({foo: null}, 'foo.bar') // throws
Verified by running
Ran at .bench-harvested-checkouts/dot-prop-27: throws "Cannot read properties of null (reading 'bar')".
Note
Fix commit 4467d364 adds an isObj guard to both `has` and `delete`; the detector reports exactly those two.

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

set(obj, '__proto__.x', 1) walks onto Object.prototype and writes there, leaving the property on every object in the process.

require('dot-prop').set({}, '__proto__.polluted', 1); ({}).polluted // 1
Verified by running
Ran at the GHSA-ff7x-qrg7-qggm vulnerable checkout: ({}).polluted === 1 afterwards.
Note
GHSA-ff7x-qrg7-qggm.

cron-parser

1 finding

round-trip-pairAlready reported

CROSS_FIELD_CONTAMINATION

Day-of-month is narrowed using the MONTH field, and stringify does not know it happened, so an expression does not survive being parsed and printed.

require('cron-parser').parseExpression('* * * 2 *').stringify() // '* * 1-29 2 *'
Verified by running
Ran at .bench-harvested-checkouts/cron-parser-239: returns "* * 1-29 2 *".
Note
The oracle is the project's own test asserting the round trip, and the inputs are the project's own test literals. 28 of them round-trip unchanged, 18 are rewritten as a function of their own field — ordinary normalisation — and exactly one is rewritten by a different field. That one is the defect.

glob-parent

1 finding

regex-blowupAlready reported

SUPERLINEAR_MATCH

A pattern in index.js takes 1.1s on 2,000 characters and 9.9s on 4,000 — worse than quadratic, and silent at the maintainer's fix.

node -e "const t=Date.now(); require('glob-parent')('['.repeat(4000)+'!'); console.log(Date.now()-t+'ms')"
Verified by running
Ran at the GHSA-ww39-953v-wcq6 vulnerable checkout: 8,432ms for 4,000 characters.
Note
GHSA-ww39-953v-wcq6.

ini

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

Decoding an ini file with a [__proto__] section writes into Object.prototype.

require('ini').decode('[__proto__]\npolluted=1'); ({}).polluted // '1'
Verified by running
Ran at the GHSA-qqgx-2p2h-9c37 vulnerable checkout: ({}).polluted === "1" afterwards.
Note
GHSA-qqgx-2p2h-9c37.

mixin-deep

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

Merging a JSON object carrying a __proto__ key writes into Object.prototype.

require('mixin-deep')({}, JSON.parse('{"__proto__":{"polluted":1}}')); ({}).polluted // 1
Verified by running
Ran at the GHSA-3mpr-hq3p-49h9 vulnerable checkout: ({}).polluted === 1 afterwards.
Note
GHSA-3mpr-hq3p-49h9.

set-value

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

set({}, '__proto__.x', 1) writes into Object.prototype.

require('set-value')({}, '__proto__.polluted', 1); ({}).polluted // 1
Verified by running
Ran at the GHSA-4g88-fppr-53pp vulnerable checkout: ({}).polluted === 1 afterwards.
Note
GHSA-4g88-fppr-53pp.

arraybuffer.prototype.slice

1 finding

model-huntNo prior reportNot a vulnerability

WRONG_BYTES_COPIED

implementation.js computes `first` and `newLen` per spec, and its own comment names the step that should use them -- `26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen)` -- then copies with the RAW `start`/`end` arguments one line below. Any call that is not two plain in-range integers returns a zeroed buffer instead of the data.

const impl=require('arraybuffer.prototype.slice/implementation'); const ab=new Uint8Array([1,2,3,4,5,6,7,8]).buffer; Array.from(new Uint8Array(impl.call(ab))) -> [0,0,0,0,0,0,0,0], native gives [1,2,3,4,5,6,7,8]
Verified by running
Ran the reproduction on Node v22.23.2 against the installed 1.0.4, with the native ArrayBuffer.prototype.slice in the same file as a control for every case. Read implementation.js to confirm the loop uses start/end while the comment above it names first/newLen.
Checked against
arraybuffer.prototype.slice 1.0.4, which is the current release. Checked against npm on 2026-09-05.
Note
Silent: no throw, no warning, a correctly sized buffer of zeros. `slice()`, `slice(4)`, `slice(-4)` and `slice(1.5, 5.5)` all return zeros; `slice(2, 6)` is correct, which is why ordinary use does not surface it. With `start` undefined the loop condition `undefined < undefined` is false and the loop never runs at all.

aria-query

1 finding

model-huntNo prior reportNot a vulnerability

KEY_EQUALITY_DISAGREES_WITH_STORAGE

lib/elementRoleMap.js stores entries with an equality that includes `constraints` and retrieves them with one that omits it, so four keys the map's own entries() produces are unreachable and get() returns a neighbouring entry's value.

const {elementRoles}=require('aria-query'); elementRoles.entries().filter(([k,v])=>elementRoles.get(k)!==v).length -> 4, and elementRoles.get({name:'td',constraints:['ancestor table element has grid role','ancestor table element has treegrid role']}) gives ['cell'] where entries() holds ['gridcell']
Verified by running
Ran the reproduction on Node v22.23.2 against the installed 5.3.2, iterating the map's own entries() so the expectation comes from the artifact rather than from a written list. Read both comparison functions to confirm the asymmetry.
Checked against
aria-query 5.3.2, which is the current release. Checked against npm on 2026-09-05.
Note
The two comparisons are in one file: the builder uses ariaRoleRelationConceptEquals (line 96), which ANDs name, constraints and attributes; get uses name and attributes only (line 73). has() returns true for the shadowed keys, so they are not absent, they are silently overridden. A <td> in a grid or treegrid resolves to cell instead of gridcell, and a <header>/<footer> scoped to <main> resolves to banner/contentinfo instead of generic.

dunder-proto

1 finding

model-huntNo prior reportNot a vulnerability

RETURN_CONTRADICTS_DECLARATION

set.js resolves to a bound Object.prototype.__proto__ setter on every modern runtime, so it returns undefined -- while its own set.d.ts declares `setDunderProto<P>(target, proto): P` and its own node-v0.10 fallback branch ends `return proto`.

const set=require('dunder-proto/set'); const proto={a:1}; const o={}; set(o, proto) === proto -> false (returns undefined), though Object.getPrototypeOf(o) === proto is true
Verified by running
Ran the reproduction on Node v22.23.2 against the installed 1.0.1, asserting both the return value and that the prototype was in fact set, so the finding is the return and not the mutation. Read set.js to confirm which branch is taken.
Checked against
dunder-proto 1.0.1, which is the current release. Checked against npm on 2026-09-05.
Note
The mutation itself is correct; only the return value is wrong. Two independent statements of intent disagree with the shipped behaviour: the .d.ts return type, and the fallback branch in the same module which explicitly returns proto. A caller writing `const p = set(o, proto)` gets undefined on Node and the declared P under typecheck.

electron-to-chromium

1 finding

model-huntNo prior reportNot a vulnerability

INHERITED_PROPERTY_READ_AS_DATA

The lookup tables are plain object literals read with bracket access and no own-property guard, so a query named after an Object.prototype member returns that member instead of undefined.

const e2c=require('electron-to-chromium'); e2c.chromiumVersions['constructor'] -> [Function: Object] rather than undefined; and a version string built from it reads 'Chrome >= function toString() { [native code] }'
Verified by running
Ran against the installed 1.5.412, then downloaded 1.5.416 with npm pack and ran the same probe against the extracted tarball, because the installed copy was four releases behind and a stale reproduction is not evidence about the current release.
Checked against
electron-to-chromium 1.5.412 as installed, and re-verified against 1.5.416, the current release, by downloading it fresh on 2026-09-05.
Note
Reachable wherever a version or query string reaches these tables from configuration or user input -- browserslist queries are ordinary user data. The failure is not a throw but a function object flowing into a string, which then formats as source text.

merge

1 finding

prototype-pollutionNo prior reportNot a vulnerability

PROTOTYPE_REPLACED_ON_RECEIVER

`merge.clone()` converts an own `__proto__` data property into a prototype mutation. The clone loses the property and inherits from the input instead, so it is not a faithful copy and its prototype is attacker-controlled. The sibling `merge()` was fixed for the same shape in 2.1.1; `clone` was not.

const merge=require('merge'); const input=JSON.parse('{"__proto__":{"pwned":1},"keep":2}'); Object.getOwnPropertyNames(input) -> ['__proto__','keep'] but Object.getOwnPropertyNames(merge.clone(input)) -> ['keep'], and Object.getPrototypeOf(merge.clone(input)) !== Object.prototype with clone.pwned === 1
Verified by running
Ran the reproduction on Node v22.23.2 against merge@2.1.1 downloaded fresh with npm pack, asserting three things separately: the input's own property list, the clone's own property list, and the clone's prototype identity. Also asserted Object.prototype stayed clean, so the finding is the receiver and not global pollution.
Checked against
merge 2.1.1, which is the CURRENT release (npm view merge version). Checked 2026-09-06.
Note
Object.prototype is NOT polluted -- only the returned object is re-parented, which is why a check watching the global prototype sees nothing. Found by the second observable added to prototype-pollution on 2026-09-05, on the FIXED side of GHSA-7wpw-2hjm-89gp: the probe fires at 2.1.1 because the fix covered merge() and not clone(). Confirmed by hand that merge() itself is clean at 2.1.1 and does not replace the target's prototype.

axios

1 finding

declared-method-missingAlready reportedNot a vulnerability

DECLARED_METHOD_WITHOUT_RUNTIME

index.d.ts declares setContentEncoding, getContentEncoding and hasContentEncoding on the AxiosHeaders class. The runtime registers accessors only for Content-Type, Content-Length, Accept, Accept-Encoding, User-Agent and Authorization, so all three methods are undefined. A consumer writing headers.setContentEncoding('gzip') typechecks and gets a TypeError the first time the line runs.

const {AxiosHeaders}=require('axios'); const h=new AxiosHeaders(); console.log('control setContentType:',typeof h.setContentType); h.setContentEncoding('gzip') -> control prints 'function', then TypeError: h.setContentEncoding is not a function
Verified by running
Ran the reproduction on Node v22.23.2 against a freshly installed 1.20.0. The control in the same expression asserts setContentType IS a function, so the class was reached and constructed correctly and the absence is specific to the three Content-Encoding methods rather than to the object. 29 of the 32 methods index.d.ts declares on AxiosHeaders are present.
Checked against
axios 1.20.0, the current release. Checked 2026-09-08.
Note
NOT A DISCOVERY, and the prior report is what makes it worth keeping. PR #6696 ('fix(TypeScript): Fix AxiosHeaders types', 2024-11-09) describes this exact defect in its own words -- the methods 'declared in the types and documented in the README did not actually exist at runtime and would throw an error'. The PR was CLOSED WITHOUT MERGING, so the defect is still live at 1.20.0 while a correct description of it has sat in the tracker for nearly two years. The finder reached it from the source alone. Root cause is one line: AxiosHeaders.accessor([...]) at dist/node/axios.cjs:1479 omits 'Content-Encoding' from a list whose other five entries all have working methods.

fast-xml-parser

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

src/fxp.d.ts -- the types the package's own exports map serves for the `import` condition -- declares `export class Expression` and `export class MatcherView`, both with constructors and methods. src/fxp.js, the runtime behind the same condition, exports only XMLParser, XMLValidator and XMLBuilder. Both classes are undefined at runtime.

node --input-type=module -e "import * as fxp from 'fast-xml-parser'; console.log('control exports:',Object.keys(fxp).join(',')); new fxp.Expression('a')" -> control prints 'XMLBuilder,XMLParser,XMLValidator', then TypeError: fxp.Expression is not a constructor
Verified by running
Ran the reproduction on Node v22.23.2 against a freshly installed 5.11.1. The control lists the three names that DO survive, so the module loaded: the package kept 3 of the 5 values its .d.ts declares. Also read src/fxp.js, whose entire export list is XMLParser, XMLValidator, XMLBuilder, and src/fxp.d.ts lines 54 and 85 where the two classes are declared.
Checked against
fast-xml-parser 5.11.1, the current release. Checked 2026-09-08.
Note
A DISCOVERY. Searched the project's open and closed issues before writing 'none': `MatcherView` returns zero hits anywhere in the repository, and nothing describes the declared-but-unexported classes. The nearest neighbour, issue #801 (missing types for the path-expression matcher), is most likely the change that ADDED these declarations without the runtime export to match. MatcherView is the type fxp's own docs say every user callback receives when jPath is false, so the declaration is not vestigial -- it describes an API the package means to have.

validator

2 findings

regex-blowupAlready reportedNot a vulnerability

ANCHOR_PRECEDENCE

isISO6346 tests /^[A-Z]{3}(U[0-9]{7})|([J,Z][0-9]{6,7})$/. Alternation binds looser than the anchors, so the pattern is (^[A-Z]{3}(U[0-9]{7})) OR (([J,Z][0-9]{6,7})$): the first branch has no end anchor and the second no start anchor. A container id with arbitrary text appended is accepted.

const v=require('validator'); v.isISO6346('AAAU0000000ZZZjunk') -> true; the control v.isISO6346('NOTACONTAINERID') is false and v.isISO6346('CSQU3054383') is true
Verified by running
Ran the reproduction against a freshly installed 13.15.35 on Node v22.23.2, with two controls in the same file: a well-formed id (true) and an obvious non-id (false), so the function is reached and is not simply returning true. Read lib/isISO6346.js to confirm the pattern. The same defect is present in all three delivered builds -- lib/, es/ and the validator.js bundle -- which is why the finder emitted it three times.
Checked against
validator 13.15.35, the current release. Checked 2026-09-08.
Note
NOT A DISCOVERY, and the tracker is instructive. Issue #2772 plus at least six open pull requests -- #2771, #2773, #2797, #2855, #2857, #2877 -- all describe this one regex, several of them titled for anchoring it and dropping the stray comma in [J,Z]. None is merged. That volume, and the 2026 dates, say other automated hunters have already piled onto this package, which is exactly the check that stops a finder's hit being mistaken for a discovery. The stray comma in the character class is a second, separate defect on the same line and is not what this row claims.

regex-blowupAlready reportedNot a vulnerability

ANCHOR_PRECEDENCE

isLicensePlate's pt-BR validator tests /^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/. Same unanchored alternation: the Mercosul branch has no end anchor and the legacy branch no start anchor, so a plate with junk appended OR prepended is accepted, depending on which branch matches.

const v=require('validator'); [v.isLicensePlate('ABC1D23junk','pt-BR'), v.isLicensePlate('junkABC1234','pt-BR')] -> [true, true]; the control v.isLicensePlate('zzz','pt-BR') is false and v.isLicensePlate('ABC1D23','pt-BR') is true
Verified by running
Ran the reproduction against a freshly installed 13.15.35 on Node v22.23.2, with a well-formed Mercosul plate and an obvious non-plate as controls in the same file. Read lib/isLicensePlate.js to confirm the pattern.
Checked against
validator 13.15.35, the current release. Checked 2026-09-08.
Note
NOT A DISCOVERY. PR #2782, 'fix(isLicensePlate): anchor pt-BR alternation to reject partial strings' (2026-06-23), is open and unmerged. Kept as a separate row from isISO6346 because it is a different function, a different regex and a different upstream report -- the two share only the shape the finder looks for, and collapsing them would overstate what one hit proves. It is also the stronger of the two witnesses: junk is accepted at BOTH ends here, one end per branch.

ramda

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

package.json's exports map declares "./dist": "./dist/index.js". The published tarball's dist/ contains only ramda.js and ramda.min.js -- there is no index.js -- so the one subpath the package names explicitly is the one that cannot be loaded, while the wildcard "./dist/*" beside it resolves fine.

require('ramda/dist') -> Error: Cannot find module '<...>/node_modules/ramda/dist/index.js'; the controls require('ramda') and require('ramda/dist/ramda') both load
Verified by running
Ran the reproduction on Node v22.23.2 against a freshly installed 0.32.0. Two controls in the same file: the package's main entry loads, and the sibling subpath ramda/dist/ramda -- served by the './dist/*' wildcard in the same exports object -- loads. So the failure is the map's own './dist' key and not a broken install. Listed the tarball's dist/ directly to confirm index.js is absent.
Checked against
ramda 0.32.0, the current release. Checked 2026-09-08.
Note
A DISCOVERY, with the weakest 'none' in this file and it is labelled that way deliberately. The searchable strings for it are generic -- 'exports', 'dist', 'MODULE_NOT_FOUND' -- so a report phrased differently could have been missed, and the confidence here is medium-high rather than high. What was checked: ramda's open exports-map issues (#3236, #3281, #3298) are all about the deprecated FOLDER mappings './src/' and './es/', not about the broken './dist' subpath. The two sit in the same object, which is presumably why nobody separated them.

lodash.set

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

set() walks a caller-supplied path without excluding __proto__, so a path arriving as a string, as an array, or as an array containing a boxed String writes through to Object.prototype, where the property is visible to every object in the process.

const set=require('lodash.set'); set({}, ['__proto__','polluted'], 'yes'); ({}).polluted -> yes
Verified by running
Ran all three path spellings against a freshly installed 4.3.2 on Node v22.23.2, deleting the planted key between cases and asserting Object.prototype was clean before the run. A control path 'a.b' in the same file leaves Object.prototype untouched, so the pollution is caused by the path and not by calling the function.
Checked against
lodash.set 4.3.2, which is still the latest published version -- the package has had no release since 2016. Checked 2026-09-08.
Note
NOT A DISCOVERY, and the row exists to be the finder reaching a PUBLISHED ADVISORY from the source alone. CVE-2020-8203 lists lodash.set >= 3.7.0 <= 4.3.2 with first_patched_version null: the fix landed only in the monolithic lodash 4.17.19 and was never backported to the split package, so 4.3.2 is simultaneously the newest and the vulnerable version. The three path spellings all pollute -- string, plain array, and array with a boxed String -- which matters because a guard that only rejects the literal string '__proto__' misses the last one.

defaults-deep

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

defaultsDeep() recurses into a source object without excluding the constructor/prototype chain, so ordinary JSON of the shape {"constructor":{"prototype":{...}}} writes onto Object.prototype.

const dd=require('defaults-deep'); dd({}, JSON.parse('{"constructor":{"prototype":{"polluted":1}}}')); ({}).polluted -> 1; the control dd({}, {a:1}) leaves Object.prototype clean
Verified by running
Ran both payload shapes against a freshly installed 0.2.4 on Node v22.23.2, asserting Object.prototype was clean before the run and deleting the planted key between cases. The __proto__ payload does NOT pollute and the constructor payload does, so the row names the route that actually works rather than the class in general. A control dd({}, {a:1}) leaves Object.prototype untouched.
Checked against
defaults-deep 0.2.4, which is still the latest published version. The package is abandoned. Checked 2026-09-08.
Note
NOT A DISCOVERY, and the two advisories on this package are worth keeping apart. CVE-2018-3723 covers < 0.2.4 and WAS fixed by 0.2.4; CVE-2018-16486 then covers <= 0.2.4 with first_patched_version null -- the constructor.prototype route this row reproduces is the bypass of that fix, and nothing has been published since. The __proto__ route is closed: the probe measured it in the same run and Object.prototype stayed clean, which is the 0.2.4 fix still holding. Only the constructor route gets through.

dotenv

1 finding

model-huntNo prior reportNot a vulnerability

SILENT_KEY_LOSS

parse() silently discards a `__proto__` key. The line is read, no error is raised, and the key is simply absent from the returned object -- so a variable in a .env file vanishes with nothing to tell the reader it did.

const de=require('dotenv'); const p=de.parse('__proto__=EVIL\nSAFE=ok'); Object.keys(p) -> ["SAFE"]; Object.prototype.hasOwnProperty.call(p,'__proto__') -> false. The control key SAFE survives, so the loss is specific to __proto__ and not a parse failure.
Verified by running
Re-run 2026-09-10 on Node v22.14.0 against dotenv@17.4.2 installed fresh with --ignore-scripts: Object.keys(parsed) printed [ 'SAFE' ], hasOwnProperty.call(parsed,'__proto__') printed false, and Object.getPrototypeOf(parsed) === Object.prototype printed true, so the key is dropped and nothing leaks. First recorded by a model reading the source; this is the execution.
Checked against
dotenv 17.4.2, the latest published version. Checked 2026-09-09.
Note
NOT A POLLUTION BUG, and calling it one would overstate it. Object.getPrototypeOf(p) is still Object.prototype and nothing leaks onto it -- the assignment lands on the __proto__ ACCESSOR and is thrown away. The defect is the silence: dotenv's own contract is that every parsed line becomes a key. Searched motdotla/dotenv for `proto`, `prototype` and silent key loss; the nearest issue is #1043, which is the fast parser diverging on VALUES, not a key being dropped. Found by a model reading the source with no report in hand, then verified by execution with a control. OBSERVATION REWRITTEN 2026-09-10 to the exact rendering the witness prints (JSON.stringify), after executing it; the words are unchanged and moved after the value, because the discovered-corpus needle is the leading token and prose there compares nothing.

papaparse

1 finding

model-huntAlready reportedNot a vulnerability

SILENT_KEY_LOSS

parse() with header:true announces a `__proto__` column in meta.fields but omits it from every row, so the parsed data disagrees with the header list the same call returned.

const Papa=require('papaparse'); const out=Papa.parse('a,__proto__\n1,2',{header:true}); out.meta.fields -> ['a','__proto__'] while Object.keys(out.data[0]) -> ['a']. The control column `a` is present in both.
Verified by running
Checked against
papaparse 5.7.0, the latest published version. Checked 2026-09-09.
Note
An independent re-find of open issue #992, 'Accept any header name (inc. __proto__)'. Recorded as a re-find rather than a discovery because the tracker was checked before the label was written. The interesting half is the DISAGREEMENT WITHIN ONE RETURN VALUE: meta.fields and data are produced by the same call and do not match, so a caller iterating meta.fields reads undefined for a column the library told it exists.

deepmerge

1 finding

model-huntAlready reportedNot a vulnerability

CALLER_STATE_MUTATED

deepmerge() writes arrayMerge, isMergeableObject and cloneUnlessOtherwiseSpecified onto the caller's own options object, so an object the caller still holds gains three properties it never set.

const dm=require('deepmerge'); const opts={}; dm({a:1},{b:2},opts); Object.keys(opts) -> ['arrayMerge','isMergeableObject','cloneUnlessOtherwiseSpecified'] (was []). Also reproduces when the caller supplies a real option.
Verified by running
Checked against
deepmerge 4.3.1, the latest published version. Checked 2026-09-09.
Note
REPORTED, FIXED, AND STILL LIVE -- which is why it is worth a row. PRs #167 ('Stops mutating options object') and #173 ('Avoid mutating options') are both MERGED, and the behaviour is present in 4.3.1, the current release. Either the fix regressed or those changes addressed a different mutation path. Labelled `issue` rather than `none` because the behaviour was plainly reported; a reader should not be told nobody had noticed.

ini-parser

1 finding

model-huntAlready reported

PROTOTYPE_POLLUTION

parse() writes an ini [__proto__] section straight onto Object.prototype, so any file the caller parses can add a property that appears on every object in the process.

const ip=require('ini-parser'); ip.parse('[__proto__]\npolluted=yes\n'); ({}).polluted -> 'yes' and Object.prototype.polluted is set. A section with any other name leaves Object.prototype clean.
Verified by running
Checked against
ini-parser 0.0.2, the latest published version, and STILL UNPATCHED. GHSA-96r7-mrqf-jhcc says 'all versions vulnerable' with first_patched_version null. Checked 2026-09-09.
Note
NOT A DISCOVERY -- a critical advisory has existed since 2020-06-10 and the tracker was checked before this label was written. It is here because it is LIVE: the advisory has no fix, 0.0.2 is still the latest release, and the finder reached it with no report in hand. Verified by execution, with a control section proving the pollution is specific to [__proto__].

psl

1 finding

model-huntAlready reportedNot a vulnerability

WRONG_PARSE_RESULT

parse() returns only the last subdomain label for a host under a TLD that is not in the public suffix list, so a.b.example.<unlisted> reports subdomain 'b' and the outer labels are dropped.

const psl=require('psl'); psl.parse('a.b.example.notarealtld').subdomain -> 'b' (should be 'a.b'). Control: psl.parse('a.b.example.com').subdomain -> 'a.b', correct, so the loss is specific to unlisted suffixes.
Verified by running
Checked against
psl as published at 2026-09-09.
Note
LABELLED AS A RE-FIND ON PURPOSE, and not confidently. Open issue #23 ('Domain contains subdomain') reports incorrect subdomain results but is a screenshot from 2018 against 1.1.23, and it cannot be established from it whether the unlisted-suffix path is the same defect. Recorded as `issue` because claiming a discovery that turns out to be #23 is the worse error of the two.

traverse

1 finding

model-huntNo prior reportNot a vulnerability

SILENT_STATE_CORRUPTION

set() with an empty path array writes a property literally named 'undefined' onto the object instead of replacing the root, so the call silently corrupts the target rather than failing.

const tr=require('traverse'); const o={x:1}; tr(o).set([], 99); Object.keys(o) -> ["x","undefined"]. No error is raised; the caller's object gains a junk key.
Verified by running
Re-run 2026-09-10 on Node v22.14.0 against traverse@0.6.11 installed fresh: after tr(o).set([], 99), Object.keys(o) printed [ 'x', 'undefined' ] and nothing threw.
Checked against
traverse 0.6.11, current at 2026-09-09.
Note
Searched ljharb/js-traverse for `set` in issue titles; the only open match is #10, about builtin objects with internal slots, which is a different path. An empty path is the natural way to express 'the root', so the API invites the call that corrupts. OBSERVATION REWRITTEN 2026-09-10 to the exact rendering the witness prints (JSON.stringify), after executing it; the words are unchanged and moved after the value, because the discovered-corpus needle is the leading token and prose there compares nothing.

node-persist

1 finding

model-huntNo prior reportNot a vulnerability

SHARED_MUTABLE_STATE

create() hands every instance the SAME options object rather than a copy, so configuring one store rewrites the configuration of every other store in the process.

const np=require('node-persist'); const a=np.create(); const b=np.create(); a.options === b.options -> true. Mutating a.options is therefore visible through b.
Verified by running
Re-run 2026-09-10 on Node v22.14.0 against node-persist@4.0.4 installed fresh: a.options === b.options printed true for two stores from create().
Checked against
node-persist 4.0.4, current at 2026-09-10 (was recorded without a version on 2026-09-09).
Note
Searched simonlast/node-persist for shared-options reports and found none. The library's own API offers create() precisely so a process can hold more than one store, which is the case this defect breaks.

getopts

1 finding

model-huntNo prior reportNot a vulnerability

SILENT_TYPE_COERCION

Command-line values that merely look numeric are coerced to Number, so hex and exponent forms change value and integers past 2^53 lose precision -- an id passed as 9007199254740993 arrives as 9007199254740992.

const g=require('getopts'); g(['--n','0x10']).n -> 16. A number, not the string '0x10'; g(['--n','1e3']).n -> 1000; g(['--n','9007199254740993']).n -> 9007199254740992. A caller reading an identifier off the command line silently gets a different one.
Verified by running
Re-run 2026-09-10 on Node v22.14.0 against getopts@2.3.0 installed fresh: the three reads printed 16, 1000 and 9007199254740992 in that order.
Checked against
getopts 2.3.0, current at 2026-09-09.
Note
Searched jorgebucaran/getopts for numeric and number issues. The nearest open item is #60, prototype-chain corruption via --__proto__, which is a different defect and not this one. The precision case is the sharp end: a value that survives every round trip as a string is destroyed by being parsed as a CLI argument. OBSERVATION REWRITTEN 2026-09-10 to the exact rendering the witness prints (JSON.stringify), after executing it; the words are unchanged and moved after the value, because the discovered-corpus needle is the leading token and prose there compares nothing.

bytes

1 finding

model-huntNo prior reportNot a vulnerability

SILENT_INPUT_TRUNCATION

parse() accepts trailing garbage after a number and silently discards it, INCLUDING the unit -- so '100kbXYZ' returns 100 bytes rather than 102400 or an error.

const b=require('bytes'); b.parse('100abc') -> 100; b.parse('100kbXYZ') -> 100 (NOT 102400). Controls: b.parse('abc') -> null, correctly rejected, and b.parse('100kb') -> 102400, correct. So the parser can reject, and does not here.
Verified by running
Re-run 2026-09-10 on Node v22.14.0 against bytes@3.1.2 installed fresh: parse('100kb') 102400, parse('abc') null, parse('100abc') 100, parse('100kbXYZ') 100, so the parser rejects where it can and does not here.
Checked against
bytes 3.1.2, current at 2026-09-09.
Note
The unit being dropped is what makes this more than pedantry: a limit written '100kbXYZ' in a config does not fail loudly, it becomes 100 BYTES -- a thousandfold tighter than intended, in a library used for body-size limits. Searched visionmedia/bytes.js; open issues are performance (#67) and TypeScript definitions (#68).

clone

1 finding

model-huntAlready reportedNot a vulnerability

LOSSY_CLONE

clone() of a RegExp drops the sticky, unicode and dotAll flags, so the copy matches differently from the original.

const cl=require('clone'); cl(/a/y).flags -> '' ; cl(/b/u).flags -> '' ; cl(/d/s).flags -> ''. Control: cl(/c/gi).flags -> 'gi', preserved. A cloned /a/y is no longer sticky, so a caller's matching loop changes behaviour.
Verified by running
Checked against
clone 2.1.2, current at 2026-09-09, with the behaviour still present.
Note
REPORTED, CLOSED, STILL LIVE. #22 ('RegExp properties aren't cloned') and #23 ('Clone RegExp flags and set lastIndex if needed') are both closed, and 2.1.2 still drops y, u and s. Labelled `issue` rather than `none` because it was plainly reported; the point of the row is that closing a report did not change the package.

cuid

1 finding

model-huntAlready reportedNot a vulnerability

DEFECTIVE_VALIDATOR

isCuid() returns true for any string beginning with 'c', so 'cat' and even 'c' validate as identifiers.

const cuid=require('cuid'); cuid.isCuid('c') -> true; cuid.isCuid('cat') -> true; cuid.isCuid('c123') -> true. Controls: cuid.isCuid('x123') -> false and cuid.isCuid('banana') -> false, so the check reads the first character and nothing else.
Verified by running
Checked against
cuid 3.0.0, current at 2026-09-09. The package is also deprecated in favour of @paralleldrive/cuid2.
Note
REPORTED, CLOSED, STILL LIVE. #296 ('isCuid incorrectly validates non-CUID strings in version 2.2.2') is closed and 3.0.0 behaves identically. A function whose only job is validation accepting 'cat' is worth a row even deprecated, because deprecation does not stop it being installed.

diacritics

1 finding

model-huntNo prior reportNot a vulnerability

WRONG_PARSE_RESULT

remove() rewrites the non-breaking space U+00A0 into an ordinary space U+0020, changing whitespace that carries no diacritic.

const d=require('diacritics'); [...d.remove('a\u00a0b')].map(c=>c.codePointAt(0).toString(16)) -> ["61","20","62"]. The U+00A0 became U+0020. Control: d.remove('caf\u00e9') -> cafe, which is the function doing its actual job.
Verified by running
Re-run 2026-09-10 on Node v22.14.0 against diacritics@1.3.0 installed fresh: the code points printed [ '61', '20', '62' ] and remove('caf\u00e9') printed cafe. As one statement per line; the expression as first written did not parse because an array literal on the next line reads as an index on the require.
Checked against
diacritics 1.3.0, current at 2026-09-09.
Note
Minor, and recorded as such. U+00A0 is not an accented character and the map should not carry it; a caller normalising accents does not expect their non-breaking spaces to collapse. Searched andrewrk/node-diacritics and found nothing on it. OBSERVATION REWRITTEN 2026-09-10 to the exact rendering the witness prints (JSON.stringify), after executing it; the words are unchanged and moved after the value, because the discovered-corpus needle is the leading token and prose there compares nothing.

figlet

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports["./browser"].require.default names ./dist/figlet.js, and the published tarball does not contain that file. require('figlet/browser') fails with MODULE_NOT_FOUND before a line of consumer code runs.

require('figlet/browser') -> MODULE_NOT_FOUND; the module Node names is dist/figlet.js, and the control require('figlet') resolves
Verified by running
The 1.11.4 tarball was fetched with npm pack, extracted into a bare node_modules, and require.resolve('figlet/browser') executed on Node v22.23.2 with a control in the same program asserting that require.resolve('figlet') succeeds -- so the failure is the subpath and not the install. The control printed ok and the subpath printed MODULE_NOT_FOUND naming dist/figlet.js.
Checked against
figlet 1.11.4, the current release (dist-tags.latest on 2026-09-09). Checked 2026-09-09.
Reported
https://github.com/patorjk/figlet.js/issues/178, filed 2026-09-09. Open as of 2026-09-10.

simple-get

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

On a cross-host redirect simple-get deletes cookie and authorization and forwards proxy-authorization to the new host. The strip list at index.js:59-62 is the fix for CVE-2022-0355 and it is one header short.

const http = require('node:http'); const get = require('simple-get'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { return get.concat({ url: 'http://localhost:' + this.address().port + '/start', headers: { authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' } }, () => (this.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : 'the second host received proxy-authorization=' + seen['proxy-authorization'] + ' while authorization=' + seen.authorization + ' and cookie=' + seen.cookie))) }))) -> the second host received proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Ran on Node v22.23.2 against a freshly installed simple-get@4.0.1. The expression is the whole experiment: it stands up two local http servers, the first answering 302 to the second on a textually different hostname (localhost -> 127.0.0.1), which is the pair simple-get's own `redirectHost !== originalHost` test treats as cross-host. TWO CONTROLS ARE IN THE RENDERED STRING ITSELF: `authorization=undefined and cookie=undefined` says the cross-host branch demonstrably ran, so the surviving header is not a redirect that never happened, and a run where the second server is never reached renders 'the redirect never reached the second host' instead of a leak. proxy-authorization arrived intact. FIXED-SIDE CONTROL: adding `delete opts.headers['proxy-authorization']` beside the two deletes already there turns the same witness to NOT_REPRODUCED.
Checked against
simple-get 4.0.1, the current release (dist-tags.latest on 2026-09-09). Checked 2026-09-09.
Disclosed privately
Not reported. Filing a third-party report is Martin's call, and a security-class report to a maintainer is the case where that matters most.

streamx

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['./errors'].default is './lib/errors', with no extension. Node's exports resolution does not try extensions, so require('streamx/errors') fails MODULE_NOT_FOUND while lib/errors.js sits right there on disk. The types condition beside it names lib/errors.d.ts, which does resolve -- so TypeScript accepts the import and Node cannot load it.

require('streamx/errors') -> MODULE_NOT_FOUND: Cannot find module -- and the module it names is streamx/lib/errors, which ships; require('streamx') resolves
Verified by running
Node v22.23.2 against a freshly installed streamx@2.28.1. require('streamx/errors') threw MODULE_NOT_FOUND naming .../streamx/lib/errors; the control require('streamx') resolved to index.js and returned a module whose Readable is a function; fs.existsSync on lib/errors.js printed true, so the file ships and only the specifier is wrong. tsc 5.x with module/moduleResolution node16 was reported to accept `import * as errors from 'streamx/errors'`; RE-CHECKED 2026-09-10 with tsc 5.9.3 and that form is refused with TS2497 because lib/errors.d.ts ends `export = StreamError`. The default-import form, `import errors from 'streamx/errors'` with esModuleInterop, compiles clean and exits 0, which is the claim that stands: the types resolve and the runtime does not. FIXED-SIDE CONTROL: changing the exports entry to './lib/errors.js' -- three characters -- turns the same witness to NOT_REPRODUCED.
Checked against
streamx 2.28.1, the current release (dist-tags.latest on 2026-09-09). Checked 2026-09-09.
Note
INTRODUCED BY THE CURRENT RELEASE, which is why nothing has reported it: the './errors' subpath does not exist in the exports map of 2.22.0 or 2.16.1 and appears for the first time in 2.28.1, so no consumer has had time to depend on it. One character is missing. THE TYPES HALF IS WHY IT IS WORTH RECORDING RATHER THAN SHRUGGING AT: `import * as errors from 'streamx/errors'` compiles clean under moduleResolution node16, because the types condition points at a file that exists while default points at one that does not, so the compiler cannot warn about the thing that will fail. streamx is a dependency of tar-fs, tar-stream and much of the prebuild toolchain, so the subpath is reachable from a large tree. NOT REPORTED UPSTREAM: mafintosh/streamx has no issue naming this subpath or the exports map.

readdir-glob

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

One declaration file serves both conditions. dist/index.d.mts declares the named export readdirGlob, and the CJS build dist/index.js -- which exports['.'].require.types points at that same .d.mts for -- does not have it. require('readdir-glob').readdirGlob is undefined.

const {readdirGlob} = require('readdir-glob'); console.log('control ReaddirGlob:', typeof require('readdir-glob').ReaddirGlob); readdirGlob('.', {pattern: '**/*.js'}) -> TypeError: readdirGlob is not a function -- and the control line above prints 'function', so the module loaded and only this export is missing
Verified by running
Node v22.23.2 against a freshly installed readdir-glob@3.0.0. Object.keys of the CJS module is ['ReaddirGlob']; the same keys under the ESM build are ['ReaddirGlob','default','readdirGlob'], so the divergence is between the two builds and not a bad install. tsc 5.x with moduleResolution bundler accepted `import {readdirGlob} from 'readdir-glob'` and exited 0, and under node16 it failed with TS1479. FIXED-SIDE CONTROL: attaching readdirGlob to the CJS module.exports turns the same witness to NOT_REPRODUCED.
Checked against
readdir-glob 3.0.0, the current release (dist-tags.latest on 2026-09-09). Checked 2026-09-09.
Note
THE THIRD CASE OF THIS CLASS HERE, after axios and @vitest/utils, and the same root cause each time: a declaration file that describes one build being served for two. The ESM build does export readdirGlob and a default; the CJS build exports only ReaddirGlob, hanging off a callable module.exports. It was introduced by PR #29, 'Migrate to tsdown and add default export in esm module', which added the ESM named export and pointed both conditions' types at the .mts. WHERE IT BITES, stated narrowly: under moduleResolution bundler `import {readdirGlob} from 'readdir-glob'` compiles clean and is undefined in any CJS output; under node16 the compiler instead emits TS1479 claiming the referenced file is an ECMAScript module that cannot be required, which is false -- the package ships a real CJS build -- so the declaration misleads in both directions rather than in one. NOT REPORTED UPSTREAM: none of the 30 issues and PRs on Yqnn/node-readdir-glob names it.

typed-array-byte-length

1 finding

undeclared-dependencyNo prior reportNot a vulnerability

UNDECLARED_DEPENDENCY

index.js:9 does `require('available-typed-arrays')` at module scope and the manifest does not list it in dependencies -- only call-bind, for-each, gopd, has-proto and is-typed-array. Under any resolver that enforces declared dependencies the very first require of the package throws MODULE_NOT_FOUND.

(() => { const fs = require('node:fs'), p = require('node:path'); const at = require.resolve('typed-array-byte-length/package.json'); const m = JSON.parse(fs.readFileSync(at, 'utf8')); const src = fs.readFileSync(p.join(p.dirname(at), 'index.js'), 'utf8'); const declared = Object.keys(m.dependencies || {}); const undeclared = [...src.matchAll(/require\('([^'.][^']*)'\)/g)].map((x) => x[1]).filter((r) => !declared.includes(r)); return undeclared.length === 0 ? 'every require in index.js is declared' : 'index.js requires ' + undeclared.join(', ') + ' and dependencies declares only ' + declared.join(', '); })() -> index.js requires available-typed-arrays and dependencies declares only call-bind, for-each, gopd, has-proto, is-typed-array
Verified by running
Node v22.23.2. THREE INSTALLS, all against a freshly downloaded 1.0.3. (1) npm flat install -> LOADED, byteLength(new Uint8Array(4))=4; available-typed-arrays was present in node_modules as a transitive of which-typed-array. (2) pnpm default -> LOADED; resolution went through node_modules/.pnpm/available-typed-arrays@1.0.7. (3) Yarn 4 PnP -> THREW MODULE_NOT_FOUND with Yarn's own wording, 'typed-array-byte-length tried to access available-typed-arrays, but it isn't declared in its dependencies'. (4) pnpm with hoist=false and node-linker=isolated -> THREW MODULE_NOT_FOUND: Cannot find module 'available-typed-arrays'. That fourth layout is the witness because it is both strict and writable. CONTROL: require('typed-array-byte-length/package.json') resolved in the same process, so the failure is the inner require and not an uninstalled package. FIXED-SIDE CONTROL: linking available-typed-arrays@1.0.7 into the package's own node_modules -- what declaring the dependency does -- turned the same witness to LOADED, byteLength=4, and removing the link again turned it back to MODULE_NOT_FOUND.
Checked against
typed-array-byte-length 1.0.3, the current release (dist-tags.latest on 2026-09-09), and inspect-js/typed-array-byte-length main at the same version. Checked 2026-09-09.
Disclosed privately
Not reported. Filing a third-party report is Martin's call.

filename-reserved-regex

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports.types names ./index.d.ts and no such file exists -- not in the tarball, whose files field is ["index.js"], and not in the repository either. Every TypeScript consumer resolving through the exports map gets TS7016 and an implicit any for a package that declares it ships types.

(() => { const fs = require('node:fs'), p = require('node:path'); let dir = process.cwd(), at = null; for (let up = 0; up < 12 && at === null; up += 1) { const c = p.join(dir, 'node_modules', 'filename-reserved-regex', 'package.json'); if (fs.existsSync(c)) at = c; const parent = p.dirname(dir); if (parent === dir) break; dir = parent; } if (at === null) return 'filename-reserved-regex is not installed beside this file'; const e = JSON.parse(fs.readFileSync(at, 'utf8')).exports; const d = p.dirname(at); return 'exports.types ' + e.types + ' exists=' + fs.existsSync(p.join(d, e.types)) + ' while exports.default ' + e.default + ' exists=' + fs.existsSync(p.join(d, e.default)); })() -> exports.types ./index.d.ts exists=false while exports.default ./index.js exists=true
Verified by running
TypeScript 5.9.2, tsconfig module=node16 moduleResolution=node16 strict=true, against a freshly installed filename-reserved-regex@4.0.1 and filenamify@6.0.0. Published bytes: tsc exited 2 with TS7016 naming index.js. CONTROL IN THE SAME COMPILATION: the filenamify import on the line above raised nothing, so the failure is this package and not the tsconfig. ls of the installed package printed index.js, license, package.json, readme.md -- no index.d.ts. FIXED-SIDE CONTROL: writing the two-line index.d.ts the exports map names turned tsc to exit 0; deleting it again turned it back to exit 2.
Checked against
filename-reserved-regex 4.0.1, the current release (dist-tags.latest on 2026-09-09), and sindresorhus/filename-reserved-regex main, whose file listing has no index.d.ts at all. Checked 2026-09-09.
Disclosed privately
Not reported. Filing a third-party report is Martin's call.

superagent

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

lib/utils.js cleanHeader deletes authorization and cookie when the redirect changes origin -- the branch is commented '// secuirty' -- and forwards proxy-authorization to the new host.

await (async () => { const http = require('node:http'); const request = require('superagent'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); return await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { const first = this; return request.get('http://localhost:' + first.address().port + '/start').set({ authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' }).redirects(3).end(() => (first.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : seen.authorization !== undefined || seen.cookie !== undefined ? 'the cross-origin strip branch did not run' : 'the second host received proxy-authorization=' + seen['proxy-authorization'] + ' while authorization=' + seen.authorization + ' and cookie=' + seen.cookie))); }))); })() -> the second host received proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Node v22.23.2 against a freshly installed superagent@10.3.0. TWO CONTROLS ARE IN THE RENDERED VERDICT ITSELF: 'authorization=undefined, cookie=undefined' says the changesOrigin branch demonstrably ran, so the surviving header is not a redirect that never happened; and a run where the second server is never reached renders 'NOTHING MEASURED - the redirect never reached the second host' instead of a leak. The whole witness is guarded so that only the finding can be reported: a throw inside the driver is caught and rendered as 'NOTHING MEASURED - the probe itself failed'. proxy-authorization arrived intact. FIXED-SIDE CONTROL: adding `delete header['proxy-authorization'];` beside the two deletes already in cleanHeader rendered proxy-authorization=undefined; restoring the published file rendered 'Basic P' again.
Checked against
superagent 10.3.0, the current release (dist-tags.latest on 2026-09-09). Checked 2026-09-09.
Disclosed privately
Not reported. Filing a third-party report is Martin's call, and a security-class report to a maintainer is the case where that matters most.

make-fetch-happen

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

lib/fetch.js deletes authorization and cookie when the redirect's hostname changes and forwards proxy-authorization to the new host. This is npm's own fetch wrapper, used by the CLI's registry client.

await (async () => { const http = require('node:http'); const fetch = require('make-fetch-happen'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); return await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { const first = this; const done = () => (first.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : seen.authorization !== undefined || seen.cookie !== undefined ? 'the cross-hostname strip branch did not run' : 'the second host received proxy-authorization=' + seen['proxy-authorization'] + ' while authorization=' + seen.authorization + ' and cookie=' + seen.cookie)); return fetch('http://localhost:' + first.address().port + '/start', { headers: { authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' }, cache: 'no-store', retry: false }).then(done, done); }))); })() -> the second host received proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Node v22.23.2 against a freshly installed make-fetch-happen@16.0.1, in the same guarded witness as the superagent row and with the same two controls rendered into the verdict: 'authorization=undefined, cookie=undefined' proves the cross-hostname branch ran, and an unreached second server renders 'NOTHING MEASURED' rather than a leak. proxy-authorization arrived intact. FIXED-SIDE CONTROL: adding `request.headers.delete('proxy-authorization')` beside the two deletes already there rendered proxy-authorization=undefined; restoring the published file rendered 'Basic P' again.
Checked against
make-fetch-happen 16.0.1, the current release (dist-tags.latest on 2026-09-09). Checked 2026-09-09.
Disclosed privately
Not reported. Filing a third-party report is Martin's call, and a security-class report to a maintainer is the case where that matters most.

tapable

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

tapable.d.ts:160 declares `export class TypedHookMap`, a value, and lib/index.js does not export it. Every other class in that declaration file has a runtime export beside it; this one is the exception, so `new TypedHookMap(factory)` typechecks against tapable's own types and throws TypeError at runtime.

(() => { const t = require('tapable'); const control = typeof t.HookMap; try { new t.TypedHookMap(() => new t.SyncHook(['a'])); return 'control HookMap=' + control + ' and TypedHookMap constructed'; } catch (e) { return 'control HookMap=' + control + ' while new TypedHookMap() threw ' + e.constructor.name + ': ' + e.message; } })() -> control HookMap=function while new TypedHookMap() threw TypeError: t.TypedHookMap is not a constructor
Verified by running
Node v22.23.2 and TypeScript 5.9.2 against a freshly installed tapable@2.3.3. THE CONTROL IS IN THE RENDERED STRING: `control HookMap=function` says the module loaded and its neighbouring class -- declared four lines above TypedHookMap in the same file -- is a real runtime export, so the failure is this one name and not a bad install. Object.keys of the CJS module lists twelve hooks plus HookMap and MultiHook, and TypedHookMap is not among them. THE TYPE HALF WAS COMPILED, NOT ASSUMED: a tsconfig with module=commonjs, moduleResolution=node10, strict=true accepted `new TypedHookMap<{x: SyncHook<[string]>}>(() => new SyncHook(['a']))` and exited 0, on the same line-for-line file whose runtime run throws. FIXED-SIDE CONTROL: appending `module.exports.TypedHookMap = require('./HookMap')` to lib/index.js turned the witness to 'constructed'; removing it again turned it back to the TypeError.
Checked against
tapable 2.3.3, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Disclosed privately
Not reported. Filing a third-party report is Martin's call.

logform

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

index.d.ts declares `export class Format` and `export class Colorizer` from the package root; index.js exports only `format` and `levels`. Both classes exist at runtime one level down, on logform/colorize, and neither is re-exported from the root the declaration describes.

(() => { const l = require('logform'); const control = typeof l.format; try { new l.Format({}); return 'control format=' + control + ' and Format constructed'; } catch (e) { return 'control format=' + control + ' while new Format() threw ' + e.constructor.name + ': ' + e.message; } })() -> control format=function while new Format() threw TypeError: l.Format is not a constructor
Verified by running
Node v22.23.2 and TypeScript 5.9.2 against a freshly installed logform@2.7.0. THE CONTROL IS IN THE RENDERED STRING: `control format=function` says the module loaded and the export the README documents is present, so the failure is these names and not a bad install. Object.keys of the module is ['format','levels']; typeof Format and typeof Colorizer are both 'undefined'. THE TYPE HALF WAS COMPILED: the same tsconfig that accepted the tapable line accepted `new Format({})` and exited 0. FIXED-SIDE CONTROL: appending `exports.Format = exports.Colorizer = require('./colorize').Colorizer` to index.js turned the witness to 'constructed' with a `transform` of type function -- the real class, reached from the root; removing it again turned it back to the TypeError.
Checked against
logform 2.7.0, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Disclosed privately
Not reported. Filing a third-party report is Martin's call.

minipass-fetch

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

lib/index.js:219-225 deletes authorization and cookie when the redirect's hostname changes and forwards proxy-authorization to the new host. This is the fetch underneath make-fetch-happen, so it is the second layer of npm's own registry client with the same omission.

fetch('http://localhost:A/start',{headers:{authorization,cookie,'proxy-authorization'}}) through a 302 to http://127.0.0.1:B/next -> the second host receives proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Node v22.14.0 against minipass-fetch@6.0.0 installed fresh with --ignore-scripts, in a two-server witness: the first server on localhost answers 302 to the second on 127.0.0.1, the request carries authorization, cookie and proxy-authorization, and the second server printed proxy-authorization=Basic P, authorization=undefined, cookie=undefined. The two undefineds are the control that the hostname-change branch ran. FIXED-SIDE CONTROL: adding requestOpts.headers.delete('proxy-authorization') beside the two deletes at lib/index.js:223-224 printed undefined for all three; restoring the published file printed Basic P again. First surfaced by scripts/probe-installed.ts on the 5.0.2 that make-fetch-happen installs, then confirmed on 6.0.0 as above.
Checked against
minipass-fetch 6.0.0, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10. Also 5.0.2, the version make-fetch-happen@16.0.1 installs.
Note
THE FIFTH INSTANCE OF THIS CLASS IN THIS FILE, and the first found by the recipe HOW-TO-IMPROVE-NEXT names rather than by reading: the three package-level probes were run over the current releases of every package with a pollution, traversal or redirect advisory, plus the HTTP clients and extractors beside them (271 packages, 5 proven, 266 clean, 557 abstained, 8 timed out). Four of the five were already in this file; this one was not. It sits directly under make-fetch-happen, so npm's registry client carries the omission at two layers. THE FAIR ARGUMENT AGAINST is the one the other four rows carry and it was checked here: minipass-fetch has no proxy option and never sets the header itself (the only 'proxy' in lib/ is a comment about piping a stream), so it is present only when a caller set it by hand. NOT REPORTED UPSTREAM: npm/minipass-fetch has no issue mentioning proxy-authorization or a redirect; it carries a SECURITY.md, so the channel is npm's security route rather than a public issue.

popsicle-redirects

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

dist/index.js:45-49, safeRedirect: when the redirect leaves the original origin it deletes cookie and authorization and forwards proxy-authorization. Reached through popsicle, whose default Node transport stacks this middleware.

popsicle.fetch('http://localhost:A/start',{headers:{authorization,cookie,'proxy-authorization'}}) through a 302 to http://127.0.0.1:B/next -> the second host receives proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Node v22.14.0 against popsicle@12.1.2 installed fresh with --ignore-scripts, in the two-server witness: the first server on localhost answers 302 to the second on 127.0.0.1; the second printed proxy-authorization=Basic P authorization=undefined cookie=undefined, the two undefineds being the control that the cross-origin branch ran. FIXED-SIDE CONTROL: adding newRequest.headers.delete("proxy-authorization") beside the two deletes in popsicle-redirects/dist/index.js printed none of the three; restoring the published file printed Basic P again.
Checked against
popsicle-redirects 1.1.1, the current release, as installed by popsicle 12.1.2, the current release. Checked 2026-09-10.
Note
THE SIXTH INSTANCE OF THIS CLASS IN THIS FILE and the sixth library to have written the confidential-header branch and left this header out of it. Found 2026-09-10 evening by the second probe sweep: the three package-level probes over 534 installed packages (more HTTP clients, extractors, deep mergers and config parsers beside the advisory set), 15 proven, 567 clean, 1081 abstained, 20 timed out. Then re-installed fresh at the current release, on its own, and probed again. The comment above the branch says 'Delete cookie header when leaving the original URL', so the intent is exactly the one the other five rows record. The hop-by-hop caveat applies unchanged: popsicle has no proxy option of its own, so the header is present only when a caller set it. NOT REPORTED UPSTREAM: serviejs/popsicle has no issue naming proxy-authorization or a redirect header.

fast-deep-merge

1 finding

prototype-pollutionNo prior reportNot a vulnerability

PROTOTYPE_POLLUTION

deepmergeMutate, a published export, walks a __proto__ key of its source into target['__proto__'], which is Object.prototype, and then writes the payload's keys onto it. deepmerge, the non-mutating export, does not.

node --input-type=module -e "import { deepmergeMutate } from 'fast-deep-merge'; deepmergeMutate({}, JSON.parse('{\"__proto__\":{\"polluted\":\"yes\"}}')); console.log(({}).polluted)" -> yes
Verified by running
Node v22.14.0 against fast-deep-merge@1.0.0 installed fresh with --ignore-scripts, from an ES module: after deepmerge({}, payload) ({}).polluted printed undefined; after deepmergeMutate({}, payload) it printed yes. First surfaced by probe-installed.ts as 3 of 198 probed calls, first deepmergeMutate.
Checked against
fast-deep-merge 1.0.0, the current release. Checked 2026-09-10.
Note
Found 2026-09-10 evening by the second probe sweep: the three package-level probes over 534 installed packages (more HTTP clients, extractors, deep mergers and config parsers beside the advisory set), 15 proven, 567 clean, 1081 abstained, 20 timed out. Then re-installed fresh at the current release, on its own, and probed again. index.js:120-132: for (const key in source) with an own-property check, then const targetVal = target[key]; for key '__proto__' that read returns Object.prototype, isObject is true for both sides, and deepmergeMutate(Object.prototype, payload) writes the keys globally. The non-mutating deepmerge builds a fresh result object and does not reach the global prototype, which was checked in the same run (({}).polluted stayed undefined after it). A small package, and recorded as such; the shape is the lodash.merge one (CVE-2018-3721). The package is ESM-only, so the discovered-corpus witness cannot require it; the reproduce field is the shell form on purpose. NOT REPORTED UPSTREAM: opensly/fast-deep-merge has no issue mentioning prototype or __proto__.

gaxios

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

gaxios 8.0.0 forwards Proxy-Authorization across a cross-host redirect. DERIVED: it delegates the request to node-fetch 3.3.2 (build/esm/src/gaxios.js:524, await import('node-fetch')) and passes maxRedirects through as follow, so the omission is node-fetch's and this row exists because Google's client inherits it.

gaxios.request({url:'http://localhost:A/start',headers:{authorization,cookie,'proxy-authorization'}}) through a 302 to http://127.0.0.1:B/next -> the second host receives proxy-authorization=Basic P
Verified by running
Node v22.14.0 against gaxios@8.0.0 installed fresh, two-server witness through gaxios.request: the second host received proxy-authorization=Basic P and neither authorization nor cookie.
Checked against
gaxios 8.0.0, the current release, with node-fetch 3.3.2 underneath. Checked 2026-09-10.
Note
A DERIVED INSTANCE, recorded so the class's reach is visible and not as a seventh independent omission: the branch that strips two headers and forwards the third is node-fetch's, one dependency down. Found 2026-09-10 evening by the second probe sweep: the three package-level probes over 534 installed packages (more HTTP clients, extractors, deep mergers and config parsers beside the advisory set), 15 proven, 567 clean, 1081 abstained, 20 timed out. Then re-installed fresh at the current release, on its own, and probed again. The fix is upstream in node-fetch; a gaxios report would be a heads-up. NOT REPORTED UPSTREAM. No draft written.

isomorphic-fetch

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

isomorphic-fetch 3.0.0 forwards Proxy-Authorization across a cross-host redirect. DERIVED: fetch-npm-node.js is four lines that call node-fetch 2.7.0, so this is node-fetch's omission under another name.

fetch('http://localhost:A/start',{headers:{authorization,cookie,'proxy-authorization'}}) through a 302 to http://127.0.0.1:B/next -> the second host receives proxy-authorization=Basic P
Verified by running
Node v22.14.0 against isomorphic-fetch@3.0.0 installed fresh, two-server witness: the second host received proxy-authorization=Basic P and neither authorization nor cookie.
Checked against
isomorphic-fetch 3.0.0, the current release, with node-fetch 2.7.0 underneath. Checked 2026-09-10.
Note
A DERIVED INSTANCE, like the gaxios row. Found 2026-09-10 evening by the second probe sweep: the three package-level probes over 534 installed packages (more HTTP clients, extractors, deep mergers and config parsers beside the advisory set), 15 proven, 567 clean, 1081 abstained, 20 timed out. Then re-installed fresh at the current release, on its own, and probed again. NOT REPORTED UPSTREAM. No draft written.

deep-set

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

deep-set 1.0.1, the current release, still lets a __proto__ path write onto Object.prototype. GHSA-wgxm-rg53-h2c6 (2022) names it and no fixed version exists.

const deepSet=require('deep-set'); deepSet({}, '__proto__.polluted', 'yes'); ({}).polluted -> yes
Verified by running
probe-installed.ts against deep-set@1.0.1 installed fresh: PROVEN, 1 of 22 probed calls, first <default> fn({}, "__proto__...").
Checked against
deep-set 1.0.1, the current release. Checked 2026-09-10.
Reported
Prior: GHSA-wgxm-rg53-h2c6, published 2022-05-24, unfixed.

lodash.setwith

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

lodash.setwith 4.3.2, same shape and same advisory as lodash.set.

const setWith=require('lodash.setwith'); setWith({}, ['__proto__','polluted'], 'yes', Object); ({}).polluted -> yes
Verified by running
probe-installed.ts against lodash.setwith@4.3.2 installed fresh: PROVEN, 3 of 22 probed calls.
Checked against
lodash.setwith 4.3.2, the current and final release. Checked 2026-09-10.
Reported
Prior: GHSA-p6mc-m468-83gw, published 2020-07-15.

@remix-run/web-fetch

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

On a cross-host redirect, fetch() rebuilds the request with `headers: new Headers(request.headers)` and no hostname comparison, so Authorization, Cookie and Proxy-Authorization all go to the new host. This is the fetch that @remix-run/node installs as global.fetch unless installGlobals({nativeFetch:true}) is passed.

node witness: fetch=require('@remix-run/web-fetch'); fetch('http://localhost:<A>/start',{headers:{authorization:'Bearer A',cookie:'c=C','proxy-authorization':'Basic P'}}) where A 302s to http://127.0.0.1:<B>/next -> B received authorization=Bearer A cookie=c=C proxy-authorization=Basic P
Verified by running
Node v22.14.0 against a freshly installed @remix-run/web-fetch@4.4.2 alone, prober re-run on that directory: PROVEN REDIRECT_CREDENTIAL_LEAK. two-server witness (rw-harness.js): server A on localhost answers 302 to server B on 127.0.0.1; the request carries authorization='Bearer A', cookie='c=C', proxy-authorization='Basic P'; B prints what arrived with explicit undefineds, and an unreached B prints NOTHING MEASURED. Result: authorization=Bearer A cookie=c=C proxy-authorization=Basic P. FIXED-SIDE CONTROL: inserting one line before dist/lib.node.cjs:1905 -- if (new URL(request.url).hostname !== locationURL.hostname) delete authorization, cookie, proxy-authorization from requestOptions.headers -- rendered authorization=undefined cookie=undefined proxy-authorization=undefined; restoring the published file rendered all three values again.
Checked against
@remix-run/web-fetch 4.4.2, dist-tags.latest (published 2023-12-06). Checked 2026-09-10.
Note
MECHANISM: src/fetch.js:184-195 (the loaded CJS is dist/lib.node.cjs:1873-1885) builds requestOptions with `headers: new Headers(request.headers)` and src/fetch.js:219 / dist:1905 calls fetch(new Request(locationURL.href, requestOptions)); grep for isDomainOrSubdomain/isSameProtocol over src and dist returns nothing, so the strip node-fetch added for CVE-2022-0235 is absent here entirely. NO PROXY OPTION: the only 'proxy' in the package is a JS Proxy object in src/headers.js:108-110, so Proxy-Authorization is only present when the caller set it; Authorization and Cookie need no such caveat. REACH: @remix-run/node 2.17.5 (latest) depends on ^4.4.2 and dist/globals.js:15-47 assigns global.fetch = RemixFetch from require('@remix-run/web-fetch') in the else-branch of installGlobals({nativeFetch} = {}), i.e. whenever nativeFetch is not passed as true. PRIOR REPORT: advisories affecting @remix-run/web-fetch: none; remix-run/web-std-io has issues disabled; gh issue list -R remix-run/remix --search 'web-fetch redirect authorization' returns nothing relevant and 'proxy-authorization' returns only #3159 'create-remix fails behind proxy'; advisories affecting @remix-run/node: only GHSA-9583-h5hc-x8cw (path traversal in file session storage). INDEPENDENT: the request is made by this package's own http client, not delegated.

electron-fetch

2 findings

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

On a redirect the same Request object, headers and all, is handed straight to fetch() for the new location with no hostname check, so Authorization, Cookie and Proxy-Authorization reach a different host.

node witness: fetch=require('electron-fetch').default; fetch('http://localhost:<A>/start',{headers:{authorization:'Bearer A',cookie:'c=C','proxy-authorization':'Basic P'},useElectronNet:false}) where A 302s to http://127.0.0.1:<B>/next -> B received authorization=Bearer A cookie=c=C proxy-authorization=Basic P (identical with default options)
Verified by running
Node v22.14.0 against a freshly installed electron-fetch@1.9.1 alone, prober re-run on that directory: PROVEN REDIRECT_CREDENTIAL_LEAK. two-server witness (rw-harness.js): server A on localhost answers 302 to server B on 127.0.0.1; the request carries authorization='Bearer A', cookie='c=C', proxy-authorization='Basic P'; B prints what arrived with explicit undefineds, and an unreached B prints NOTHING MEASURED. A first witness printed NOTHING MEASURED because require('electron-fetch') is the module object and the function is its .default -- that was the witness's error, not the package's, and is recorded so nobody repeats it. Corrected result: authorization=Bearer A cookie=c=C proxy-authorization=Basic P. FIXED-SIDE CONTROL: one line after lib/index.js:1443 deleting the three headers when url.parse(resolved).hostname !== url.parse(request.url).hostname rendered all three undefined; restoring the published file rendered all three values again.
Checked against
electron-fetch 1.9.1, dist-tags.latest (published 2022-09-28; a 1.9.2-2 beta tag exists). Checked 2026-09-10.
Note
MECHANISM: lib/index.js:1443-1444 -- `request.counter++; resolve(fetch(url.resolve(request.url, res.headers.location), request));` -- re-enters fetch with the original request object; the only header touched on the redirect path is content-length at line 1440. PROXY: the package's proxy support is Electron's net 'login' event (lib/index.js:1372-1388, README options user/password/onLogin) and it never sets a proxy-authorization header itself (grep returns nothing), so Proxy-Authorization is caller-supplied; Authorization and Cookie need no caveat. The witness ran on plain Node with useElectronNet:false and again with defaults (no Electron present, so it falls back to node http) and both leaked. PRIOR REPORT: advisories: none; gh issue list -R arantes555/electron-fetch --search 'redirect' returns #28 'Redirect is not working in electron fetch' (2020-09-27, OPEN, a user asking to disable following) and #17; --search 'authorization' returns #49 'Basic auth-scheme issue' and #13; none is about credentials crossing hosts. INDEPENDENT: a node-fetch 2-era fork with its own client; nothing is delegated.

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

index.d.ts declares `export enum FetchErrorType` (a value), which the package does not export at runtime.

(() => { const m = require('electron-fetch'); return 'control default=' + typeof m.default + ' FetchErrorType=' + typeof m.FetchErrorType; })() -> control default=function FetchErrorType=undefined
Verified by running
The probe proved it on the batch tree and again on a fresh alone install of 1.9.1; then confirmed by hand on Node v22.14.0: grep FetchErrorType index.d.ts hits lines 12 and 25, and require('electron-fetch').FetchErrorType printed undefined.
Checked against
electron-fetch 1.9.1, dist-tags.latest. Checked 2026-09-10.
Note
index.d.ts:12-22 declares the enum with nine string members ('body-timeout', 'system', 'max-size', 'abort', 'request-timeout', 'proxy', 'no-redirect', 'max-redirect', 'invalid-redirect') and index.d.ts:25 types FetchError's constructor as taking a FetchErrorType; at runtime lib/index.js:204 is `function FetchError(message, type, systemError)` and every call site passes a bare string (e.g. lib/index.js:388 'body-timeout'). The prober's sentence: 'declares 1 value the package does not export at runtime: FetchErrorType. The package kept 4 of its 5 declarations, so it loaded correctly.' PRIOR REPORT: gh issue list -R arantes555/electron-fetch --search 'FetchErrorType' returns nothing. OBSERVATION REWRITTEN 2026-09-10 to expression -> rendering with the control in the string, the tapable row's shape, so the discovered corpus can admit it.

got

1 finding

redirect-credential-leakAlready reported

CREDENTIAL_LEAK_ACROSS_REDIRECT

got 11.x deletes host, cookie and authorization when a redirect changes hostname or port and forwards proxy-authorization to the new host. 11.8.6 is still published under the version-11 dist-tag; latest (16.0.0) strips cross-origin state and is not affected.

node witness: got=require('got') (11.8.6); got('http://localhost:<A>/start',{headers:{authorization:'Bearer A',cookie:'c=C','proxy-authorization':'Basic P'},retry:0}) where A 302s to http://127.0.0.1:<B>/next -> B received authorization=undefined cookie=undefined proxy-authorization=Basic P
Verified by running
Node v22.14.0 against a freshly installed got@11 alone (11.8.6), prober re-run on that directory: PROVEN REDIRECT_CREDENTIAL_LEAK. two-server witness (rw-harness.js): server A on localhost answers 302 to server B on 127.0.0.1; the request carries authorization='Bearer A', cookie='c=C', proxy-authorization='Basic P'; B prints what arrived with explicit undefineds, and an unreached B prints NOTHING MEASURED. Result: authorization=undefined cookie=undefined proxy-authorization=Basic P -- the two undefineds prove the cross-host branch at 878 ran. FIXED-SIDE CONTROL: adding `if ('proxy-authorization' in options.headers) delete options.headers['proxy-authorization']` beside the authorization delete at dist/source/core/index.js:885-887 rendered proxy-authorization=undefined; restoring the published file rendered 'Basic P' again. got@16.0.0 was installed and read (dist/source/core/index.js:930-955, isDifferentOrigin then _stripCrossOriginState) but not driven, because it is ESM-only and the prober requires CJS.
Checked against
got 11.8.6, dist-tags.version-11 (published 2022-12-08); dist-tags.latest is 16.0.0 (published 2026-08-30), where the witness cannot apply because the package is ESM-only and its redirect path calls _stripCrossOriginState. Checked 2026-09-10.
Fixed upstream in
got 16.0.0 (dist-tags.latest) strips cross-origin state on redirect; the version-11 line (11.8.6) does not.
Reported
Prior (class, not this header): sindresorhus/got#1090 'HTTP authentication leak in redirects', opened 2020-02-25, closed 2020-04-12

node-fetch-commonjs

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

A CommonJS build of node-fetch 3.3.1: on a cross-host redirect it deletes authorization, www-authenticate, cookie and cookie2 and forwards proxy-authorization. The omission is node-fetch's (already in this file); this package republishes it under another name.

node witness: fetch=require('node-fetch-commonjs').default; fetch('http://localhost:<A>/start',{headers:{authorization:'Bearer A',cookie:'c=C','proxy-authorization':'Basic P'}}) where A 302s to http://127.0.0.1:<B>/next -> B received authorization=undefined cookie=undefined proxy-authorization=Basic P
Verified by running
Node v22.14.0 against a freshly installed node-fetch-commonjs@3.3.2 alone, prober re-run on that directory: PROVEN REDIRECT_CREDENTIAL_LEAK. two-server witness (rw-harness.js): server A on localhost answers 302 to server B on 127.0.0.1; the request carries authorization='Bearer A', cookie='c=C', proxy-authorization='Basic P'; B prints what arrived with explicit undefineds, and an unreached B prints NOTHING MEASURED. Result: authorization=undefined cookie=undefined proxy-authorization=Basic P. FIXED-SIDE CONTROL: adding 'proxy-authorization' to the list at index.js:2325 rendered proxy-authorization=undefined; restoring the published file rendered 'Basic P' again.
Checked against
node-fetch-commonjs 3.3.2, dist-tags.latest (published 2023-09-12). Checked 2026-09-10.
Note
DERIVED, NOT INDEPENDENT. README: 'node-fetch but in CommonJS format. This module is built from node-fetch directly', badge 'current upstream v3.3.1'. MECHANISM: index.js:2324-2328 -- `if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) { for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) requestOptions.headers.delete(name); }` -- the node-fetch 3 strip verbatim, one header short. No proxy option (grep 'proxy' in index.js hits only that list's absence). PRIOR REPORT: advisories affecting node-fetch-commonjs: none; gh issue list -R proteriax/node-fetch-cjs --search 'redirect' and 'authorization' return nothing. This row exists because the package proved on its own current release and is separately installable; a fix belongs upstream in node-fetch and would reach this package on its next rebuild.

request-promise-native

1 finding

redirect-credential-leakAlready reported

CREDENTIAL_LEAK_ACROSS_REDIRECT

module.exports is `request` itself (2.88.2, deprecated), whose redirect handler removes authorization when the hostname changes and forwards both cookie and proxy-authorization to the new host. The omission is request's; this package adds only promise methods.

node witness: rp=require('request-promise-native'); rp({uri:'http://localhost:<A>/start',headers:{authorization:'Bearer A',cookie:'c=C','proxy-authorization':'Basic P'}}) where A 302s to http://127.0.0.1:<B>/next -> B received authorization=undefined cookie=c=C proxy-authorization=Basic P (request 2.88.2 called directly: identical)
Verified by running
Node v22.14.0 against a freshly installed request-promise-native@1.0.9 alone (which pulls request 2.88.2 as its peer), prober re-run on that directory: PROVEN REDIRECT_CREDENTIAL_LEAK. two-server witness (rw-harness.js): server A on localhost answers 302 to server B on 127.0.0.1; the request carries authorization='Bearer A', cookie='c=C', proxy-authorization='Basic P'; B prints what arrived with explicit undefineds, and an unreached B prints NOTHING MEASURED. Result via rp(): authorization=undefined cookie=c=C proxy-authorization=Basic P; via request() directly: identical. FIXED-SIDE CONTROL: adding request.removeHeader('cookie') and request.removeHeader('proxy-authorization') beside the authorization removal at request/lib/redirect.js:138 rendered all three undefined through rp(); restoring the published file rendered cookie=c=C and 'Basic P' again.
Checked against
request-promise-native 1.0.9, dist-tags.latest (published 2020-07-22); request 2.88.2, dist-tags.latest, deprecated ('see request/request#3142'). Checked 2026-09-10.
Reported
Prior (class, authorization only): request/request#450 'Authorization header shouldn't be forwarded across redirects', opened 2013-02-28, closed 2015-09-14

download

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

download() is got.stream(uri, opts) on got 8.3.2, which copies every option including headers into the redirected request; Authorization, Cookie and Proxy-Authorization all reach the new host. The omission is got 8's; download@8 pins ^8.3.1 and is unmaintained.

node witness: download=require('download'); download('http://localhost:<A>/start',{headers:{authorization:'Bearer A',cookie:'c=C','proxy-authorization':'Basic P'}}) where A 302s to http://127.0.0.1:<B>/next -> B received authorization=Bearer A cookie=c=C proxy-authorization=Basic P (got 8.3.2 called directly: identical)
Verified by running
Node v22.14.0 against a freshly installed download@8.0.0 alone, prober re-run on that directory: PROVEN REDIRECT_CREDENTIAL_LEAK for download and for its got 8.3.2. two-server witness (rw-harness.js): server A on localhost answers 302 to server B on 127.0.0.1; the request carries authorization='Bearer A', cookie='c=C', proxy-authorization='Basic P'; B prints what arrived with explicit undefineds, and an unreached B prints NOTHING MEASURED. Result via download(): authorization=Bearer A cookie=c=C proxy-authorization=Basic P; via got 8.3.2 directly: identical. No fixed-side control: the fix belongs in got, whose current release already has it (got 16.0.0), and download would need a major dependency bump rather than a one-line edit.
Checked against
download 8.0.0, dist-tags.latest (published 2020-04-02), resolving got@8.3.2. Checked 2026-09-10.
Fixed upstream in
got 11 (partial: strips authorization and cookie) and got 16.0.0 (strips proxy-authorization too); download has not moved off got 8.
Note
DERIVED: index.js:10 `const got = require('got')` and index.js:78 `const stream = got.stream(uri, opts)`; download adds no redirect handling. MECHANISM (got 8.3.2): index.js:136 `const redirectOpts = Object.assign({}, opts, urlLib.parse(redirectUrl));` then index.js:140 `get(redirectOpts)` -- no hostname comparison, no header deletion. No proxy option in download (grep 'proxy' returns nothing) and got 8 had none, so Proxy-Authorization is caller-supplied; Authorization and Cookie need no caveat. PRIOR REPORT: advisories affecting download: none; gh issue list -R kevva/download --search 'redirect' returns #224 'Npm audit failure via older version of got' (2023-02-24, OPEN), #213 '[node-downloader-helper] A very good alternative to this dead package' (2023-01-29, OPEN), #159, #85, #74; --search 'authorization' returns #207 and #155 (basic auth usage), none about credentials crossing hosts. The prober also proved got (8.3.2) inside download's own tree, which is the same defect seen from the other side.

smart-merge

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

merge() walks every enumerable key of each source with `for (var i in b)` and assigns `a[i] = ...` with no key filter, so a `__proto__` key in ordinary JSON writes onto Object.prototype.

const merge=require('smart-merge'); merge({}, JSON.parse('{"__proto__":{"polluted":"yes"}}')); ({}).polluted -> 'yes'; the control merge({}, {a:{b:1}}) leaves Object.prototype clean and returns {"a":{"b":1}}
Verified by running
Node v22.14.0 against a freshly installed smart-merge@0.1.7 alone, prober re-run on that directory: PROVEN PROTOTYPE_POLLUTION ('7 of 22 probed calls did it. First: <default> fn(payload)'). By hand: ({}).polluted was undefined before, 'yes' after the JSON payload, undefined again after deleting the planted key and running the control. FIXED-SIDE CONTROL: adding `if (i === '__proto__' || i === 'constructor' || i === 'prototype') continue;` as the first line of the loop at index.js:9 left ({}).polluted undefined while merge({a:1},{b:{c:2}}) still returned {"a":1,"b":{"c":2}}; restoring the published file polluted again.
Checked against
smart-merge 0.1.7, dist-tags.latest (published 2016-04-19; nothing since). Checked 2026-09-10.
Reported
Prior: Mohamed-amin/smart-merge#6 '🚨 Potential Prototype Pollution', opened 2021-05-17, still OPEN (a huntr.dev disclosure notice)

object-set

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

set(obj, path, value) walks the path with `nested = nested[key]` and assigns at each step without excluding __proto__, constructor or prototype, so a '__proto__.x' path (string or array) writes onto Object.prototype.

const set=require('object-set'); set({}, '__proto__.polluted', 'yes'); ({}).polluted -> 'yes'; set({}, ['__proto__','polluted'], 'yes') does the same; the control set({}, 'a.polluted', 'yes') leaves Object.prototype clean
Verified by running
Node v22.14.0 against a freshly installed object-set@1.0.1 alone, prober re-run on that directory: PROVEN PROTOTYPE_POLLUTION ('3 of 22 probed calls did it. First: <default> fn({}, [String("__proto__"),"x"], 1)'). By hand: ({}).polluted undefined before; 'yes' after the string path; 'yes' after the array path; undefined after the control, deleting the planted key between cases. FIXED-SIDE CONTROL: inserting `if (key === '__proto__' || key === 'constructor' || key === 'prototype') { return object; }` after index.js:35 left ({}).polluted undefined while set({}, 'a.b', 1) still returned {"a":{"b":1}}; restoring the published file polluted again.
Checked against
object-set 1.0.1, dist-tags.latest (published 2016-04-15; nothing since). Checked 2026-09-10.
Reported
Prior: gearcase/object-set#3 'Prototype Pollution', opened 2022-04-28, OPEN; gearcase/object-set#4 'Prototype pollution via __proto__/constructor.prototype paths (CWE-1321)', opened 2026-08-08, OPEN

object-path-immutable

1 finding

prototype-pollutionNo prior report

PROTOTYPE_POLLUTION

set(src, path, value) and wrap(src).set(path, value) pollute Object.prototype through a '__proto__.x' path whenever src is null or undefined -- the documented 'create it for me' case -- because the fresh {} the library creates is then indexed by '__proto__' and the write lands on Object.prototype. With a real object as src the same path is harmless.

const opi=require('object-path-immutable'); opi.set(undefined, '__proto__.polluted', 'yes'); ({}).polluted -> 'yes'; opi.wrap().set('__proto__.polluted','yes').value() does the same; the controls opi.set({}, '__proto__.polluted', 'yes') and opi.set(undefined, 'a.polluted', 'yes') leave Object.prototype clean (the second returns {"a":{"polluted":"yes"}})
Verified by running
Node v22.14.0 against a freshly installed object-path-immutable@4.1.2 alone, prober re-run on that directory: PROVEN PROTOTYPE_POLLUTION ('5 of 1210 probed calls did it. First: wrap().set fn("__proto__.x", 1)'). The first hand witness used set({}, ...) and did NOT pollute -- that discrepancy was chased through seventeen call shapes and resolved to the src-nullish route above (POLLUTED: wrap().set, wrap(undefined).set, set(undefined, ...), set(Object.create(null), ...); clean: set({}, ...), array paths on {}, constructor.prototype on {}, update/assign/merge/push/insert on {}). FIXED-SIDE CONTROL: inserting `if (currentPath === '__proto__' || currentPath === 'constructor' || currentPath === 'prototype') { return dest }` after cjs:110 left ({}).polluted undefined for both polluting shapes while set(undefined, 'a.b', 'yes') still returned {"a":{"b":"yes"}}; restoring the published file polluted again.
Checked against
object-path-immutable 4.1.2, dist-tags.latest (published 2021-09-16). Checked 2026-09-10.
Note
THE ROUTE IS NARROWER THAN THE CLASS AND THE ROW NAMES IT. MECHANISM, cjs/object-path-immutable.js: _changeImmutable at 110-126 -- `var currentPath = path[0]; if (!dest || dest === src) { dest = clone(src, true, ...) }` where clone(undefined, true) returns a fresh {} (59-66); then, since src == null the `src = src[currentPath]` at 120-122 is skipped, and 124 does `dest[currentPath] = _changeImmutable(dest[currentPath], src, path.slice(1), cb)` -- dest['__proto__'] IS Object.prototype, and on that recursion `!dest` and `dest === src` are both false so no clone happens and the callback at 117 writes cb(Object.prototype, 'polluted'). With src = {} the recursion instead sees dest === src === Object.prototype and clones it, which is why the control is clean; Object.create(null) as src pollutes for the same reason as undefined (its '__proto__' read is undefined). The 'object-path' dependency (three prototype-pollution advisories of its own: CVE-2020-15256, CVE-2021-23434, CVE-2021-3805) is required at cjs:6 but is not on this code path; the walk above is this package's own. PRIOR REPORT: advisories affecting object-path-immutable: none; gh issue list --search 'prototype pollution' and '__proto__' return nothing; the package ships a SECURITY.md asking for private reports to report @ mario.fyi. NOT REPORTED UPSTREAM.

expand-object

1 finding

prototype-pollutionAlready reported

PROTOTYPE_POLLUTION

expand('__proto__.polluted:yes') writes onto Object.prototype because the string is handed to set-value 0.3.3, which walks the dotted path with no key filter. The pollution is set-value's (already in this file); expand-object is the reachable caller, and an advisory already covers it.

const expand=require('expand-object'); expand('__proto__.polluted:yes'); ({}).polluted -> 'yes'; the control expand('a.polluted:yes') leaves Object.prototype clean
Verified by running
Node v22.14.0 against a freshly installed expand-object@0.4.2 alone, prober re-run on that directory: PROVEN PROTOTYPE_POLLUTION for expand-object ('3 of 22 probed calls did it. First: <default> fn("__proto__.x", 1)') and for its set-value 0.3.3. By hand: ({}).polluted undefined before, 'yes' after expand('__proto__.polluted:yes'), undefined after the control. No fixed-side control: the fix belongs in set-value (current set-value 4.1.0 has it) and expand-object would need a dependency bump.
Checked against
expand-object 0.4.2, dist-tags.latest (published 2016-01-29; nothing since), resolving set-value@0.3.3. Checked 2026-09-10.
Fixed upstream in
set-value 2.0.1 / 3.0.3 / 4.x reject __proto__ keys; expand-object still pins ^0.3.3.
Reported
Prior: GHSA-4vjr-hfpp-2m7w / CVE-2025-3197, published 2025-04-04, 'expand-object Vulnerable to Prototype Pollution via the expand() Function', vulnerable range <= 0.4.2, first_patched_version null

unfetch

1 finding

undelivered-entryAlready reportedNot a vulnerability

UNDELIVERED_ENTRY

exports['.'].import names ./index.mjs and exports['.'].default names ./index.js; the tarball ships neither. The built files are dist/unfetch.mjs and dist/unfetch.js, which main and module still point at, but the exports map wins over main, so both require('unfetch') and import 'unfetch' fail MODULE_NOT_FOUND on the root entry of the current release.

(() => { try { require('unfetch'); return 'loaded'; } catch (e) { const control = typeof require('unfetch/dist/unfetch.js'); return e.code + ' on ' + e.message.split('\n')[0].replace(/^.*node_modules\//, '') + '; control require(unfetch/dist/unfetch.js) is ' + control; } })() -> MODULE_NOT_FOUND on unfetch/index.js'; control require(unfetch/dist/unfetch.js) is function
Verified by running
Node v22.14.0 against the 5.0.0 installed by npm in probe-sweep3. require('unfetch') threw MODULE_NOT_FOUND naming .../unfetch/index.js; a dynamic import from a real .mjs threw ERR_MODULE_NOT_FOUND naming .../unfetch/index.mjs. CONTROLS in the same programs: require('unfetch/dist/unfetch.js') loaded a function and import('unfetch/dist/unfetch.mjs') loaded a default that is a function, so the built files ship and only the two root targets are wrong. fs.existsSync on index.js and index.mjs both printed false. Prior-report search ran before this was written: gh api advisories for unfetch returned none; the issue search returned the three above.
Checked against
unfetch 5.0.0, the current release (dist-tags.latest on 2026-09-10; published 2022-12-31). Checked 2026-09-10.
Not reported, on purpose
THE STREAMX SHAPE ON THE ROOT ENTRY, so every consumer breaks rather than one subpath: the './*': './*' fallback in the same map cannot rescue '.', and 'main' is not consulted once 'exports' exists. ALREADY REPORTED THREE TIMES AND STILL OPEN: developit/unfetch#162 (2023-01-28, 'unfetch@5.0.0 published package might be missing files'), #163 (2023-02-05, 'Package.json conditional exports points to non-existent files') and #172 (2023-07-07, "Module not found: Can't resolve 'unfetch'"). Nothing has been published since 2022-12-31, so this is a human-reported, unrepaired defect in an apparently unmaintained package. The finder reached it independently, which is worth recording; a fourth report would not be. NOT REPORTED for that reason. OBSERVATION SET 2026-09-10 to the exact string the expression renders, from running the generated witness against a fresh 5.0.0; the first recording paraphrased the error and the corpus needle compares text.

@standard-schema/spec

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['.']['standard-schema-spec'], the first condition in the root map, names ./src/index.ts, and the tarball's files field is ["dist"] so src/ is not shipped. Any resolver asked for that condition gets MODULE_NOT_FOUND; every other condition resolves.

node --conditions=standard-schema-spec -e "require('@standard-schema/spec')" -> MODULE_NOT_FOUND: Cannot find module '.../@standard-schema/spec/src/index.ts'; the same require without the flag loads
Verified by running
Node v22.14.0 against the 1.1.0 installed in probe-sweep2. The control require('@standard-schema/spec') loaded (a module exporting only types, so Object.keys is []); the same require under --conditions=standard-schema-spec threw MODULE_NOT_FOUND naming src/index.ts; ls of the package directory shows LICENSE, README.md, dist, package.json and no src. Prior-report search: no advisories; issue searches for the condition name, src/index.ts and 'exports condition' returned nothing on this subject.
Checked against
@standard-schema/spec 1.1.0, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Not reported, on purpose
REAL AND ALMOST CERTAINLY HARMLESS, and the reason is in the repository: a GitHub code search for 'standard-schema-spec' in standard-schema/standard-schema returns six files, and the four outside the two package manifests are tsconfig.json files (packages/web, packages/spec, packages/utils, packages/examples) -- it is the monorepo's own customConditions entry for resolving sibling packages to TypeScript source during development, and it shipped in the published manifest. Nobody outside that monorepo sets a condition named after the package, so no consumer reaches the missing file. The fix is deleting one line. NOT REPORTED, the napi-postinstall judgement: real, one line, and not worth a maintainer's attention.

property-expr

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

SPEC_CHAR_REGEX (index.js:27) is declared with the g flag and its only use is SPEC_CHAR_REGEX.test(part) in hasSpecialChars (index.js:153), so lastIndex carries between calls. shouldBeQuoted, and through it the exported forEach, answers differently for the same segment depending on what the previous call matched.

(() => { const p = require('property-expr'); const seen = (parts) => { const out = []; p.forEach(parts, (part, isBracket) => out.push(part + (isBracket ? '(bracket)' : ''))); return out.join(','); }; const fresh = seen(['a-b']); seen(['abcd-ef']); const after = seen(['a-b']); return 'fresh ' + fresh + ' | after ' + after; })() -> fresh "a-b"(bracket) | after a-b
Verified by running
Node v22.14.0 against the 2.0.6 installed in probe-sweep2. Four calls in one process: forEach(['a-b']) fresh gave "a-b"(bracket); forEach(['abcd-ef']) gave "abcd-ef"(bracket) and primed lastIndex; forEach(['a-b']) immediately after gave a-b with no bracket; forEach(['a-b']) once more gave "a-b"(bracket) again. CONTROL in the same program: the leading-digit path, which uses the non-global LEAD_DIGIT_REGEX, answered "1a"(bracket) on both consecutive calls. The exported join was also run and does not call shouldBeQuoted, so it is unaffected; forEach is the reachable surface.
Checked against
property-expr 2.0.6, the current release (dist-tags.latest on 2026-09-10; published 2023-10-13). Checked 2026-09-10.
Not reported, on purpose
THE CONTENT-DISPOSITION SHAPE, unfixed and unreported: a module-scope /g regex used only with .test(). The finder's own oracle for this class is content-disposition#128, where a maintainer repaired exactly this and the finder discriminated the pinned/fixed pair. Here the mechanism is one call deep: a match on 'abcd-ef' leaves lastIndex at 5, the next test on the three-character 'a-b' starts past the end, returns false and resets, so every third identical call is answered wrongly and the callback receives a bare segment with isBracket=false where the call before and after quote it. THE FAIR ARGUMENT AGAINST, and why this is recorded as modest: the package's largest consumer is yup, whose getIn strips the quotes when isBracket is true and uses the segment as-is otherwise, so yup lands on the same key either way and is not visibly affected; only a caller of forEach that reads the part or isBracket it is handed sees the divergence. The fix is deleting the g flag, which nothing in the file needs. NOT REPORTED: the only advisory is GHSA-6fw4-hr69-g3rv (2021, prototype pollution, unrelated) and jquense/expr's issues mention neither lastIndex, SPEC_CHAR_REGEX nor quoting; #18 (2023-10-11, open) is about the TypeScript declaration.

minipass-sized

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares >=8 and both shipped entries use nullish coalescing: dist/commonjs/index.js:14 and dist/esm/index.js:11 are `Error.captureStackTrace(this, from ?? this.constructor);`, and line 25 uses optional chaining. Both are Node 14 syntax, so every Node from 8 through 13 that the manifest admits fails to parse the file.

package.json engines.node is '>=8'; dist/commonjs/index.js:14 is `Error.captureStackTrace(this, from ?? this.constructor);` and :25 is `const size = options?.size;`. ?? and ?. are Node 14.0; the range admits 8.0 through 13.x.
Verified by running
Manifest and both source lines read from the 2.0.0 installed in probe-sweep; the minipass beside it is 7.1.3 with engines '>=16 || 14 >=14.17', read from its own manifest. npm view confirms 2.0.0 is latest with engines >=8. Prior-report search: no advisories; isaacs/minipass-sized has two issues in total (#14 'Can this library be updated?', closed 2026-01-07 when 2.0.0 shipped, and #15 about the size option), neither about engines.
Checked against
minipass-sized 2.0.0, the current release (dist-tags.latest on 2026-09-10; published 2026-01-07). Checked 2026-09-10.
Not reported, on purpose
THE WHOLE RANGE IS WRONG, which is the lightningcss shape rather than the napi-postinstall one -- and lightningcss was filed. Two things make this one weaker. First, the too-low branch is Node 8 to 13, all end-of-life for years. Second, and specific to this package: its only dependency, minipass ^7.1.2, declares engines '>=16 || 14 >=14.17', so an installer that honours engines already refuses every Node that would hit the SyntaxError here, one package down. The stale field is a leftover from 1.x (2019) that the 2.0.0 rewrite in January 2026 carried forward. Real, one line, and the dependency's floor already does the job; recorded, and whether to file is Martin's call.

signal-exit

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares >=14 and both shipped entries define private class methods: dist/cjs/index.js:220 and dist/mjs/index.js:216 are `#processReallyExit(code) {`, with `#processEmit` beside it. Private methods are Node 14.6; the range admits 14.0 through 14.5, which cannot parse the file.

package.json engines.node is '>=14'; dist/cjs/index.js:220 is `#processReallyExit(code) {` and :230 is `#processEmit(ev, ...args) {`. Private class methods are Node 14.6; the range admits 14.0 through 14.5.
Verified by running
Manifest and both source lines read from the 4.1.0 installed in probe-sweep2 (probe-sweep3 holds the identical version and the scanner reported the identical two sites). Prior-report search: no advisories; tapjs/signal-exit issue searches for 'engines', 'private' and 'node 14' returned nothing on this subject.
Checked against
signal-exit 4.1.0, the current release (dist-tags.latest on 2026-09-10; published 2023-07-29). Checked 2026-09-10.
Not reported, on purpose
NARROWER THAN NAPI-POSTINSTALL: the admitted-but-broken window is six minor releases of a Node line that has been end-of-life since 2023-04, and the fix is changing 14 to 14.6 (or 16, which is what the code's other choices assume). signal-exit sits under most of the npm CLI and test tooling, which is why it is in two of the three trees, but nobody on Node 14.0-14.5 is installing current tooling. Real, and not worth a maintainer's attention -- the same judgement as the napi-postinstall and @babel/helper-* rows.

properties-parser

1 finding

engines-below-syntaxAlready reportedNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares >= 0.3.1 and index.js:405, inside the exported editor path, is `return new Editor(text, options ?? {});`. Nullish coalescing is Node 14; the range admits every Node from 0.3 to 13, none of which can parse the file.

package.json engines.node is '>= 0.3.1'; index.js:405 is `return new Editor(text, options ?? {});`. ?? is Node 14.0; the range admits 0.3.1 through 13.x.
Verified by running
Manifest and the source line read from the 0.6.0 installed in probe-sweep3; npm view confirms 0.6.0 is latest with engines '>= 0.3.1'. Prior-report search: no advisories; the issue search for 'engines' returned #11, read in full.
Checked against
properties-parser 0.6.0, the current release (dist-tags.latest on 2026-09-10; published 2023-05-26). Checked 2026-09-10.
Not reported, on purpose
THE PRIOR REPORT IS THE INTERESTING PART: xavi-/node-properties-parser#11, 'Requires node >=4.0.0', filed 2015 and closed 2015-11-19, said exactly this about an earlier line -- `chr.codePointAt()` needs Node 4 and engines says >= 0.3.1 -- and the field was never changed; it still says >= 0.3.1 eight years and one syntax generation later. That makes this a human-reported defect the maintainers declined to act on, not a discovery, and priorReport says so even though the specific line is new. Every admitted-but-broken Node is long dead. Real, previously reported, ignored once already: NOT REPORTED, because a second report of a closed one is the clearest way to spend goodwill.

filenamify

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

filenamify 3.0.0 validates the replacement string with reControlChars.test(replacement) on a /g regex (index.js:20), so the check's answer alternates with lastIndex: the same bad replacement throws on the first call and is accepted on the second.

const f=require('filenamify'); const r=[]; for (const i of [1,2,3]) { try { f('abc',{replacement:'<'}); r.push('accepted'); } catch (e) { r.push('threw'); } } r.join(',') -> threw,accepted,threw
Verified by running
Node v22.14.0 against filenamify@3.0.0: call 1 threw 'Replacement string cannot contain reserved filename characters', the identical call 2 returned "abc" with the validator bypassed, call 3 threw again; control filenamify('a<b') -> "a!b". filenamify@7.0.3 installed alone and rescanned: clean.
Checked against
filenamify 3.0.0 through 6.0.0; fixed in 7.0.0, the current line is 7.0.3. Checked 2026-09-10.
Fixed upstream in
7.0.0
Note
Found 2026-09-10 by the reading finders (scripts/scan-installed.ts) over the 1,332 packages in tonight's three probe-sweep trees, 12,584 files, 24 hits, 22 true on the finder's own claim; verified by an agent that executed every claim with a control. The content-disposition shape exactly: a global regex reused through .test. Read at tags through the GitHub contents API, the /g .test survives to v6.0.0 and v7.0.0 introduces a separate non-global reControlCharsTest for the check, the same repair content-disposition made. A pinned/fixed pair the finder discriminates on, fixed by a maintainer without a report naming it; recorded for the corpus, not for filing.

yargs

2 findings

cjs-binding-in-esmNo prior reportNot a vulnerability

CJS_BINDING_IN_DECLARED_ESM

yargs 16.2.2, used from an ES module, silently ignores config({extends: '<module name>'}): build/lib/utils/apply-extends.js:14 does require.resolve(config.extends) inside a try, the ReferenceError in ESM is swallowed, and the caller gets its config back with the extends key still in argv and the extended file never read. CJS honours the same call.

node --input-type=module -e "import yargs from 'yargs'; const a = yargs([]).config({extends: 'zz-extends-cfg', beta: 2}).parse(); console.log('alpha=' + a.alpha + ' extends=' + a.extends)" -> alpha=undefined extends=zz-extends-cfg
Verified by running
Node v22.14.0, a zz-extends-cfg module beside the script exporting {alpha: 1}: from a real .mjs against 16.2.2 the parse printed alpha=undefined extends=zz-extends-cfg; the same line from CommonJS printed alpha=1 extends=undefined; from ESM against 18.1.0 installed alone it printed alpha=1 extends=undefined.
Checked against
yargs 16.2.2; fixed in the current 18.1.0, which reads _shim.require(config.extends) and gives alpha=1 extends=undefined from ESM. Checked 2026-09-10.
Fixed upstream in
18.1.0
Note
Found 2026-09-10 by the reading finders (scripts/scan-installed.ts) over the 1,332 packages in tonight's three probe-sweep trees, 12,584 files, 24 hits, 22 true on the finder's own claim; verified by an agent that executed every claim with a control. THE SCANNER POINTED AT THE WRONG LINE AND THE WRONG MECHANISM, and the row records the corrected one. It flagged line 27, the bare require(config.extends), as an unguarded ReferenceError; that line is reachable only if line 14's require.resolve inside the try succeeded, which in ESM it never does, so the catch returns config and line 27 never runs. The observable defect is the silent-success shape one layer up: the try swallowed the very error that meant the feature could not work. Fixed in 18.1.0 by the shim; recorded for the corpus, not for filing. The same scan's two which-module.js rows on yargs were FALSE (an early-return typeof guard the rule does not see), which is a rule defect in cjs-binding-in-esm.ts and is tracked in core, not here.

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

yargs 17.7.3's exports map names `./browser` with `types: ./browser.d.ts`, a file the package does not ship (browser.mjs is there), so a TypeScript consumer importing `yargs/browser` gets TS7016.

node -e "console.log(JSON.stringify(require('yargs/package.json').exports['./browser']))"; ls node_modules/yargs/browser* -> types names browser.d.ts; only browser.mjs exists
Verified by running
Read from the installed package: the exports entry and the listing. Control: the import target browser.mjs exists.
Checked against
yargs 17.7.3, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. 17.7.3 is the 17.x line the tree resolved; the current 18.1.0 was not installed here and is unchecked. yargs/yargs#2302 asks how to use the ESM shim and is the nearest prior thread; none names browser.d.ts. Recorded, not drafted until 18 is checked.

mongoose

2 findings

prototype-pollutionNo prior report

PROTOTYPE_POLLUTION

Schema.prototype.virtual (lib/schema.js:2706-2711) walks a dotted virtual name into this.tree with parts.reduce, so a name whose first segment is __proto__ reads tree['__proto__'] -- Object.prototype -- and writes a VirtualType onto it. Schema.add and Schema.path, the siblings that take the same dotted names, refuse the segment with 'Cannot set special property' (schema.js:812, 1314, 1337, via utils.specialProperties); virtual() and the schema-level virtuals option are the two doors without that guard.

const { Schema } = require('mongoose'); new Schema().virtual(JSON.parse('"__proto__.polluted"'), 1); typeof ({}).polluted -> object. That is the VirtualType written onto Object.prototype; new Schema().path('__proto__.x', String) on the same install throws MongooseError: Cannot set special property `__proto__` on a schema
Verified by running
Node v22.14.0 against mongoose@9.9.5 installed fresh with --ignore-scripts, alone. CONTROL FIRST: new Schema().virtual('plain.x', 1) added nothing to Object.prototype and ({}).x stayed undefined. Then virtual('__proto__.polluted', 1) added ['polluted'] to Object.getOwnPropertyNames(Object.prototype) and ({}).polluted became a VirtualType. new Schema({}, { virtuals: { '__proto__.viaOption': { get() {} } } }) added ['viaOption']. THE SIBLINGS IN THE SAME RUN: Schema.path('__proto__.viaPath', String) and Schema.add({ '__proto__.viaAdd': String }) both threw 'Cannot set special property `__proto__` on a schema' and added nothing. FIXED-SIDE CONTROL: inserting one line before schema.js:2706, `if (parts.some(p => utils.specialProperties.has(p))) throw new MongooseError(...)` -- the guard add() already uses -- turned the witness into that same MongooseError with Object.prototype untouched and ({}).polluted undefined; the control plain.x still passed; restoring the published file restored the pollution.
Checked against
mongoose 9.9.5, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE MAINTAINERS' OWN PRECEDENT IS WHAT MAKES THIS A VULNERABILITY IN THEIR TERMS RATHER THAN OURS: CVE-2022-2564 (GHSA-f825-f98c-gj3g, 'Prototype pollution via Schema.path', fixed 6.4.6) and CVE-2022-24304 (GHSA-h8hf-x3f4-xwgp, the schema object, fixed 6.4.6) were accepted and fixed for exactly this input through path() and add(), and the fix is the specialProperties guard those two now carry. virtual() takes the same dotted path and was not given it; neither was the `virtuals` schema option, which is documented as an alias for .virtual (schema.js:90) and pollutes the same way. THE FAIR ARGUMENT AGAINST, stated plainly: a virtual name is developer-authored schema code, not a request body, and the 2026 advisory GHSA-664h-wqgq-64gw (update casting, patched in 9.7.2 and closed in this 9.9.5) is the one whose input really is user-controlled. So this is the shape the maintainers have already called a vulnerability twice, reachable only where an application builds schemas from untrusted names. The probe found it as 1 of 13,420 driven calls. NOT REPORTED UPSTREAM: Automattic/mongoose issues searched for 'virtual __proto__' and 'virtual prototype pollution' return nothing on point; the three prototype-pollution advisories name path(), the schema object and _castUpdate, not virtual(). REPRODUCE REWRITTEN 2026-09-11 to the exact string the expression renders, from running the generated witness against a fresh install: typeof renders as the bare word; the commentary is moved after the value.

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

lib/helpers/populate/modelNamesFromRefPath.js:10 declares hasNumericPropRE with the g flag and line 30 uses it only as hasNumericPropRE.test(populatedPath), so the branch that rewrites a refPath for a numerically-indexed populate path (a.1.b -> a.1.c) runs on every other identical call: the second call falls through to mpath.get(refPath) and returns the model names of every array element instead of the indexed one.

const f = require('mongoose/lib/helpers/populate/modelNamesFromRefPath'); const doc = { a: [{ c: 'M1' }, { c: 'M2' }] }; [1,2,3].map(() => JSON.stringify(f('a.c', doc, 'a.1.b', null, null))) -> ["[\"M2\"]","[\"M1\",\"M2\"]","[\"M2\"]"]. Three consecutive calls with the same arguments answer M2, then M1 and M2, then M2
Verified by running
Node v22.14.0 against mongoose@9.9.5 installed fresh, alone. Three calls in one process with doc {a:[{c:'M1'},{c:'M2'}]}, refPath 'a.c', populatedPath 'a.1.b': ["M2"], then ["M1","M2"], then ["M2"]. CONTROL in the same program: populatedPath 'a.b', which has no numeric segment and never enters the branch, answered ["M1","M2"] on all three calls. FIXED-SIDE CONTROL: removing the g flag on line 10 gave ["M2"] three times with the control unchanged; restoring the file restored the alternation.
Checked against
mongoose 9.9.5, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The content-disposition shape, one call deep: a match on 'a.1.b' leaves lastIndex at 4, the next test on the same five-character string starts past the '.1.' and fails, and the helper answers as if the path had no index. THE FUNCTION IS ON THE POPULATE PATH: getModelsMapForPopulate.js:256, 329, 446, 471 and 476 and virtualType.js:184 call it with options.path as populatedPath, so Model.populate(doc, { path: 'a.1.b' }) against a refPath is the public surface; the same populate issued twice in one process gets two different model lists, and the second one is the one that would look up documents in models the index never pointed at. The split on line 31 uses the same regex but String.prototype.split ignores lastIndex, which is why only the test diverges. The fix is deleting the g flag, which nothing in the file needs. NOT REPORTED UPSTREAM: Automattic/mongoose issues mention neither hasNumericPropRE nor lastIndex; #12066 (2022, closed) is about refPath in sub-documents and does not describe alternation. REPRODUCE REWRITTEN 2026-09-11 to the exact string the expression renders, from running the generated witness against a fresh install: the array of JSON strings is what the expression returns and JSON.stringify prints.

cssfilter

1 finding

stateful-matcherNo prior report

STATEFUL_MATCHER_DIVERGENCE

lib/default.js:381 declares REGEXP_URL_JAVASCRIPT = /javascript\s*\:/img and safeAttrValue (line 391) uses it only as .test(value), so the package's one value check -- the thing that turns a javascript: URL in a style declaration into '' -- alternates with lastIndex: the same value is stripped on one call and passed through on the next.

const { safeAttrValue } = require('cssfilter'); [1,2,3].map(() => JSON.stringify(safeAttrValue('background', 'javascript:alert(1)'))) -> ["\"\"","\"javascript:alert(1)\"","\"\""]. The second of three identical calls lets the javascript: value through
Verified by running
Node v22.14.0 against cssfilter@0.0.11 installed fresh, alone. safeAttrValue('background', 'javascript:alert(1)') three times: "", "javascript:alert(1)", "". new FilterCSS().process('background:url(javascript:alert(1))') three times in the same process: "background:url(javascript:alert(1));", "", "background:url(javascript:alert(1));" -- the first public call passed the payload because the direct calls before it had left lastIndex past the match. CONTROL: safeAttrValue('color', 'red') answered "red" three times. Through xss@1.0.15 (cssfilter 0.0.10) the same style was stripped all three times by xss's own pre-check, so xss is not the reach. FIXED-SIDE CONTROL: changing line 381 to /javascript\s*\:/im gave "" three times from both entry points, the control unchanged; restoring the file restored the alternation.
Checked against
cssfilter 0.0.11, the current release (dist-tags.latest on 2026-09-10); the batch tree held 0.0.10, pinned by xss 1.0.15, where the same lines sit at 379 and 389. Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. A SANITIZER WHOSE ONLY VALUE CHECK IS NONDETERMINISTIC, which is what makes this a vulnerability in the package's own terms: css.js:50 installs DEFAULT.safeAttrValue and css.js:77 runs it on every whitelisted declaration, so FilterCSS.process('background:url(javascript:alert(1))') returns the declaration intact on every second call. THE TWO CAVEATS that keep this modest: modern browsers do not execute javascript: inside CSS (this was an IE-era vector), and the package's largest consumer, xss 1.0.15, does not reach it for this input -- xss's own lib/default.js:205-216 checks expression() and url(javascript:) first and, tellingly, writes `.lastIndex = 0` before every one of its own global-regex tests, so the same author knew the trap in one package and not the other. Direct consumers of FilterCSS and of safeAttrValue, and any xss caller who passes a custom css option that bypasses those pre-checks, get the alternating filter. The fix is deleting the g flag, which .test never needs. NOT REPORTED UPSTREAM: no advisory affects cssfilter and leizongmin/js-css-filter issues mention neither javascript: nor lastIndex. REPRODUCE REWRITTEN 2026-09-11 to the exact string the expression renders, from running the generated witness against a fresh install: the first recording began with an empty string, which made the corpus needle empty and the verdict vacuous; the generator now refuses that shape.

websocket-extensions

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

lib/parser.js:4 declares NOTOKEN with the g flag and serializeParams (line 63) uses it only as NOTOKEN.test(value) to decide whether a parameter value must be quoted, so a value with a non-token character is quoted on one call and emitted bare on the next, and the bare form is not a valid Sec-WebSocket-Extensions header.

const P = require('websocket-extensions/lib/parser'); [1,2,3].map(() => P.serializeParams('permessage-deflate', { foo: 'b c' })) -> permessage-deflate; foo="b c", permessage-deflate; foo=b c, permessage-deflate; foo="b c"
Verified by running
Node v22.14.0 against websocket-extensions@0.1.4 installed fresh, alone. Parser.serializeParams('permessage-deflate', {foo:'b c'}) three times: quoted, bare, quoted. CONTROL: {foo:15} gave permessage-deflate; foo=15 three times. PUBLIC PATH in the same process: new Extensions().add(ext).generateOffer() with an extension whose createClientSession().generateOffer() returns {foo:'b c'} gave x-demo; foo=b c, then x-demo; foo="b c", then x-demo; foo=b c. FIXED-SIDE CONTROL: dropping the g on parser.js:4 gave the quoted form on all six calls; restoring the file restored the alternation.
Checked against
websocket-extensions 0.1.4, the current release (dist-tags.latest on 2026-09-10; published 2020-06). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The content-disposition shape in a header serializer. REACH: websocket_extensions.js:50 (generateOffer), :81 (the error text in activate) and :105 (generateResponse) all call Parser.serializeParams with parameters the extension itself chose, and the one extension everybody ships, permessage-deflate, offers only numbers and booleans, which take the typeof-number branch and never touch the regex. So the divergence is reachable by an extension that offers a string parameter containing a space, quote or separator, which today is nearly nobody; recorded as modest for that reason. The package sits under websocket-driver, faye-websocket, sockjs and primus in this tree, which is why it was here at all. The fix is deleting the g flag on line 4; TOKEN beside it has none. NOT REPORTED UPSTREAM: the only advisory is CVE-2020-7662 (ReDoS in the parser, fixed in 0.1.4) and faye/websocket-extensions-node issues mention neither serializeParams nor quoting.

rate-limiter-flexible

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

types.d.ts:79, 256 and 260 declare `export class RateLimiterAbstract`, `RateLimiterInsuredAbstract` and `RateLimiterStoreAbstract` from the package root; index.js exports 28 names and none of the three, although lib/RateLimiterAbstract.js, lib/RateLimiterInsuredAbstract.js and lib/RateLimiterStoreAbstract.js all ship. README line 217 says "Any new limiter with storage must be extended from `RateLimiterStoreAbstract`", so the documented extension point typechecks and throws.

const r = require('rate-limiter-flexible'); (() => { try { class MyStore extends r.RateLimiterStoreAbstract {} return 'extended'; } catch (e) { return 'RateLimiterCompatibleAbstract=' + typeof r.RateLimiterCompatibleAbstract + ' RateLimiterMemory=' + typeof r.RateLimiterMemory + ' while class extends RateLimiterStoreAbstract threw ' + e.constructor.name + ': ' + e.message; } })() -> RateLimiterCompatibleAbstract=function RateLimiterMemory=function while class extends RateLimiterStoreAbstract threw TypeError: Class extends value undefined is not a constructor or null
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against rate-limiter-flexible@11.2.0 installed fresh, alone. typeof of the three names on the root module: undefined, undefined, undefined; neighbours RateLimiterCompatibleAbstract and RateLimiterMemory: function, function; Object.keys of the module lists 28 names of which the only Abstract is RateLimiterCompatibleAbstract; typeof require("rate-limiter-flexible/lib/RateLimiterStoreAbstract") is function, so the value ships one level down. `class MyStore extends r.RateLimiterStoreAbstract {}` threw TypeError: Class extends value undefined is not a constructor or null. THE TYPE HALF WAS COMPILED: tsc --strict --module commonjs --moduleResolution node10 accepted the same two lines and exited 0. FIXED-SIDE CONTROL: appending three module.exports lines to index.js made typeof RateLimiterStoreAbstract function and the extends succeed; restoring the file restored the TypeError.
Checked against
rate-limiter-flexible 11.2.0, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE NEIGHBOUR IS THE CONTROL: RateLimiterCompatibleAbstract, declared at types.d.ts:30 in the same hand-written file and named in README line 229 as the other extension point, IS exported from index.js, so the maintainers export abstract bases on purpose and missed these three. The logform shape, but with the README pointing consumers at the missing name: a TypeScript consumer following line 217 writes `import { RateLimiterStoreAbstract } from "rate-limiter-flexible"; class MyStore extends RateLimiterStoreAbstract`, the compiler accepts it, and the module throws at class-definition time. The JavaScript workaround is a deep require of lib/RateLimiterStoreAbstract (the package has no exports map), which is what issue #85 ("Adding a new Rate Limiter Store", 2020) implies people do. Three lines in index.js. NOT REPORTED UPSTREAM: animir/node-rate-limiter-flexible issues searched for RateLimiterAbstract return #284, #356, #320, #85, #109 and #112, none about the missing export.

mongodb

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

mongodb.d.ts declares 21 runtime values the driver does not export: Batch, BulkOperationBase, BulkWriteResult, FindOperators, HostAddress, LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS, ListSearchIndexesCursor, MONGO_CLIENT_EVENTS, MongoCredentials, MongoDBCollectionNamespace, MongoDBNamespace, RunCommandCursor, ServerDescription, ServerMonitoringMode, ServerSession, StreamDescription, TopologyDescription, TypedEventEmitter, WriteConcernError, WriteError. DERIVED: src/index.ts exports every one of them under "// type only exports below, these are removed from emitted JS" with `export type { ... }`, and api-extractor's rollup drops the `type` and emits `export declare class Batch<T = Document> {` (mongodb.d.ts:932), so the omission is api-extractor's (microsoft/rushstack#3616, open since 2022-09-05) and this row exists because the driver's published declaration inherits it.

const m = require('mongodb'); (() => { try { new m.HostAddress('localhost:27017'); return 'constructed'; } catch (e) { return 'MongoClient=' + typeof m.MongoClient + ' while new HostAddress() threw ' + e.constructor.name + ': ' + e.message; } })() -> MongoClient=function while new HostAddress() threw TypeError: m.HostAddress is not a constructor
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against mongodb@7.6.0 installed fresh, alone. Every `export declare class|const|function|enum` name in mongodb.d.ts was checked against the CJS module: 131 declared values, 21 absent (the list in the summary); neighbours MongoClient and ObjectId are functions. THE TYPE HALF WAS COMPILED: `import { Batch, HostAddress, ListSearchIndexesCursor, MongoClient } from "mongodb"; new HostAddress("localhost:27017")` under --strict --module commonjs --moduleResolution node10 exited 0, and the same line at runtime threw TypeError: m.HostAddress is not a constructor. The upstream mechanism was read from mongodb/node-mongodb-native src/index.ts at HEAD via the GitHub contents API. No fixed-side control: the fix is in a build tool, not a one-line edit to the shipped file.
Checked against
mongodb 7.6.0, the current release (dist-tags.latest on 2026-09-10); 23 names at the 6.21.0 in the batch tree, 21 at 7.6.0. Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. A DERIVED INSTANCE, recorded so the class's reach is visible: the driver's source says exactly what it means -- `export type { Batch, BulkOperationBase, FindOperators, ... }`, `export type { LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS, Stream }`, `export type { ..., HostAddress, ... }` -- and the shipped mongodb.d.ts says the opposite, because api-extractor's .d.ts rollup does not preserve type-only exports (rushstack#3616 describes this exact transformation, `export type { SomeClass }` in, `export declare class SomeClass {}` out). So `new HostAddress('h:1')` and `new Batch(...)` typecheck against the driver's own declaration and throw. The fix is upstream in api-extractor or a post-processing step in the driver's build; a report to the driver would be a heads-up that its published types over-promise 21 names. MongoDB's JIRA (project NODE) searched for 'Batch is not a constructor', 'HostAddress export' and 'api-extractor export type' returns nothing; GitHub issues are disabled on the repository.

bson

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

bson.d.ts:1323 declares `export declare const LongWithoutOverridesClass: LongWithoutOverrides;` and the runtime has it only as a module-local (lib/bson.cjs:3195 `const LongWithoutOverridesClass = Long;`, never assigned to exports). DERIVED: src/bson.ts:39 is `export type { LongWithoutOverridesClass } from './timestamp';` and api-extractor's rollup drops the `type`, the same omission as the mongodb row (microsoft/rushstack#3616).

const b = require('bson'); 'LongWithoutOverridesClass=' + typeof b.LongWithoutOverridesClass + ' Long=' + typeof b.Long + ' Timestamp=' + typeof b.Timestamp -> LongWithoutOverridesClass=undefined Long=function Timestamp=function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against bson@7.3.2 installed fresh, alone. Every export-declared value in bson.d.ts checked against the module: 32 declared, 1 absent; Long and Timestamp are functions and Object.getPrototypeOf(Timestamp) === Long, so the runtime base is there under its real name. THE TYPE HALF WAS COMPILED: `import { LongWithoutOverridesClass, Long } from "bson"; const v: unknown = LongWithoutOverridesClass` exited 0. src/bson.ts:39 read from mongodb/js-bson at HEAD via the GitHub contents API. No fixed-side control, for the reason given on the mongodb row.
Checked against
bson 7.3.2, the current release (dist-tags.latest on 2026-09-10); also at the 6.10.4 in the batch tree. Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The smallest instance of the api-extractor shape: one name, the base class the declaration file itself uses at bson.d.ts:1647 (`export declare class Timestamp extends LongWithoutOverridesClass`), so a consumer who reads the types and writes `LongWithoutOverridesClass` in value position -- to extend it, or to check `instanceof` -- gets undefined. Recorded beside the mongodb row because the same build produces both and the fix is the same place. MongoDB's JIRA searched for LongWithoutOverridesClass returns only NODE-2624 (Timestamp as an unsigned long, backlog), which is not this.

csv-generate

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['./stream'].require.default names ./dist/cjs/stream.cjs and the package ships dist/cjs/index.cjs, index.d.cts, stream.d.cts, sync.cjs and sync.d.cts -- the CJS declaration for the subpath and no CJS module. require('csv-generate/stream') fails MODULE_NOT_FOUND while import('csv-generate/stream') resolves lib/stream.js and require('csv-generate/sync'), the sibling subpath, resolves.

(() => { try { require('csv-generate/stream'); return 'loaded'; } catch (e) { const control = typeof require('csv-generate/sync').generate; return e.code + ' on ' + e.message.split('\n')[0].replace(/^.*node_modules\//, '') + '; control require(csv-generate/sync).generate is ' + control; } })() -> MODULE_NOT_FOUND on csv-generate/dist/cjs/stream.cjs'; control require(csv-generate/sync).generate is function
Verified by running
Node v22.14.0 against csv-generate@4.6.1 installed fresh, alone. require('csv-generate/stream') threw MODULE_NOT_FOUND naming dist/cjs/stream.cjs; the controls require('csv-generate') (typeof generate function) and require('csv-generate/sync') resolved; import('csv-generate/stream') resolved with keys generate. ls dist/cjs confirmed the five files above. The rollup inputs and the build:ts line were read from the repository at HEAD via the GitHub contents API. No fixed-side control: the fix is a build input, not a one-line edit to a shipped file.
Checked against
csv-generate 4.6.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE BUILD NEVER PRODUCES THE FILE THE MANIFEST NAMES: packages/csv-generate/rollup.config.js at adaltas/node-csv HEAD has inputs lib/index.js (lines 15, 42) and lib/sync.js (61, 88) and no lib/stream.js, while the build:ts script (package.json:92) copies lib/stream.d.ts to dist/cjs/stream.d.cts -- so the types condition beside the broken default resolves and the streamx shape holds: a TypeScript CJS consumer of `csv-generate/stream` compiles clean and cannot load. The subpath is real (lib/stream.js exports generate over node:stream/web) and is delivered to ESM consumers only. One rollup input, or dropping the require branch from that subpath. NOT REPORTED UPSTREAM: no advisory affects csv-generate; adaltas/node-csv issues searched for 'csv-generate/stream' and 'stream.cjs' return nothing naming the subpath's CJS entry. REPRODUCE REWRITTEN 2026-09-11 to the exact string the expression renders, from running the generated witness against a fresh install: same trimming of the machine path as the unfetch row.

agenda

1 finding

undelivered-entryAlready reportedNot a vulnerability

UNDELIVERED_ENTRY

exports['./testing'] names ./test/shared/index.ts for types, import and default, and the manifest's files field is ["dist"], so the published tarball holds LICENSE.md, README.md, dist and package.json and nothing under test/. import('agenda/testing') fails ERR_MODULE_NOT_FOUND while import('agenda') resolves.

(async () => { try { await import('agenda/testing'); return 'loaded'; } catch (e) { const control = typeof (await import('agenda')).Agenda; return e.code + ' on ' + e.message.split('\n')[0].replace(/^.*node_modules\//, '').replace(/' imported from.*$/, "'") + '; control import(agenda).Agenda is ' + control; } })() -> ERR_MODULE_NOT_FOUND on agenda/test/shared/index.ts'; control import(agenda).Agenda is function
Verified by running
Node v22.14.0 against agenda@6.2.6 installed fresh, alone, from an ES module: import('agenda') resolved and typeof Agenda was function; import('agenda/testing') threw ERR_MODULE_NOT_FOUND naming test/shared/index.ts; ls of the package directory showed no test/ and the manifest's files field printed ["dist"].
Checked against
agenda 6.2.6, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The manifest advertises a conformance-test subpath for custom backends and points every condition at a .ts source file that the files field excludes -- and even if it shipped, a .ts target under an import condition is not loadable by Node without a loader. A human already wrote this up: #1803 says the suite 'is not published anywhere, nor exported in any existing published package', which is the same fact seen from the consumer's side; the maintainer-side fact is that the export IS declared and the target is not delivered. Recorded as a reached-independently row, not a discovery. REPRODUCE REWRITTEN 2026-09-11 to the exact string the expression renders, from running the generated witness against a fresh install: the bare import rendered the error with the absolute path of this machine; the expression now trims it to the package-relative path, as the unfetch row does. Prior report: agenda/agenda#1803 (open, 2026-07-28) asks for exactly this subpath to be published; the finder reached it independently from the manifest.

mailgun.js

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['./definitions'].browser.require names ./AMD/definitions.js and the AMD directory holds definitions.amd.js and mailgun.amd.js, so a browser bundler resolving the subpath for a CommonJS consumer (conditions browser + require) lands on a file that does not exist. The three other entries the scanner flagged -- exports['.'].node.default -> ./CJS/mailgun.node.js, exports['./definitions'].node.default -> ./CJS/definitions.js and exports['./definitions'].browser.default -> ./AMD/definitions.js -- are also missing files but are unreachable: import or require always matches before default inside those objects.

walking exports['./definitions'] with the condition set {browser, require} in manifest key order -> ./AMD/definitions.js, and fs.existsSync of it is false; with {node, require} it is ./CJS/definitions.cjs, which exists, and require('mailgun.js/definitions') resolves
Verified by running
Node v22.14.0 against mailgun.js@14.0.1 installed fresh, alone. fs.existsSync on the seven targets: CJS/mailgun.node.js false, CJS/mailgun.node.cjs true, CJS/definitions.js false, CJS/definitions.cjs true, AMD/definitions.js false, AMD/definitions.amd.js true, AMD/mailgun.amd.js true. A twenty-line walk of the exports objects in manifest key order (first key in the enabled set wins, default always enabled) gave: '.' -> existing files under {node,require}, {node,import}, {browser,require}, {browser,import}; './definitions' -> existing files under three of the four sets and ./AMD/definitions.js (missing) under {browser,require}. CONTROLS: require and import of both 'mailgun.js' and 'mailgun.js/definitions' all succeeded in Node.
Checked against
mailgun.js 14.0.1, the current release (dist-tags.latest on 2026-09-10); the manifest at mailgun/mailgun.js HEAD carries the same three targets at lines 47, 62 and 66-67. Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. ONE OF THE FOUR SCANNER HITS IS REACHABLE AND THREE ARE NOT, and the row says which: Node always has one of import/require enabled, and a bundler's browser condition set always carries one too, so a `default` key listed after both inside the same condition object is dead; the finder reported it as ERR_MODULE_NOT_FOUND for 'a consumer importing exactly as the manifest instructs', which no consumer can do. The browser.require entry is different: webpack with target web resolving require('mailgun.js/definitions') matches browser then require and is handed ./AMD/definitions.js, a file the build wrote as definitions.amd.js. Every Node path resolves, so this is a modest row about a rarely-taken branch, not a broken package; the same slip is visible as .js where the built file says .amd.js/.cjs, which reads like the manifest predating a rename. NOT REPORTED UPSTREAM: no advisory affects mailgun.js; mailgun/mailgun.js issues searched for 'exports definitions' return #364 (2023, TypeScript errors), which is not this.

express-validator

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ">= 8.0.0" and five shipped, entry-reachable files use optional chaining, which is Node 14: lib/express-validator.js:78 `const formatter = this.options?.errorFormatter;`, lib/validation-result.js:25, lib/context.js:70, lib/field-selection.js:132, lib/middlewares/exact.js:28. The package's own README line 21 says "make sure that you have Node.js 14 or newer", so the manifest contradicts the documentation and the code agrees with the documentation.

require('express-validator/package.json').engines.node -> ">= 8.0.0", beside lib/express-validator.js:78 `this.options?.errorFormatter`, which Node 8 through 13 cannot parse
Verified by running
Read against express-validator@7.3.2 installed fresh, alone: the engines field and the five lines above were printed from the installed package, and README.md:21 was read from it. The syntax claim is settled by the grammar (optional chaining shipped in V8 8.0, Node 14.0) and was not executed on a Node 8 through 13, none of which is installed here.
Checked against
express-validator 7.3.2, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. Real, and not worth a maintainer's attention -- the napi-postinstall and signal-exit judgement: the admitted-but-broken window is Node 8.0 through 13.x, every one of which is a dead line (Node 8 end-of-life 2019-12-31, 10 on 2021-04-30, 12 on 2022-04-30, 13 on 2020-06-01), and on such a Node loading the main entry is a SyntaxError before any of the package's code runs, which nobody would mistake for anything else. The one-line fix is changing 8.0.0 to 14 to match the README. NOT REPORTED UPSTREAM: express-validator/express-validator issues searched for 'engines' return nothing and 'node 8' returns unrelated threads.

mysql2

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ">= 8.0" and two shipped, entry-reachable files use optional chaining, which is Node 14: lib/tracing.js:16 `const hasTracingChannel = typeof dc?.tracingChannel === 'function';` and lib/pool_cluster.js:148 `if (node?.pool.config.connectionConfig.trace) {`.

require('mysql2/package.json').engines.node -> ">= 8.0", beside lib/tracing.js:16 `dc?.tracingChannel`, which Node 8 through 13 cannot parse
Verified by running
Read against mysql2@3.24.4 installed fresh, alone: the engines field and the two lines above were printed from the installed package. Not executed on a Node 8 through 13; none is installed here.
Checked against
mysql2 3.24.4, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. Real, and not worth a maintainer's attention, for the same reason as the express-validator row: the window is Node 8.0 through 13.x, all end-of-life, and the failure on them is a SyntaxError at load. The README's Node.js version badge links nodejs.org/en/download rather than stating a floor, so there is no documentation to contradict. NOT REPORTED UPSTREAM: sidorares/node-mysql2 issues searched for 'engines' and 'optional chaining' return nothing on point; the seven advisories against mysql2 are unrelated.

luxon

1 finding

engines-below-syntaxAlready reportedNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ">=12" and the ESM entry the exports map hands to `import` -- build/es6/luxon.mjs:3248 `let sum = vals.milliseconds ?? 0;` -- uses nullish coalescing, which is Node 14. The require entry, build/node/luxon.js, was not flagged, so on Node 12 or 13 the package loads by require and fails by import.

require('luxon/package.json').engines.node -> ">=12", beside build/es6/luxon.mjs:3248 `vals.milliseconds ?? 0`, which Node 12 and 13 cannot parse
Verified by running
Read against luxon@3.7.2 installed fresh, alone: the engines field, the exports map ({ import: ./build/es6/luxon.mjs, require: ./build/node/luxon.js }) and line 3248 were printed from the installed package. Not executed on Node 12 or 13.
Checked against
luxon 3.7.2, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fourth probe sweep, the framework ecosystem (servers, body parsers, sessions, auth, ORMs and drivers, queues, loggers, validators, fs and archive tools, HTML/markdown, templating): the four package-level probes over 1,038 installed packages, 10 proven, 1,109 clean, 2,124 abstained, 49 timed out; and the reading finders over the same 1,038 packages, 20,139 files, 37 hits, 11 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. Already written up by a human two years ago and still open, which puts it in the reached-independently column rather than the discovery one. The judgement is the same as the other two engines rows: Node 12 and 13 are dead lines and the fix is one character in the manifest. Recorded because the split between the two entries -- CJS fine, ESM SyntaxError, on the same Node -- is the more confusing failure of the three. Prior report: moment/luxon#1510 "Nullish-coalescing operator not supported in Node v12" (open since 2023-09-17); the finder reached it independently.

serialize-javascript

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

index.js:14 declares IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g and serializeFunc (index.js:209) uses it only as .test(serializedFn), so the guard that refuses to serialize a native function alternates with lastIndex: the same native function throws on one call and on the next is emitted as the literal text `function max() { [native code] }`, which is not parseable JavaScript.

const serialize = require('serialize-javascript'); [1,2,3].map(() => { try { return serialize(Math.max); } catch (e) { return 'threw ' + e.constructor.name + ': ' + e.message; } }) -> ["threw TypeError: Serializing native function: max","function max() { [native code] }","threw TypeError: Serializing native function: max"]. The second of three identical calls emits the native function's toString as if it were source
Verified by running
Node v22.14.0 against serialize-javascript@7.1.1 installed fresh with --ignore-scripts, alone. serialize(Math.max) three times in one process: threw TypeError 'Serializing native function: max', then returned the string 'function max() { [native code] }', then threw again. CONTROL in the same run: serialize(() => 1) answered '()=>1' three times. FIXED-SIDE CONTROL: changing index.js:14 to /\{\s*\[native code\]\s*\}/ (no g) made all three calls throw the TypeError with the control unchanged; restoring the published file restored the alternation. The batch tree's copy (also 7.1.1) alternated identically.
Checked against
serialize-javascript 7.1.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The content-disposition shape one call deep: the first .test matches inside 'function max() { [native code] }' and leaves lastIndex past the match, the next .test on the identical string starts there and fails, and the function falls through to the escaping path and is emitted verbatim. THE GUARD IS THE PACKAGE'S OWN STATED CONTRACT: the throw exists because a native function has no serializable body, and the output on the even calls is text that eval() rejects with a SyntaxError, so a consumer that serializes an options object containing e.g. `Number` or `Math.max` gets an exception on the first pass and unusable output on the second. NOT REPORTED UPSTREAM: yahoo/serialize-javascript issues searched for 'lastIndex' and 'IS_NATIVE_CODE_REGEXP' return nothing; #179 (2024, open, 'Fails on native functions, like Number or String') and #61 (2019, closed) report the throw itself and describe it as deterministic, which this shows it is not -- #179's own repro `serialize({ type: Number })` throws once and silently emits invalid text on the next call in the same process. The fix is deleting the g flag, which .test never needs. Two vendored copies alternate the same way and are recorded as DERIVED rows: terser-webpack-plugin 5.6.1 and minimizer-webpack-plugin 5.10.1 ship dist/serialize-javascript.js with the identical lines 27 and 156.

terser-webpack-plugin

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

terser-webpack-plugin 5.6.1 vendors serialize-javascript as dist/serialize-javascript.js; line 27 declares IS_NATIVE_CODE_REGEXP with the g flag and line 156 uses it only as .test, so the native-function guard alternates exactly as in the upstream file. DERIVED: the branch is serialize-javascript's, copied into this package's dist.

const serialize = require('terser-webpack-plugin/dist/serialize-javascript.js'); [1,2,3].map(() => { try { return serialize(Math.max); } catch (e) { return 'threw ' + e.constructor.name + ': ' + e.message; } }) -> ["threw TypeError: Serializing native function: max","function max() { [native code] }","threw TypeError: Serializing native function: max"]
Verified by running
Node v22.14.0 against terser-webpack-plugin@5.6.1 installed fresh, alone. require('terser-webpack-plugin/dist/serialize-javascript.js')(Math.max) three times: threw, 'function max() { [native code] }', threw. The batch tree's copy answered identically.
Checked against
terser-webpack-plugin 5.6.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
A DERIVED INSTANCE, recorded so the class's reach is visible and not as a second independent defect: the regex and the .test are serialize-javascript's, vendored verbatim (dist/serialize-javascript.js:27 and :156 against upstream index.js:14 and :209). Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The package serializes minimizer options for its worker threads through this copy, so an option value that is a native function trips the alternating guard; the package has no exports map, which is why the deep require above loads it. The fix is upstream's one-character fix carried into the vendored file, or re-vendoring after upstream fixes it. NOT REPORTED UPSTREAM.

minimizer-webpack-plugin

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

minimizer-webpack-plugin 5.10.1 vendors serialize-javascript as dist/serialize-javascript.js; line 27 declares IS_NATIVE_CODE_REGEXP with the g flag and line 156 uses it only as .test, so the native-function guard alternates exactly as in the upstream file. DERIVED: the branch is serialize-javascript's, copied into this package's dist.

const serialize = require('minimizer-webpack-plugin/dist/serialize-javascript.js'); [1,2,3].map(() => { try { return serialize(Math.max); } catch (e) { return 'threw ' + e.constructor.name + ': ' + e.message; } }) -> ["threw TypeError: Serializing native function: max","function max() { [native code] }","threw TypeError: Serializing native function: max"]
Verified by running
Node v22.14.0 against minimizer-webpack-plugin@5.10.1 installed fresh, alone. require('minimizer-webpack-plugin/dist/serialize-javascript.js')(Math.max) three times: threw, 'function max() { [native code] }', threw. The batch tree's copy answered identically.
Checked against
minimizer-webpack-plugin 5.10.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
A DERIVED INSTANCE, the same vendored file as terser-webpack-plugin's (dist/serialize-javascript.js:27 and :156), recorded so the class's reach is visible. Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. No exports map, so the deep require loads the copy directly. NOT REPORTED UPSTREAM.

nx

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

dist/src/tasks-runner/utils.js:259 declares replacementRegex = /{([\s\S]+?)}/g and interpolate (line 262) uses it as .test(template) to decide whether a task output path needs interpolation at all; the reset is the .replace at line 276, which only runs on the path that did not throw. When a template fails the `{workspaceRoot}`-not-at-start check (line 265) after the .test has matched, lastIndex stays past the match, and the next interpolate of a different, valid template returns it verbatim with `{projectRoot}` still in it.

const { interpolate } = require('nx/src/tasks-runner/utils'); const data = { projectRoot: 'apps/a', projectName: 'a', workspaceRoot: '/w' }; const seq = []; try { interpolate('dist/{workspaceRoot}', data); } catch (e) { seq.push('threw'); } seq.push(interpolate('{projectRoot}/dist', data), interpolate('{projectRoot}/dist', data)); seq -> ["threw","{projectRoot}/dist","apps/a/dist"]. After one invalid output throws, the next identical valid call returns the raw template and the one after that interpolates it
Verified by running
Node v22.14.0 against nx@23.2.1 installed fresh with --ignore-scripts, alone. interpolate('dist/{workspaceRoot}', data) threw "Output 'dist/{workspaceRoot}' is invalid"; the next interpolate('{projectRoot}/dist', data) returned '{projectRoot}/dist'; the same call again returned 'apps/a/dist'. CONTROL in a fresh process with no throw first: 'apps/a/dist' twice. FIXED-SIDE CONTROL: changing line 262 to `template.startsWith('/') || template.search(replacementRegex) === -1` gave ['threw','apps/a/dist','apps/a/dist','apps/a/a']; restoring the published file restored the raw template. The batch tree's nx 23.2.1 behaved identically.
Checked against
nx 23.2.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE SPLIT ON LINE 268 IGNORES lastIndex AND THE REPLACE ON 276 RESETS IT, which is why the divergence needs the throw between them: test matches on 'dist/{workspaceRoot}' (lastIndex 20), line 265 throws, and the next call's test on the 18-character '{projectRoot}/dist' starts past its end and fails, so line 263 returns the template untouched and the outputs of that task are the literal path '{projectRoot}/dist'. THE FUNCTION IS PUBLIC: nx exports './src/*' to './dist/src/*.js', dist/src/devkit-internals.js:102 re-exports interpolate, and @nx/devkit's dist/internal.js:200 re-exports it again for plugin authors, so `import { interpolate } from '@nx/devkit/internal'` is the documented reach beside getOutputsForTargetAndConfiguration (utils.js:224), which calls it for every declared output. WHAT THIS NEEDS to matter, stated plainly: the throw at 265 must be caught in the same process and interpolate called again -- a one-shot CLI run dies at the throw, so the reach is the long-lived nx daemon and plugin workers, where one project's malformed output would leave the next project's outputs uninterpolated; that in-process catch was not demonstrated here. The fix is one line: `template.search(replacementRegex) === -1` on line 262 (search ignores and restores lastIndex), or a second non-global regex for the test. NOT REPORTED UPSTREAM: nrwl/nx issues searched for 'interpolate lastIndex' and 'replacementRegex' return nothing.

postcss-normalize-url

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

src/index.js:13 declares escapeChars = /([\s\(\)"'])/g and transformDecl (line 118) tests it before checking url.type === 'string'; when the test matches on an unquoted url() whose value carries an escaped space, the type check fails, the .replace on line 119 that would have reset lastIndex never runs, and the next quoted url in the same stylesheet with a space at an earlier offset fails the test, falls into the else branch at line 126 (`url.type = 'word'`), and is emitted unquoted and unescaped: url("a b") becomes url(a b), which is not valid CSS.

const postcss = require('postcss'); const plugin = require('postcss-normalize-url'); postcss([plugin()]).process('a{background:url(a\\ b)}b{background:url("a b")}', { from: undefined }).css -> a{background:url(a\ b)}b{background:url(a b)}. The second declaration lost its quotes and its escape; with the two rules in the other order both come out as url(a\ b)
Verified by running
Node v22.14.0 against postcss-normalize-url@9.0.2 installed fresh, alone, with postcss 8. One stylesheet, two rules: 'a{background:url(a\ b)}b{background:url("a b")}' produced 'a{background:url(a\ b)}b{background:url(a b)}'. CONTROL: the same two rules in the other order produced url(a\ b) for both, and three runs of a lone quoted url("c d") produced url(c\ d) each time. FIXED-SIDE CONTROL: reordering line 118 to `url.type === 'string' && escapeChars.test(url.value)` produced url(a\ b) for both rules in both orders; restoring the file restored url(a b). At 8.0.4 in the batch tree the same input produced the same wrong output.
Checked against
postcss-normalize-url 9.0.2, the current release (dist-tags.latest on 2026-09-10). The batch tree held 8.0.4, pinned by cssnano 8.0.10, where the same lines sit at 14, 119 and 120. Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. A MINIFIER THAT EMITS INVALID CSS FROM VALID CSS, deterministically, from one input file: url(a\ b) is a legal unquoted URL with an escaped space, postcss-value-parser hands it over as a word node, escapeChars.test matches the space and leaves lastIndex at 3, and the quoted url("a b") that follows is three characters long, so the next test starts at its end and reports no escapable character. The else branch then does what it does for every clean value -- strips the quotes -- and the space is emitted bare. THE SAME SYMPTOM WAS FIXED ONCE BEFORE FOR A DIFFERENT CAUSE: cssnano/cssnano#362 (2017, closed, 'URL with space gets unquoted') is exactly this output, url(/folder/name with space.png), and the fix then was the escaping branch that lastIndex now skips. THROUGH cssnano ITSELF this input came out as `a,b{background:url(a\ b)}` -- correct, and merged -- so cssnano's default preset is not shown to reach it by this witness; the direct plugin and any preset that orders it differently are. The fix is one line: swap the two conditions on line 118 so the test runs only when the replace that resets it will follow, or use a non-global copy for the test. NOT REPORTED UPSTREAM: cssnano/cssnano issues searched for 'normalize-url lastIndex' and 'normalize-url escape' return nothing on the mechanism.

tsx

1 finding

cjs-binding-in-esmNo prior reportNot a vulnerability

CJS_BINDING_IN_DECLARED_ESM

The ESM build of the CommonJS register API, dist/register-DHgpdRjs.mjs (loaded for `import { register } from 'tsx/cjs/api'`), passes the bare identifier `module` as the parent argument of _resolveFilename inside the scopedResolve it returns when register() is given a namespace; an ES module has no such binding, so register({ namespace }).resolve(...) throws ReferenceError from any importer that reached the .mjs build, while the same call from CommonJS resolves.

In an .mjs file: import { register } from 'tsx/cjs/api'; register({ namespace: 'n' }).resolve('./x.ts', import.meta.url) -> ReferenceError: module is not defined. The same two lines in a .cjs file answer x.ts?namespace=n
Verified by running
Node v22.14.0 against tsx@4.23.13 installed fresh, alone. r.mjs (import { register } from 'tsx/cjs/api'; register({ namespace: 'n' }).resolve('./x.ts', import.meta.url)) printed 'ReferenceError: module is not defined'. CONTROL: r.cjs with require and __filename printed the resolved path '<dir>/x.ts?namespace=n'. The minified .mjs contains `o(v+Se(g),module,!1,b)},"scopedResolve")` and the .cjs the same call with `module` defined. The batch tree's tsx 4.23.13 gave the same two answers.
Checked against
tsx 4.23.13, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE SOURCE IS ONE FILE COMPILED TWICE: src/cjs/api/register.ts:139-144 at privatenumber/tsx HEAD is `return resolveFilename(request + urlSearchParamsStringify(parameters), module, false, resolveOptions)`, where `module` is the CommonJS wrapper binding of that file; the build emits both register-*.cjs, where it exists, and register-*.mjs, where it does not, and package.json exports './cjs/api' with an import condition pointing at the .mjs. Only the namespace branch reaches it -- register() without a namespace returns no resolve -- so the documented feature that breaks is the namespaced API's resolve(), and it breaks on the ESM side only. NOTE ON THE WITNESS: `node --input-type=module -e` defines `module` as a global and hides this, so the reproduce is a file. NOT REPORTED UPSTREAM: privatenumber/tsx issues searched for 'module is not defined', 'namespace resolve' and 'api.resolve' return nothing on point.

@vue/compiler-sfc

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

dist/compiler-sfc.d.ts:348 declares `export declare class ScriptCompileContext`, but packages/compiler-sfc/src/index.ts:68 exports it as `export type { ScriptCompileContext }`, so no build carries the value: require('@vue/compiler-sfc').ScriptCompileContext is undefined, and a consumer that writes `new ScriptCompileContext(...)` typechecks and throws 'is not a constructor'.

(() => { const s = require('@vue/compiler-sfc'); const control = typeof s.compileScript; try { new s.ScriptCompileContext({}, {}); return 'control compileScript=' + control + ' and ScriptCompileContext constructed'; } catch (e) { return 'control compileScript=' + control + ' while new ScriptCompileContext() threw ' + e.constructor.name + ': ' + e.message; } })() -> control compileScript=function while new ScriptCompileContext() threw TypeError: s.ScriptCompileContext is not a constructor
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against @vue/compiler-sfc@3.5.42 installed fresh with --ignore-scripts, alone. typeof require('@vue/compiler-sfc').ScriptCompileContext is undefined while compileScript is a function; `new s.ScriptCompileContext({}, {})` threw TypeError 's.ScriptCompileContext is not a constructor'. THE TYPE HALF WAS COMPILED: tsc --strict --module commonjs --moduleResolution node10 accepted `new ScriptCompileContext({} as any, {} as any)` and exited 0. FIXED-SIDE CONTROL: editing the d.ts to `declare class ScriptCompileContext` and appending `export type { ScriptCompileContext };` made tsc report TS1362 "'ScriptCompileContext' cannot be used as a value because it was exported using 'export type'"; restoring the file restored exit 0. src/index.ts:68 was read from vuejs/core HEAD via the GitHub contents API.
Checked against
@vue/compiler-sfc 3.5.42, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE TYPE-ONLY INTENT IS IN THE SOURCE AND THE DECLARATION LOST IT: `export type { X }` of a class is exactly the case TypeScript's own emit would render as a type-only re-export, but the bundled d.ts inlines the class as `export declare class`, which promises a constructor. tsc --strict accepted `new ScriptCompileContext({} as any, {} as any)` with exit 0 and Node threw on the same line. The declaration is used by the exported TypeResolveContext types (d.ts:404-422), which is why the class must appear in the file; the one-line repair is making it `declare class ScriptCompileContext` plus `export type { ScriptCompileContext }`, which turns the consumer's `new` into TS1362 at compile time. The probe found it as 1 of 19 declarations; the other 18 are exported at runtime. NOT REPORTED UPSTREAM: vuejs/core issues searched for 'ScriptCompileContext' return five issues that mention the type in stack traces and none about it being declared as a value.

source-map

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

source-map.d.ts:313 and :325 declare `export const BasicSourceMapConsumer` and `export const IndexedSourceMapConsumer`; source-map.js exports only SourceMapGenerator, SourceMapConsumer and SourceNode, although lib/source-map-consumer.js defines and exports both classes. A TypeScript consumer of the two subclass constructors typechecks and reads undefined.

(() => { const s = require('source-map'); return 'BasicSourceMapConsumer=' + typeof s.BasicSourceMapConsumer + ' IndexedSourceMapConsumer=' + typeof s.IndexedSourceMapConsumer + ' while SourceMapConsumer=' + typeof s.SourceMapConsumer + ' and require(source-map/lib/source-map-consumer).BasicSourceMapConsumer=' + typeof require('source-map/lib/source-map-consumer').BasicSourceMapConsumer; })() -> BasicSourceMapConsumer=undefined IndexedSourceMapConsumer=undefined while SourceMapConsumer=function and require(source-map/lib/source-map-consumer).BasicSourceMapConsumer=function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against source-map@0.8.0 installed fresh, alone. typeof of the two names on the root module: undefined, undefined; SourceMapConsumer: function; Object.keys: SourceMapGenerator, SourceMapConsumer, SourceNode; typeof require('source-map/lib/source-map-consumer').BasicSourceMapConsumer: function. tsc --strict --module commonjs --moduleResolution node10 accepted the import and exited 0. FIXED-SIDE CONTROL: appending two `exports.X = require('./lib/source-map-consumer').X` lines to source-map.js made both typeof function; restoring the file restored undefined.
Checked against
source-map 0.8.0, the current release (dist-tags.latest on 2026-09-10). 0.8.0 was published 2026-07-20 after seven years as 0.8.0-beta.0. Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE VALUE SHIPS ONE LEVEL DOWN, which is the rate-limiter-flexible shape: lib/source-map-consumer.js:173 defines class BasicSourceMapConsumer and exports both it and IndexedSourceMapConsumer, and the package has no exports map, so the deep require reaches them; the root entry never re-exports them and the hand-written d.ts says it does. The declaration also types SourceMapConsumer.with() and fromSourceMap() as returning these classes (d.ts:218-249), which is fine as types; the two `export const` lines are the part that lies. tsc --strict accepted `import { BasicSourceMapConsumer } from 'source-map'` and exited 0. Two lines in source-map.js fix it. NOT REPORTED UPSTREAM: mozilla/source-map issues searched for 'BasicSourceMapConsumer', 'IndexedSourceMapConsumer export' and '0.8.0 types' return nothing about the missing export.

get-uri

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

get-uri 8.0.1 forwards Authorization, Cookie and Proxy-Authorization across a cross-host redirect: dist/http.js:41 copies the caller's opts (headers included) into the request options, line 61 sends them, and on a 3xx line 90 calls http(newUri, opts) with the same opts, so every header the caller attached for the first host is re-sent to whatever host Location names.

(async () => { const http = require('node:http'); const { getUri } = require('get-uri'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); return await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { const first = this; const done = () => (first.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : 'the second host received authorization=' + seen.authorization + ' cookie=' + seen.cookie + ' proxy-authorization=' + seen['proxy-authorization'])); return getUri('http://localhost:' + first.address().port + '/start', { headers: { authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' } }).then((s) => (s.resume(), done()), done); }))); })() -> the second host received authorization=Bearer A cookie=c=C proxy-authorization=Basic P
Verified by running
Node v22.14.0 against get-uri@8.0.1 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN with its own finding sentence naming Authorization, Cookie and Proxy-Authorization. Two-server witness through getUri(): the first server on localhost answered 302 to http://127.0.0.1:<port>/next and the second host received authorization=Bearer A, cookie=c=C and proxy-authorization=Basic P with Host 127.0.0.1:<port>. The batch tree's get-uri 7.0.0 was PROVEN the same way.
Checked against
get-uri 8.0.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. NOT DERIVED: the redirect loop is get-uri's own (src/http.ts in TooTallNate/proxy-agents), built on node:http, with no strip of any header when newUri's host differs from url's; the only thing it recomputes across the hop is the protocol module (line 87-89). node-fetch and make-fetch-happen at least strip two of the three; this client strips none, which makes it the widest instance of the class in this file. THE REACH is narrower than a general HTTP client's: get-uri fetches PAC scripts for pac-proxy-agent and arbitrary URIs for its own callers, and the caller must have attached credentials for the first host -- but a PAC server that 302s to another origin then receives them. The fix is the node-fetch shape: drop the three headers when the redirect changes host. NOT REPORTED UPSTREAM: no advisory affects get-uri and TooTallNate/proxy-agents issues searched for 'get-uri redirect', 'get-uri authorization' and 'get-uri headers' return nothing on point. No draft written.

probe-image-size

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

probe-image-size 7.4.0 forwards Authorization and Proxy-Authorization across a cross-host redirect. DERIVED: http.js:17 sets follow_max: 10 and line 54 hands the caller's headers to needle.get, and the needle its range ^2.5.2 resolves (2.9.1) re-sends config.headers on every hop it follows, deleting only Cookie (lib/needle.js:563-566); needle 3.x deletes all three on a different origin, so the omission is a dependency floor one major behind.

(async () => { const http = require('node:http'); const probe = require('probe-image-size'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); return await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { const first = this; const done = () => (first.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : 'the second host received authorization=' + seen.authorization + ' cookie=' + seen.cookie + ' proxy-authorization=' + seen['proxy-authorization'])); return probe('http://localhost:' + first.address().port + '/start', { headers: { authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' } }).then(done, done); }))); })() -> the second host received authorization=Bearer A cookie=undefined proxy-authorization=Basic P
Verified by running
Node v22.14.0 against probe-image-size@7.4.0 installed fresh with --ignore-scripts, alone (needle 2.9.1 underneath). The package-level probe re-run on the fresh install alone reported PROVEN naming Authorization and Proxy-Authorization. Two-server witness through probe(url, { headers }): the second host received authorization=Bearer A and proxy-authorization=Basic P and no cookie; the call then rejected with 'unrecognized file format', as expected for a non-image body. The same witness through needle.get with follow_max: 5 from the same tree received the same two headers, and through needle@3.5.0 installed alone received none of the three (and, with needle's default follow_max of 0, the second host received no request at all). needle 3.5.0's lib/needle.js:565-568 was read for the deletes and the commit list for lib/needle.js was read via the GitHub API.
Checked against
probe-image-size 7.4.0, the current release (dist-tags.latest on 2026-09-10). needle resolves to 2.9.1 under its ^2.5.2 range; needle's own current release is 3.5.0. Checked 2026-09-10.
Note
A DERIVED INSTANCE, recorded so the class's reach is visible and not as an independent omission: the branch that re-sends the headers is needle 2.x's send_request loop, one dependency down. Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE OWNER FIXED IT AND THIS PACKAGE DID NOT FOLLOW: needle commit a1398bb (2026-03-12, 'Remove sensitive cookie/auth headers when redirecting to a different origin', released in 3.4.0/3.5.0) added `delete config.headers['authorization']` and `delete config.headers['proxy-authorization']` beside the cookie delete (3.5.0 lib/needle.js:565-568), and needle 3.5.0 installed alone forwarded none of the three in the same two-server witness; probe-image-size's package.json still declares needle ^2.5.2, so every install of the current probe-image-size gets the 2.x loop. needle 2.x itself is therefore old-version-only and gets no row; its consumer is the live instance. The fix here is a dependency bump to needle ^3.4.0. NOT REPORTED UPSTREAM: no advisory affects probe-image-size or needle for this; nodeca/probe-image-size issues searched for 'redirect' and 'authorization' return nothing on point, and tomas/needle issues searched for 'authorization redirect' and 'redirect strip headers' return nothing, which is consistent with the needle fix having landed by commit rather than by report. No draft written.

npm-registry-fetch

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

npm-registry-fetch 20.0.1 forwards Proxy-Authorization across a cross-host redirect while stripping Authorization and Cookie. DERIVED: it is a thin wrapper over make-fetch-happen 16.0.1, whose lib/fetch.js deletes two of the three confidential headers when the hostname changes, so the omission is make-fetch-happen's and this row exists because npm's registry client inherits it.

(async () => { const http = require('node:http'); const fetch = require('npm-registry-fetch'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); return await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { const first = this; const done = () => (first.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : 'the second host received proxy-authorization=' + seen['proxy-authorization'] + ' while authorization=' + seen.authorization + ' and cookie=' + seen.cookie)); return fetch('/start', { registry: 'http://localhost:' + first.address().port + '/', headers: { authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' }, retry: false }).then(done, done); }))); })() -> the second host received proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Node v22.14.0 against npm-registry-fetch@20.0.1 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN naming Proxy-Authorization only. Two-server witness through fetch('/start', { registry, headers, retry: false }): the second host received proxy-authorization=Basic P and neither authorization nor cookie.
Checked against
npm-registry-fetch 20.0.1, the current release (dist-tags.latest on 2026-09-10). with make-fetch-happen 16.0.1 underneath. Checked 2026-09-10.
Note
A DERIVED INSTANCE of the make-fetch-happen row already in this file, recorded so the class's reach is visible: the strip branch is make-fetch-happen's (and minipass-fetch's below it), one and two dependencies down. Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. npm-registry-fetch is the module the npm CLI uses for every registry request, and pacote (next row) sits on top of it, so the header a proxy-authenticated npm user configures reaches any host a registry 302s to. The fix is upstream. NOT REPORTED UPSTREAM: npm/npm-registry-fetch issues searched for 'redirect' return nothing on point. No draft written.

pacote

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

pacote 22.0.0 forwards Proxy-Authorization across a cross-host redirect while stripping Authorization and Cookie. DERIVED: tarball and manifest fetches go through npm-registry-fetch 20.0.1 and make-fetch-happen 16.0.1, whose lib/fetch.js owns the strip branch that is one header short.

(async () => { const http = require('node:http'); const pacote = require('pacote'); let seen = null; const b = http.createServer((q, s) => (seen = q.headers, s.end('ok'))); return await new Promise((resolve) => b.listen(0, '127.0.0.1', () => http.createServer((q, s) => (s.writeHead(302, { location: 'http://127.0.0.1:' + b.address().port + '/next' }), s.end())).listen(0, 'localhost', function () { const first = this; const done = () => (first.close(), b.close(), resolve(seen === null ? 'the redirect never reached the second host' : 'the second host received proxy-authorization=' + seen['proxy-authorization'] + ' while authorization=' + seen.authorization + ' and cookie=' + seen.cookie)); return pacote.tarball('http://localhost:' + first.address().port + '/start', { headers: { authorization: 'Bearer A', cookie: 'c=C', 'proxy-authorization': 'Basic P' }, cache: null, retry: false }).then(done, done); }))); })() -> the second host received proxy-authorization=Basic P while authorization=undefined and cookie=undefined
Verified by running
Node v22.14.0 against pacote@22.0.0 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN naming Proxy-Authorization only. Two-server witness through pacote.tarball(url, { headers, cache: null, retry: false }): the second host received proxy-authorization=Basic P and neither authorization nor cookie.
Checked against
pacote 22.0.0, the current release (dist-tags.latest on 2026-09-10). with npm-registry-fetch 20.0.1 and make-fetch-happen 16.0.1 underneath. Checked 2026-09-10.
Note
A DERIVED INSTANCE two layers above the make-fetch-happen row, recorded so the class's reach is visible: pacote is what npm install uses to fetch tarballs, so a remote tarball URL that 302s to another host collects the configured proxy credential. Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. The fix is upstream. NOT REPORTED UPSTREAM: npm/pacote issues searched for 'redirect proxy-authorization' return nothing. No draft written.

@nx/devkit

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['./internal-testing-utils'].default names ./dist/internal-testing-utils.js and .types names ./dist/internal-testing-utils.d.ts (with three more subpaths under ./internal-testing-utils/*), and the package ships neither: packages/devkit/tsconfig.lib.json at nrwl/nx HEAD lists internal-testing-utils.ts in exclude and includes only *.ts and src/**, so the build never emits the files the manifest and typesVersions name. require('@nx/devkit/internal-testing-utils') fails MODULE_NOT_FOUND while require('@nx/devkit/testing'), the sibling subpath, resolves.

(() => { try { require('@nx/devkit/internal-testing-utils'); return 'loaded'; } catch (e) { const control = typeof require('@nx/devkit/testing').createTreeWithEmptyWorkspace; return e.code + ' on ' + e.message.split('\n')[0].replace(/^.*node_modules\//, '') + '; control require(@nx/devkit/testing).createTreeWithEmptyWorkspace is ' + control; } })() -> MODULE_NOT_FOUND on @nx/devkit/dist/internal-testing-utils.js'; control require(@nx/devkit/testing).createTreeWithEmptyWorkspace is function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against @nx/devkit@23.2.1 installed fresh with --ignore-scripts, alone. require('@nx/devkit/internal-testing-utils') threw MODULE_NOT_FOUND naming dist/internal-testing-utils.js and require('@nx/devkit/internal-testing-utils/mock-fs') likewise; the control require('@nx/devkit/testing') loaded with createTreeWithEmptyWorkspace a function. tsc --strict --module nodenext on `import * as u from '@nx/devkit/internal-testing-utils'` reported TS2307. ls dist confirmed index, internal, ngcli-adapter, public-api and testing and no internal-testing-utils. The tsconfig and package.json were read from nrwl/nx HEAD via the GitHub contents API. No fixed-side control: the fix is a build input.
Checked against
@nx/devkit 23.2.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE BUILD NEVER PRODUCES THE FILE THE MANIFEST NAMES, the csv-generate shape: the exports map, the typesVersions block (package.json:41-52 at HEAD) and the @nx/nx-source condition all name internal-testing-utils, and the tsconfig excludes exactly that file, so the monorepo resolves it from source through its own condition and every published consumer gets MODULE_NOT_FOUND and TS2307. THE NAME SAYS INTERNAL, and that is the fair argument against filing: nothing in the documentation tells a plugin author to import it, so the consumers who hit this are the ones who read the exports map. Real, four subpaths, and one line in either the tsconfig or the manifest; recorded for the corpus, whether to file is Martin's call. NOT REPORTED UPSTREAM: nrwl/nx issues searched for 'internal-testing-utils', 'devkit internal-testing-utils' and 'mock-project-graph' return nothing on point.

@octokit/plugin-paginate-rest

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['./types'].types names ./dist-types/.d.ts -- a file with an empty basename -- and the package ships dist-types/types.d.ts beside seven other declaration files. The path is hard-coded in the repository's scripts/build.mjs:73-74, so every release carries it; `import type { PaginateInterface } from '@octokit/plugin-paginate-rest/types'` is TS2307 while the same import from the package root resolves.

(() => { const fs = require('node:fs'), p = require('node:path'); const dir = p.join(p.dirname(require.resolve('@octokit/plugin-paginate-rest')), '..'); const m = JSON.parse(fs.readFileSync(p.join(dir, 'package.json'), 'utf8')); const t = m.exports['./types'].types; return 'exports["./types"].types is ' + t + ', which ' + (fs.existsSync(p.join(dir, t)) ? 'exists' : 'does not exist') + '; dist-types holds ' + fs.readdirSync(p.join(dir, 'dist-types')).filter((f) => f.endsWith('.d.ts')).join(', '); })() -> exports["./types"].types is ./dist-types/.d.ts, which does not exist; dist-types holds compose-paginate.d.ts, index.d.ts, iterator.d.ts, normalize-paginated-list-response.d.ts, paginate.d.ts, paginating-endpoints.d.ts, types.d.ts, version.d.ts
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against @octokit/plugin-paginate-rest@15.0.0 installed fresh, alone. fs.existsSync on dist-types/.d.ts is false and readdir of dist-types lists the eight files above. tsc --strict --module nodenext --moduleResolution nodenext on `import type { PaginateInterface } from '@octokit/plugin-paginate-rest/types'` reported TS2307 on that line while the control import of the same name from the package root on the next line passed. scripts/build.mjs was read from the repository at HEAD via the GitHub contents API.
Checked against
@octokit/plugin-paginate-rest 15.0.0, the current release (dist-tags.latest on 2026-09-10). The batch tree held 14.0.0 with the identical entry. Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. A ONE-WORD SLIP IN A BUILD SCRIPT, the string.prototype.repeat shape: scripts/build.mjs at octokit/plugin-paginate-rest.js HEAD writes `"./types": { types: "./dist-types/.d.ts" }` literally, and the file it meant is dist-types/types.d.ts, which the same script emits. THE SUBPATH IS NOT DOCUMENTED -- the README imports every type from the root -- so the consumers who hit this are the ones who read the exports map, and the runtime is untouched; the types condition is the only condition on that subpath, so the subpath has no working target at all. One token in the build script. NOT REPORTED UPSTREAM: octokit/plugin-paginate-rest.js issues searched for 'types', 'dist-types' and 'Cannot find module' return #692, #648, #652 and #661 about type shapes, none about the subpath.

load-tsconfig

1 finding

undelivered-entryAlready reportedNot a vulnerability

UNDELIVERED_ENTRY

package.json types names ./dist/index.d.ts and files is ['dist']; the tarball's dist holds index.cjs and index.js and no declaration file at all, so the package loads and every TypeScript consumer gets TS7016 'Could not find a declaration file' (nodenext) or TS2307 (node10).

(() => { const fs = require('node:fs'), p = require('node:path'); const dir = p.join(p.dirname(require.resolve('load-tsconfig')), '..'); const m = JSON.parse(fs.readFileSync(p.join(dir, 'package.json'), 'utf8')); return 'types is ' + m.types + ', which ' + (fs.existsSync(p.join(dir, m.types)) ? 'exists' : 'does not exist') + '; dist holds ' + fs.readdirSync(p.join(dir, 'dist')).join(', ') + '; typeof require(load-tsconfig).loadTsConfig is ' + typeof require('load-tsconfig').loadTsConfig; })() -> types is ./dist/index.d.ts, which does not exist; dist holds index.cjs, index.js; typeof require(load-tsconfig).loadTsConfig is function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against load-tsconfig@0.2.5 installed fresh, alone. ls dist: index.cjs, index.js. tsc --strict on `import { loadTsConfig } from 'load-tsconfig'` reported TS7016 under --module nodenext and TS2307 under --moduleResolution node10; require('load-tsconfig').loadTsConfig is a function. The repository package.json was read at HEAD via the GitHub contents API.
Checked against
load-tsconfig 0.2.5, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. ALREADY REPORTED AND STILL OPEN: egoist/load-tsconfig#26 (2024-05-14, 'Please export types': 'when you install from npm, there are no types'). The repository's build script is `pnpm build-fast --dts-resolve`, which should emit the file the manifest names, so the published 0.2.5 is missing an artifact its own manifest promises. The finder reached it independently, which is worth recording; a second report would not be, and the package is a dependency of tsup, whose own d.ts does not re-export it, so tsup consumers are unaffected. NOT REPORTED for that reason.

@babel/plugin-syntax-import-attributes

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

exports['.'].types names ./lib/index.d.ts and lib/ holds index.js and index.js.map only, so the plugin loads and every TypeScript consumer gets TS7016. The same slip is in sixteen other @babel/* packages at 7.29.7, but this one is the only one whose dist-tags.latest is still 7.29.7: it has no 8.x, because Babel 8 dropped the plugin.

(() => { const fs = require('node:fs'), p = require('node:path'); const dir = p.dirname(require.resolve('@babel/plugin-syntax-import-attributes/package.json')); const m = require('@babel/plugin-syntax-import-attributes/package.json'); const t = m.exports['.'].types; return 'exports["."].types is ' + t + ', which ' + (fs.existsSync(p.join(dir, t)) ? 'exists' : 'does not exist') + '; lib holds ' + fs.readdirSync(p.join(dir, 'lib')).join(', ') + '; typeof require(@babel/plugin-syntax-import-attributes).default is ' + typeof require('@babel/plugin-syntax-import-attributes').default; })() -> exports["."].types is ./lib/index.d.ts, which does not exist; lib holds index.js, index.js.map; typeof require(@babel/plugin-syntax-import-attributes).default is function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against @babel/plugin-syntax-import-attributes@7.29.7 installed fresh with --ignore-scripts, alone. ls lib: index.js, index.js.map. tsc --strict --esModuleInterop on `import importAttributes from '@babel/plugin-syntax-import-attributes'` reported TS7016 under both --module nodenext and --moduleResolution node10; require of the package gives a function default. dist-tags.latest for all seventeen flagged packages was read with npm view.
Checked against
@babel/plugin-syntax-import-attributes 7.29.7, the current release (dist-tags.latest on 2026-09-10). 7.29.7 is dist-tags.latest; the package has no 8.x release. Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE ARGUMENT THAT CLOSED THE @babel/helper-validator-identifier ROW DOES NOT APPLY HERE: that row was not reported because 8.x is latest and ships the d.ts, and the sixteen other packages this sweep flagged (helper-compilation-targets, helper-skip-transparent-expression-wrappers, helper-string-parser, helper-validator-option and twelve plugin-bugfix-*/plugin-transform-* packages) all have 8.0.x as latest and are recorded as fixed-in-current with no row. This package's latest IS the broken 7.29.7, and a Babel 7 config written in TypeScript that imports the plugin by name compiles with TS7016 or an implicit any. The runtime entry beside the types condition resolves, so nothing breaks at run time. NOT REPORTED UPSTREAM: babel/babel issues searched for 'Could not find a declaration file', 'exports types lib/index.d.ts' and 'plugin-syntax-import-attributes types' return nothing on this package; the earlier row mentions an existing issue touching the helper packages, which these searches did not surface.

tsup

1 finding

unresolvable-declarationAlready reportedNot a vulnerability

UNRESOLVABLE_DECLARATION

dist/index.d.ts:3 is `import { SectionedSourceMapInput } from './types.cts';` and dist ships no types.cts, types.d.cts or any file named types, so a consumer who imports tsup's types and runs tsc without skipLibCheck gets TS2307 inside node_modules/tsup/dist/index.d.ts.

(() => { const fs = require('node:fs'), p = require('node:path'); const dir = p.dirname(require.resolve('tsup')); const line = fs.readFileSync(p.join(dir, 'index.d.ts'), 'utf8').split('\n')[2]; const named = fs.readdirSync(dir).filter((f) => /types/.test(f)); return 'dist/index.d.ts line 3 is `' + line + '`; dist holds ' + (named.length ? named.join(', ') : 'no file whose name contains types') + ' while dist/index.js ' + (fs.existsSync(p.join(dir, 'index.js')) ? 'exists' : 'is missing'); })() -> dist/index.d.ts line 3 is `import { SectionedSourceMapInput } from './types.cts';`; dist holds no file whose name contains types while dist/index.js exists
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against tsup@8.5.1 installed fresh with --ignore-scripts, alone. tsc --strict --module nodenext --moduleResolution nodenext on `import type { Options } from 'tsup'` reported TS2307 at node_modules/tsup/dist/index.d.ts(3,41) for './types.cts' (and a second TS2307 for the optional peer @swc/core, expected); the same command with --skipLibCheck exited 0. ls dist: five chunk files, cli-default.js, cli-main.js, cli-node.js, index.d.ts, index.js, rollup.js.
Checked against
tsup 8.5.1, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. ALREADY REPORTED AND STILL OPEN: egoist/tsup#1375 (2025-11-18, '[8.5.1] Cannot find module ./types.cts'), which quotes the identical TS2307 at dist/index.d.ts:3:41 and dates it to the 8.5.0 -> 8.5.1 upgrade. WHY IT SURVIVES: tsc --skipLibCheck, which most projects set, suppresses diagnostics inside .d.ts files and this witness exits 0 under it, so the defect is visible only to the minority who typecheck their dependencies' declarations. The finder reached it independently; a second report would not help. NOT REPORTED for that reason.

reghex

1 finding

undeclared-dependencyNo prior reportNot a vulnerability

UNDECLARED_DEPENDENCY

dist/reghex-macro.js:2, the target of the exported './macro' subpath, does `require("babel-plugin-macros")` at module scope, and package.json declares no dependencies and no peerDependencies at all. README line 43 tells the consumer to 'set up babel-plugin-macros' themselves, which is a peer dependency in everything but the manifest: under npm's flat hoisting the require resolves through the consumer's copy, and under a resolver that links only declared edges the very first require of reghex/macro throws MODULE_NOT_FOUND.

(() => { const m = require('reghex/package.json'); const declared = Object.keys(Object.assign({}, m.dependencies, m.peerDependencies)); const control = typeof require('reghex').match; try { require('reghex/macro'); return 'reghex/macro loaded'; } catch (e) { return 'control require(reghex).match is ' + control + ' while require(reghex/macro) threw ' + e.code + ': ' + e.message.split('\n')[0] + '; dependencies and peerDependencies declare ' + (declared.length ? declared.join(', ') : 'nothing'); } })() -> control require(reghex).match is function while require(reghex/macro) threw MODULE_NOT_FOUND: Cannot find module 'babel-plugin-macros'; dependencies and peerDependencies declare nothing
Verified by running
Node v22.14.0 against reghex@3.0.2 installed fresh, alone, which links exactly the declared edges -- none. require('reghex') loaded with match a function; require('reghex/macro') threw MODULE_NOT_FOUND for babel-plugin-macros. In the 1,795-package batch tree the same require threw the same error, because nothing in that tree declared babel-plugin-macros either. package.json read for dependencies and peerDependencies: both absent.
Checked against
reghex 3.0.2, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. WEAKER THAN THE typed-array-byte-length ROW, and the difference belongs here: that package's undeclared require is a hard runtime need that resolves by accident, while a Babel macro is by construction loaded from a tree where babel-plugin-macros already sits, so the README's instruction covers the npm case. It does not cover pnpm's isolated layout or Yarn PnP, where reghex's own node_modules is the only place its require can look and the consumer's copy is invisible -- which is exactly what a `peerDependencies` entry (optional via peerDependenciesMeta) exists to say. Found as a transitive dependency of tap 21.8.0 through tshy 3.3.2 and jsonc-simple-parser 3.0.0, none of which use the macro subpath, so the batch tree never loaded it. One entry in the manifest. NOT REPORTED UPSTREAM: kitten/reghex issues searched for 'babel-plugin-macros' and 'macro' return nothing.

is-npm

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ^12.20.0 || ^14.13.1 || >=16.0.0, and index.js:6, the only source line, uses optional chaining twice. Optional chaining is Node 14; the ^12.20.0 alternative admits a Node the shipped file cannot be parsed by.

index.js:6 is `export const isNpm = Boolean(userAgent?.startsWith('npm')) || Boolean(packageJson?.endsWith('package.json'));`. Optional chaining is Node 14.0; the ^12.20.0 branch of the range admits 12.20 through 12.x. acorn parses the file at ecmaVersion 2020 and rejects it at 2019 with Unexpected token (6:39).
Verified by running
Node v22.14.0 against is-npm@6.1.0 installed fresh, alone. package.json engines.node read as '^12.20.0 || ^14.13.1 || >=16.0.0'; index.js line 6 read as quoted. acorn 8 (from the batch tree) parsed index.js as a module at ecmaVersion 2020 and threw 'Unexpected token (6:39)' at 2019, the column of the first `?.`. No Node 12 was run.
Checked against
is-npm 6.1.0, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. NOT REPORTED. The napi-postinstall shape exactly: one alternative of a three-way range is wrong, the Node 12 line has been end-of-life since 2022-04, and the fix is deleting a branch of a range nobody installs on. Real, one line, and not worth a maintainer's attention -- the same judgement as the napi-postinstall and signal-exit rows. NOT REPORTED UPSTREAM: sindresorhus/is-npm issues searched for 'node 12', 'engines' and 'optional chaining' return nothing.

prettier

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares >=14 and the shipped entries define private class methods: index.mjs:10472 is `#matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {` (with #matchOne, #search, #getCodeFrame and others beside it), doc.mjs:1012 and doc.js:1047 are `#settle() {`. Private methods are Node 14.6; the range admits 14.0 through 14.5, which cannot parse the ESM entry or the CommonJS doc entry.

package.json engines.node is '>=14'; index.mjs:10472 is `#matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {` and doc.js:1047 is `#settle() {`. Private class methods are Node 14.6; the range admits 14.0 through 14.5. acorn parses index.mjs at ecmaVersion 2022 and rejects it at 2021.
Verified by running
Node v22.14.0 against prettier@3.9.6 installed fresh, alone. package.json engines.node read as '>=14' and exports['.'] as require ./index.cjs, default ./index.mjs, './doc' as require ./doc.js. grep for `#name(...) {` found the methods quoted at index.mjs:10472, :10544, :10573, :11122, :12903, :15910, :16063, doc.mjs:1012 and doc.js:1047. acorn 8 parsed index.mjs, doc.mjs and doc.js at ecmaVersion 2022 and rejected each at 2021 (acorn's 2021 rejection of class fields on earlier lines means the acorn boundary alone does not isolate private methods; the quoted lines do). No Node 14 was run.
Checked against
prettier 3.9.6, the current release (dist-tags.latest on 2026-09-10). Checked 2026-09-10.
Note
Found 2026-09-10 by the fifth probe sweep, build tooling and the CLI ecosystem (bundlers, loaders, transpilers, CSS tooling, linters, formatters, test runners, CLI helpers, release tooling, resolvers, parsers): the four package-level probes over 1,795 installed packages, 16 proven, 1,843 clean, 3,705 abstained, 6 crashed, 63 timed out; and the reading finders over the same 1,795 packages, 25,686 files, 114 hits, 17 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE signal-exit SHAPE with a twist in which entry breaks: index.cjs, the require entry, parses fine and does `import('./index.mjs')` at line 726, so on Node 14.0-14.5 require('prettier') succeeds and the first format() call rejects loading the ESM file; require('prettier/doc') fails at parse time because doc.js carries the private method itself. The admitted-but-broken window is six minor releases of a Node line end-of-life since 2023-04, and nobody on it is installing prettier 3.9; the fix is changing 14 to 14.6 (or 16, which the code's other choices assume). Real, and not worth a maintainer's attention -- the same judgement as the signal-exit and napi-postinstall rows. NOT REPORTED UPSTREAM: prettier/prettier issues searched for 'engines node 14', 'private methods node 14' and 'SyntaxError Unexpected token #' return nothing on point.

protobufjs

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

index.d.ts:352 declares `export class FieldBase extends ReflectionObject` and index.d.ts:812 `export abstract class NamespaceBase extends ReflectionObject`, and the package's runtime exports neither: require('protobufjs').FieldBase and .NamespaceBase are undefined while Field and Namespace are functions, so `new FieldBase(...)` and `class X extends NamespaceBase` typecheck and throw.

(() => { const p = require('protobufjs'); const control = typeof p.Field; try { new p.FieldBase('a', 1, 'string'); return 'control Field=' + control + ' and FieldBase constructed'; } catch (e) { return 'control Field=' + control + ' NamespaceBase=' + typeof p.NamespaceBase + ' while new FieldBase() threw ' + e.constructor.name + ': ' + e.message; } })() -> control Field=function NamespaceBase=undefined while new FieldBase() threw TypeError: p.FieldBase is not a constructor
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against protobufjs@8.8.0 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN with its own finding sentence naming FieldBase and NamespaceBase (27 of 29 declarations kept). typeof require('protobufjs').FieldBase and .NamespaceBase: undefined, undefined; Field and Namespace: function, function; `new p.FieldBase('a', 1, 'string')` threw TypeError 'p.FieldBase is not a constructor'. THE TYPE HALF WAS COMPILED: tsc --strict --skipLibCheck accepted `new FieldBase('b', 2, 'string')` and `class N extends NamespaceBase {}` beside a `new Field(...)` control and exited 0 under node10, nodenext and bundler resolution. FIXED-SIDE CONTROL: editing the d.ts to `declare class FieldBase` / `declare abstract class NamespaceBase` and appending `export type { FieldBase, NamespaceBase };` made tsc report TS1362 on both consumer lines; restoring the file restored exit 0. The batch tree's copy (also 8.8.0) was PROVEN the same way.
Checked against
protobufjs 8.8.0, the current release (dist-tags.latest on 2026-09-10, published 2026-08-27). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE DECLARATION DOCUMENTS ITS OWN FALSITY, which is what makes this row weaker than the @vue/compiler-sfc one: the doc comment above index.d.ts:352 reads 'This is not an actual class but here for the sake of having consistent type definitions' and the constructor is annotated 'Not an actual constructor. Use {@link Field} instead', and index.d.ts:812 carries the same sentence. The names exist so that Field, MapField, Namespace, Root, Service and Type can share members in the d.ts (src/field.js:62 and src/namespace.js:101 carry `@exports FieldBase` / `@exports NamespaceBase` jsdoc tags with no matching runtime binding; at runtime MapField does not even extend Field). The declaration still says `export class`, which is a value promise TypeScript enforces nowhere, so a consumer who takes the type at its word gets a TypeError; the one-line repair that keeps the shared members is `declare class FieldBase` / `declare abstract class NamespaceBase` plus `export type { FieldBase, NamespaceBase }`, which turns the consumer's `new` and `extends` into TS1362 without changing anything else in the file. A maintainer can reasonably answer 'as documented', which is why this is recorded and not drafted. NOT REPORTED UPSTREAM: protobufjs/protobuf.js issues searched for 'FieldBase' return nothing, for 'NamespaceBase' only #1024 (2018, a lookupTypeOrEnum signature) and #2148 (unrelated), and for 'not an actual class' and 'FieldBase is not a constructor' nothing on point. No advisory among the eight open protobufjs advisories concerns this.

i18next

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

index.d.ts:37, the `require` types entry, declares `export class ResourceStore { constructor(data: Resource, options: InitOptions); ... }`, and neither build exports it: dist/cjs/i18next.js:2272 is `module.exports = instance` and dist/esm/i18next.js:2283 exports sixteen names without it, so `new ResourceStore(...)` typechecks in a CommonJS consumer and throws 'is not a constructor'. The package's own ESM declaration, index.d.mts:9, already says `export type ResourceStore = i18nextMod.ResourceStore`.

(() => { const i = require('i18next'); const control = typeof i.createInstance; try { new i.ResourceStore({}, {}); return 'control createInstance=' + control + ' and ResourceStore constructed'; } catch (e) { return 'control createInstance=' + control + ' while new ResourceStore() threw ' + e.constructor.name + ': ' + e.message; } })() -> control createInstance=function while new ResourceStore() threw TypeError: i.ResourceStore is not a constructor
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against i18next@26.4.2 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN with its own finding sentence naming ResourceStore (15 of 16 declarations kept). typeof require('i18next').ResourceStore: undefined; createInstance: function; `new i.ResourceStore({}, {})` threw TypeError 'i.ResourceStore is not a constructor'; import('i18next') listed changeLanguage, createInstance, default, dir, exists, getFixedT, hasLoadedNamespace, init, keyFromSelector, loadLanguages, loadNamespaces, loadResources, reloadResources, setDefaultNamespace, t, use and no ResourceStore. THE TYPE HALF WAS COMPILED: tsc --strict --skipLibCheck accepted `new ResourceStore({}, {})` beside a createInstance() control and exited 0 under node10 and nodenext (both resolve index.d.ts for a .ts consumer); under bundler resolution, which picks index.d.mts, the same line was TS2693 "'ResourceStore' only refers to a type". FIXED-SIDE CONTROL: changing index.d.ts:37 to `declare class ResourceStore {` and appending `export type { ResourceStore };` made tsc report TS1362 on the consumer's `new`; restoring the file restored exit 0. The batch tree's copy (also 26.4.2) was PROVEN the same way.
Checked against
i18next 26.4.2, the current release (dist-tags.latest on 2026-09-10, published 2026-09-03). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE NEIGHBOUR IS THE CONTROL, the rate-limiter-flexible shape turned inward: the same package ships two declaration files for the same runtime, and they disagree. index.d.mts, served on the `import` condition, exports ResourceStore as a type alias (line 9), so an ESM TypeScript consumer who writes `new ResourceStore()` gets TS2693 at compile time; index.d.ts, served on the `require` condition and as the top-level `types`, exports it as a class with a constructor, so the identical CommonJS consumer compiles and throws. The class exists at runtime (dist/cjs/i18next.js:302 `class ResourceStore extends EventEmitter`) and is reachable only as `i18next.store` on an initialised instance; nothing documents constructing one, which is why the reach is a consumer who follows the d.ts rather than the README. The repair is what the .d.mts already does: `declare class ResourceStore` plus `export type { ResourceStore }` in index.d.ts. NOT REPORTED UPSTREAM: i18next/i18next issues searched for 'ResourceStore' return #1974 (options on clone), #1364 and #1108 (the type of i18n.services.resourceStore, 2018-2019) and behaviour reports; 'ResourceStore is not a constructor' and 'export type ResourceStore' return nothing on point. The two i18next advisories are XSS in 1.x-3.x.

rambda

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

index.d.cts:319 and :321 declare `export function dropRepeatsBy` and `export function dropRepeatsWith`, index.d.cts:2289 `export function transformPropObject` (index.d.ts identically), and README.md documents all three (lines 2064, 2093, 10810) with REPL links, but no build carries them: src/ has no dropRepeatsBy.js, dropRepeatsWith.js or transformPropObject.js, and require('rambda') and import('rambda') both expose 147 names without them.

(() => { const r = require('rambda'); return 'dropLast=' + typeof r.dropLast + ' dropRepeatsBy=' + typeof r.dropRepeatsBy + ' dropRepeatsWith=' + typeof r.dropRepeatsWith + ' transformPropObject=' + typeof r.transformPropObject + '; ' + (() => { try { r.dropRepeatsBy(Math.abs)([1, -1, 2]); return 'dropRepeatsBy called'; } catch (e) { return 'dropRepeatsBy threw ' + e.constructor.name + ': ' + e.message; } })(); })() -> dropLast=function dropRepeatsBy=undefined dropRepeatsWith=undefined transformPropObject=undefined; dropRepeatsBy threw TypeError: r.dropRepeatsBy is not a function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against rambda@11.3.0 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN with its own finding sentence naming dropRepeatsBy, dropRepeatsWith and transformPropObject (135 of 138 declarations kept). typeof of the three on require('rambda'): undefined x3, dropLast: function, Object.keys length 147; import('rambda') gave the same three undefined and 147 keys; `r.dropRepeatsBy(Math.abs)([1, -1, 2])` threw TypeError 'r.dropRepeatsBy is not a function'. `ls src` lists drop.js, dropLast.js, dropLastWhile.js, dropWhile.js and nothing for the three; grep over src, dist and rambda.js finds none of the names. THE TYPE HALF WAS COMPILED: tsc --strict --skipLibCheck accepted calls to all three beside a dropLast(1)([1, 2]) control and exited 0 under node10 and nodenext. FIXED-SIDE CONTROL: deleting the three declarations from index.d.cts made tsc report TS2305 'has no exported member' for each; restoring the file restored exit 0. The batch tree's copy (also 11.3.0) was PROVEN the same way.
Checked against
rambda 11.3.0, the current release (dist-tags.latest on 2026-09-10, published 2026-09-02). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. DOCUMENTED, TYPED, AND ABSENT: the README generated from the same source as the d.ts shows each of the three with a worked example and a 'Try this in Rambda REPL' link, and CHANGELOG.md records 'Add R.dropRepeatsBy' (line 495) and later lists dropRepeatsBy under a removal block (line 338) -- the type declaration and the README entry survived whichever change dropped the implementation. The three names are exactly what the probe listed and none of the 147 runtime exports is a renamed version of them (dropRepeats itself is also absent, from both types and runtime, which is consistent). The repair is either three source files or three deleted declarations; deleting them turns the consumer's import into TS2305. The maintainer's own 2021 answer to #575 ('The method indeed is missing in REPL but this will be fixed') was about the REPL bundle, not the package; the 2026 package has the same gap. NOT REPORTED UPSTREAM: selfrefactor/rambda issues searched for 'dropRepeatsBy' and 'transformPropObject' return nothing; 'dropRepeatsWith' returns only #575 (2021, closed) about the REPL and docs; 'not a function' and 'missing export' return #764 (mapToObject, 2025, closed) and older, none about these three. No advisories.

email-validator

1 finding

declared-value-missingAlready reportedNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

index.d.ts:14 declares `export function validate_async(email: string, callback: AsyncCallback): void` and index.js exports only `validate` (line 8), so a TypeScript consumer of the async form typechecks and throws 'validate_async is not a function'. The async export was removed on purpose in 2017 (#18) and the declaration was never updated.

(() => { const e = require('email-validator'); const control = e.validate('a@b.co'); try { e.validate_async('a@b.co', () => {}); return 'control validate=' + control + ' and validate_async called'; } catch (err) { return 'control validate=' + control + ' while validate_async threw ' + err.constructor.name + ': ' + err.message; } })() -> control validate=true while validate_async threw TypeError: e.validate_async is not a function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against email-validator@2.0.4 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN with its own finding sentence naming validate_async (1 of 2 declarations kept). Object.keys(require('email-validator')) is ['validate']; validate('a@b.co') is true; validate_async('a@b.co', cb) threw TypeError 'e.validate_async is not a function'. THE TYPE HALF WAS COMPILED: tsc --strict --skipLibCheck accepted the validate_async call beside a validate() control and exited 0 under node10, nodenext and bundler. FIXED-SIDE CONTROL: deleting the `export function validate_async` line from index.d.ts made tsc report TS2305 "Module '\"email-validator\"' has no exported member 'validate_async'"; restoring the file restored exit 0. The batch tree's copy (also 2.0.4) was PROVEN the same way.
Checked against
email-validator 2.0.4, the current release (dist-tags.latest on 2026-09-10, published 2018-05-27). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. REPORTED IN 2018 AND CLOSED WITHOUT A FIX, which is the only reason this row exists: manishsaraan/email-validator#35 (opened 2018-07-24, closed 2018-08-02, no comments) is this exact program -- 'The following program compiles fine but fails at runtime' with `EmailValidator.validate_async(...)` and the same TypeError -- and 2.0.4, the release it was filed against, is still the current release eight years later, with the declaration unchanged. #18 (2016, closed 2017-07-31) is the deliberate removal of the async form ('a worse API and not asynchronous in any way'); index.d.ts kept it. The fix is deleting one declaration and the interface it uses. A dead package at its final release; recorded for the corpus as the fixed-in-nothing counterpart of the filenamify row, not for filing. Prior reports: #35 as above; no advisories.

suffix-thumb

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

builds/types.d.ts:19 declares `export function classify(word: string, model: Model, debug?: boolean): 'Left' | 'Right' | null` and README.md:152-164 documents `import { learn, classify } from 'suffix-thumb'`, but src/index.js:8 has the import commented out (`// import classify from './classify/index.js'`), src/ has no classify directory, and the ESM entry exports seven names without it.

import('suffix-thumb').then((m) => { const model = m.learn([['walk', 'walked']]); try { m.classify('walked', model); return 'control learn=' + typeof m.learn + ' and classify called'; } catch (e) { return 'control learn=' + typeof m.learn + ' exports=' + Object.keys(m).join(',') + ' while classify() threw ' + e.constructor.name + ': ' + e.message; } }) -> control learn=function exports=compress,convert,learn,reverse,test,uncompress,validate while classify() threw TypeError: m.classify is not a function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against suffix-thumb@5.0.3 installed fresh with --ignore-scripts, alone. The package-level probe re-run on the fresh install alone reported PROVEN with its own finding sentence naming classify (7 of 8 declarations kept). import('suffix-thumb') exported compress, convert, learn, reverse, test, uncompress, validate; typeof classify undefined; `m.classify('walked', model)` threw TypeError 'm.classify is not a function' with learn() having returned a model. THE TYPE HALF WAS COMPILED: tsc --strict --skipLibCheck --moduleResolution node10 accepted `classify('walked', model)` beside a learn() control and exited 0 (nodenext and bundler: TS7016, the missing types condition above). FIXED-SIDE CONTROL: deleting the classify line from builds/types.d.ts made tsc report TS2305 "Module '\"suffix-thumb\"' has no exported member 'classify'"; restoring the file restored exit 0. require('suffix-thumb') resolved to builds/suffix-thumb.js and returned an object with no keys. The batch tree's 5.0.2 was PROVEN the same way.
Checked against
suffix-thumb 5.0.3, the current release (dist-tags.latest on 2026-09-10, published 2026-08-19). The batch tree held 5.0.2 with the identical declaration. Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE SOURCE SHOWS THE REMOVAL: src/index.js keeps `// import classify from './classify/index.js'` as a comment beside the seven live imports, the classify directory is gone from src/, and both the hand-written d.ts and the README section 'classify' (with two worked calls) still promise it. A dependency of compromise (the NLP library in this batch, by the same author), which is how it arrived. Two further things read while verifying, not finder hits and recorded here rather than as rows: (1) the exports map has no `types` condition, so under node16/nodenext or bundler resolution tsc reports TS7016 'Could not find a declaration file' and points at builds/types.d.ts as unreachable 'when respecting package.json exports' -- the classify defect is visible only under node10 resolution, where the top-level `types` field is honoured; (2) package.json has `"type": "module"` and the `require` condition names builds/suffix-thumb.js, a UMD file with a .js extension, so Node 22.12+ loads it as ESM and `require('suffix-thumb')` returns an empty `[Module: null prototype] {}` with typeof learn undefined (older Nodes throw ERR_REQUIRE_ESM), i.e. the CommonJS entry delivers nothing at all. 5.0.2 additionally had `"types": "/builds/types.d.ts"` with a leading slash; 5.0.3 corrected that to ./builds/types.d.ts and nothing else here. NOT REPORTED UPSTREAM: spencermountain/suffix-thumb has no issues at all in any state. No advisories.

libsodium-wrappers

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

dist/modules/libsodium-wrappers.d.ts, the standard (non-sumo) package's own declaration, is the sumo declaration: it declares 256 functions and 326 constants, and after `await sodium.ready` the standard runtime is missing 99 of the functions and 195 of the constants -- the whole crypto_pwhash_* family (which README.md:242 says 'is only included in the sumo version'), crypto_auth_hmacsha256/512*, crypto_aead_aes256gcm_*, crypto_box_curve25519x*, crypto_core_*, crypto_scalarmult_ristretto255_* and more -- so a TypeScript consumer of the standard package calls them, compiles, and throws.

(async () => { const s = require('libsodium-wrappers'); await s.ready; const control = typeof s.crypto_secretbox_easy; try { s.crypto_pwhash(32, s.from_string('pw'), s.randombytes_buf(16), 2, 67108864, s.crypto_pwhash_ALG_DEFAULT); return 'control crypto_secretbox_easy=' + control + ' and crypto_pwhash called'; } catch (e) { return 'control crypto_secretbox_easy=' + control + ' crypto_auth_hmacsha256=' + typeof s.crypto_auth_hmacsha256 + ' crypto_pwhash_ALG_DEFAULT=' + s.crypto_pwhash_ALG_DEFAULT + ' while crypto_pwhash() threw ' + e.constructor.name + ': ' + e.message; } })() -> control crypto_secretbox_easy=function crypto_auth_hmacsha256=undefined crypto_pwhash_ALG_DEFAULT=undefined while crypto_pwhash() threw TypeError: s.crypto_pwhash is not a function
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against libsodium-wrappers@0.8.4 installed fresh with --ignore-scripts, alone, and libsodium-wrappers-sumo@0.8.4 installed alone beside it for the diff. The package-level probe re-run on the fresh install alone reported PROVEN (564 names, before ready). After `await s.ready`: Object.keys length 289, typeof crypto_secretbox_easy function, crypto_auth function; typeof crypto_pwhash, crypto_pwhash_str, crypto_auth_hmacsha256, crypto_aead_aes256gcm_encrypt: undefined; crypto_pwhash_ALG_DEFAULT undefined; `s.crypto_pwhash(32, ...)` threw TypeError 's.crypto_pwhash is not a function'. THE TYPE HALF WAS COMPILED: tsc --strict --skipLibCheck --target es2020 accepted `await sodium.ready` followed by crypto_pwhash(...) with sodium.crypto_pwhash_ALG_DEFAULT and crypto_auth_hmacsha256(...) beside a crypto_secretbox_easy control and exited 0 under node10 and nodenext. FIXED-SIDE CONTROL: deleting the crypto_pwhash overloads, the crypto_pwhash_ALG_DEFAULT constant and the crypto_auth_hmacsha256 overload from the d.ts made tsc report TS2551/TS2339 on the three consumer lines with the control untouched; restoring the file restored exit 0. The batch tree's copy (also 0.8.4) was PROVEN the same way.
Checked against
libsodium-wrappers 0.8.4, the current release (dist-tags.latest on 2026-09-10, published 2026-04-19). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. THE PROBE'S NUMBER WAS WRONG AND THE FINDING SURVIVED THE CORRECTION: the probe reported 564 declared values missing at runtime, but this package attaches its API only after the `ready` promise resolves (index.d.ts:5-8 says so), so the probe, which never awaits, counted the whole library. Awaiting ready and re-checking every `export function` and `export const` in the d.ts leaves 99 functions and 195 constants (of 256 and 326 unique names) still absent. THE D.TS IS THE SUMO ONE: diffing dist/modules/libsodium-wrappers.d.ts against libsodium-wrappers-sumo@0.8.4's dist/modules-sumo/libsodium-wrappers.d.ts leaves four lines (crypto_pwhash_scryptsalsa208sha256 overloads), and every name missing from the standard runtime is present in the sumo runtime (581 keys after ready against 398). The README is explicit that the standard build is a subset and that crypto_pwhash is sumo-only; the generated declaration, introduced in January 2026 after #358, does not know that. The reach is exactly the confusion #313 (2023) recorded for @types/libsodium-wrappers, where a DefinitelyTyped maintainer wrote that the pwhash symbols 'will pass at compile time and fail at runtime' -- now shipped by the package itself. #366 (2026-03-30, closed the same day) is the runtime half for one family: the author called it 'a packaging mismatch' where 'the wrapper layer includes the Ristretto255 method names, but the standard libsodium runtime ... does not actually ship the underlying symbols'; nobody has reported that the declaration file carries them. The fix is generating the standard d.ts from the standard symbol list (not a one-liner). NOT REPORTED UPSTREAM: jedisct1/libsodium.js issues searched for 'sumo typescript', 'd.ts', 'types sumo', 'typescript definitions', 'sumo', 'is not a function' and 'crypto_pwhash types' return #358, #313, #364 (a return-type mismatch in the sumo d.ts), #366 and #335, none about the standard declaration listing sumo-only symbols. No advisories.

get-value

1 finding

undelivered-entryAlready reportedNot a vulnerability

UNDELIVERED_ENTRY

package.json names `types: index.d.ts` and `exports['.'].types: ./index.d.ts`, and `files: ["dist"]` keeps the root out of the tarball: neither file exists in the published package while dist/index.d.ts and dist/index.d.mts do. A TypeScript consumer under node10 resolution gets TS7016 'Could not find a declaration file for module get-value'; node16/bundler resolution recovers through the sibling of the JS target.

(() => { const fs = require('node:fs'), p = require('node:path'); const m = require('get-value/package'); const dir = p.dirname(require.resolve('get-value/package')); const has = (f) => f + ' (' + (fs.existsSync(p.join(dir, f)) ? 'exists' : 'does not exist') + ')'; return 'types is ' + has(m.types) + ', exports["."].types is ' + has(m.exports['.'].types) + ', files is ' + JSON.stringify(m.files) + ', dist holds ' + fs.readdirSync(p.join(dir, 'dist')).filter((f) => /\.d\.m?ts$/.test(f)).join(', '); })() -> types is index.d.ts (does not exist), exports["."].types is ./index.d.ts (does not exist), files is ["dist"], dist holds index.d.mts, index.d.ts
Verified by running
Node v22.14.0 and TypeScript 5.9.3 against get-value@4.1.0 installed fresh with --ignore-scripts, alone. The reading finder re-run on the fresh install alone reported the same exports['.'].types hit. fs.existsSync of index.d.ts and ./index.d.ts relative to the package: false, false; readdir of dist: index.d.mts, index.d.ts, index.js, index.js.map, index.mjs, index.mjs.map. tsc --strict --skipLibCheck on `import get from 'get-value'` with a get({a:{b:1}}, 'a.b') call: TS7016 under --moduleResolution node10 (the message names dist/index.d.mts as types 'that could not be resolved under your current moduleResolution setting'), exit 0 under nodenext and bundler. FIXED-SIDE CONTROL: setting `types` to dist/index.d.ts and exports['.'].types to ./dist/index.d.ts in the installed package.json made the node10 compile exit 0; restoring the file restored TS7016. The batch tree's copy (also 4.1.0) produced the identical hit.
Checked against
get-value 4.1.0, the current release (dist-tags.latest on 2026-09-10, published 2026-06-14). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. REPORTED THREE DAYS AFTER RELEASE AND STILL OPEN: jonschlinkert/get-value#37 (opened 2026-06-17) says 'index.d.ts is located in dist directory. It caused the TypeScript engine cannot find the type definition', which is this row; #28 (2023, closed 2025-02-05) asked for a declaration at all. The build emits the declarations into dist/ (the `files` allowlist ships only dist/) and the manifest points at the root, the load-tsconfig shape. NARROWER THAN THE FINDER'S SENTENCE: the finder says every TypeScript consumer breaks, but tsc 5.9.3 under nodenext and bundler resolution falls through to dist/index.d.mts / dist/index.d.ts beside the resolved JS target and exits 0; only classic node10 resolution, which honours the top-level `types` field, fails with TS7016. The fix is two strings in package.json. Prior reports: #37 as above; no advisories.

chrono-node

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares ^12.20.0 || ^14.13.1 || >=16.0.0, and the `import` condition's build uses nullish coalescing and optional chaining: dist/esm/chrono.js:80 is `this.option = option ?? {};` with seven more sites in dist/esm (results.js:8, timezone.js:254, calculation/duration.js:49, two locale parsers, two common files). The `require` build is transpiled (dist/cjs/chrono.js:81 is the `option !== null && option !== void 0 ? option : {}` form), so the ^12.20.0 alternative admits a Node on which `import 'chrono-node'` is a SyntaxError and require() works.

(() => { const fs = require('node:fs'), p = require('node:path'); const dir = p.dirname(require.resolve('chrono-node')); const m = require(p.join(dir, '..', '..', 'package.json')); const esm = fs.readFileSync(p.join(dir, '..', 'esm', 'chrono.js'), 'utf8').split('\n')[79].trim(); const cjs = fs.readFileSync(p.join(dir, 'chrono.js'), 'utf8').split('\n')[80].trim(); return 'engines.node is ' + m.engines.node + '; exports["."].import dist/esm/chrono.js:80 is ' + JSON.stringify(esm) + ' while exports["."].require dist/cjs/chrono.js:81 is ' + JSON.stringify(cjs); })() -> engines.node is ^12.20.0 || ^14.13.1 || >=16.0.0; exports["."].import dist/esm/chrono.js:80 is "this.option = option ?? {};" while exports["."].require dist/cjs/chrono.js:81 is "this.option = option !== null && option !== void 0 ? option : {};"
Verified by running
Node v22.14.0 against chrono-node@2.10.1 installed fresh with --ignore-scripts, alone. The reading finder re-run on the fresh install alone reported the same eight dist/esm sites. package.json engines.node read as '^12.20.0 || ^14.13.1 || >=16.0.0' and exports['.'] as { require: ./dist/cjs/index.js, import: ./dist/esm/index.js }; the two lines quoted above read from the installed files. acorn 8.18.0 (from the fifth sweep's tree) parsed dist/esm/chrono.js as a module at ecmaVersion 2022 and rejected it at 2019, 2020 and 2021 with 'Unexpected token (4:11)' (the class field; the `??` at line 80 is itself ES2020); every file under dist/cjs parsed at ecmaVersion 2019. No Node 12 was run.
Checked against
chrono-node 2.10.1, the current release (dist-tags.latest on 2026-09-10, published 2026-07-20). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. NOT REPORTED. The napi-postinstall and is-npm shape: one alternative of a three-way range is wrong, the Node 12 line has been end-of-life since 2022-04, and the fix is deleting a branch of a range nobody installs on. Narrower still than is-npm because only the ESM half is affected -- the two tsconfigs target different years, and Node 12.20-13.x with `exports` support picks the ESM file on import and the transpiled CJS on require. The esm build also uses class fields (chrono.js:4 `parsers;`), which Node 12 does parse. Real, one line, and not worth a maintainer's attention -- the same judgement as the napi-postinstall, signal-exit and is-npm rows. NOT REPORTED UPSTREAM: wanasit/chrono issues searched for 'node 12', 'engines' and 'nullish' return nothing on point (#588 is a dependency-update chore). The one chrono-node advisory is a 2021 ReDoS fixed in 2.2.4.

compromise

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares >=12.0.0, and the package cannot be loaded on any Node 12 or 13 by either loader -- but by two different mechanisms, which is what earlier readings of this row each got half of. package.json is "type": "module" with main ./src/three.js and exports["."].require ./builds/three/compromise-three.cjs. A require on 12.7-13.x resolves that .cjs build, whose minified line carries optional chaining at offset 226250 (`const o="also"!==e[t-1]?.text?...`), and raises a SyntaxError -- optional chaining is Node 14.0. A require on 12.0-12.6, which predates `exports`, resolves main instead, an ES module, and raises ERR_REQUIRE_ESM before any parse. An import on any 12.x or 13.x resolves main and reaches src/2-two/preTagger/compute/tagger/3rd-pass/06-switches.js:44, `terms[i - 1]?.text`, in five static unconditional hops -- a SyntaxError again. Verified against compromise@14.17.0 installed fresh, 2026-09-19.

(() => { const fs = require('node:fs'), p = require('node:path'); const entry = require.resolve('compromise'); const m = JSON.parse(fs.readFileSync(p.join(p.dirname(entry), '..', '..', 'package.json'), 'utf8')); const src = fs.readFileSync(entry, 'utf8'); const at = src.indexOf('?.text'); return 'engines.node is ' + m.engines.node + '; ' + entry.replace(/.*node_modules\//, '') + ' offset ' + at + ' is ' + JSON.stringify(src.slice(at - 22, at + 6)); })() -> engines.node is >=12.0.0; compromise/builds/three/compromise-three.cjs offset 226250 is "onst o=\"also\"!==e[t-1]?.text"
Verified by running
Node v22.14.0 against compromise@14.16.0 installed fresh with --ignore-scripts, alone. The reading finder re-run on the fresh install alone reported the same two builds. package.json engines.node read as '>=12.0.0' and exports['.'].require as ./builds/three/compromise-three.cjs. acorn 8.18.0 (from the fifth sweep's tree) rejected builds/three/compromise-three.cjs and builds/two/compromise-two.cjs at ecmaVersion 2019 with 'Unexpected token (1:226251)' (the character after `e[t-1]` in `"also"!==e[t-1]?.text`) and parsed both at 2020; builds/one/compromise-one.cjs parsed at 2019; the ESM build builds/three/compromise-three.mjs was rejected at 2019 the same way. No Node 12 was run.
Checked against
compromise 14.16.0, the current release (dist-tags.latest on 2026-09-10, published 2026-07-14). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. NOT REPORTED, but the nearest thing to a report is open: spencermountain/compromise#1207 (2026-06-09) says 'engines.node in package.json is >=12.0.0 but the CI only tests Node 20 and 26' and that changes 'could accidentally break functionality when using an older Node version, with no CI feedback'; the maintainer's reply asks whether 'someone with a weird art-project on an arduino running node 12' would be affected by bumping the range. This row is the answer that issue did not have: the main require entry already does not parse on Node 12 or 13, so the range is already false and bumping it breaks nobody. Wider than the chrono-node row because it is the package's main CommonJS entry, not a secondary condition; the same judgement as the napi-postinstall, signal-exit and is-npm rows since Node 12 and 13 are end-of-life, and the fix is one token in engines. Issues searched for 'node 12', 'engines' and 'optional chaining' return #1207 and nothing else on point. No advisories.

fuse.js

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

engines.node declares >=10, and every shipped build uses nullish coalescing or optional chaining: the `require` entry dist/fuse.cjs:106 is `getFn = key.getFn ?? null;`, with the same line in fuse.mjs:105, fuse.basic.cjs:109, fuse.basic.mjs:108, fuse.worker.mjs:104, the four minified builds, and `document.currentScript?.src` in fuse-worker.cjs:22 and .mjs:20. Both are Node 14.0; the range admits Node 10, 12 and 13, on which require('fuse.js') is a SyntaxError.

(() => { const fs = require('node:fs'), p = require('node:path'); const entry = require.resolve('fuse.js'); const m = JSON.parse(fs.readFileSync(p.join(p.dirname(entry), '..', 'package.json'), 'utf8')); const line = fs.readFileSync(entry, 'utf8').split('\n')[105]; return 'engines.node is ' + m.engines.node + '; ' + entry.replace(/.*node_modules\//, '') + ':106 is ' + JSON.stringify(line.trim()); })() -> engines.node is >=10; fuse.js/dist/fuse.cjs:106 is "getFn = key.getFn ?? null;"
Verified by running
Node v22.14.0 against fuse.js@7.5.0 installed fresh with --ignore-scripts, alone. The reading finder re-run on the fresh install alone reported the same eleven files. package.json engines.node read as '>=10' and exports['.'].require.default as ./dist/fuse.cjs; line 106 read from the installed file. acorn 8.18.0 (from the fifth sweep's tree) rejected dist/fuse.cjs at ecmaVersion 2019 with 'Unexpected token (106:21)', dist/fuse.mjs at (105:21) and dist/fuse.basic.cjs at (109:21) -- each the column of the `??` -- and parsed all three at 2020. No Node 10 was run.
Checked against
fuse.js 7.5.0, the current release (dist-tags.latest on 2026-09-10, published 2026-07-13). Checked 2026-09-10.
Note
Found 2026-09-10 by the sixth probe sweep, data, serialisation, crypto, dates, i18n and common utilities (JSON/YAML/TOML/XML/CSV parsers, binary codecs, schema validators, date libraries, i18n, ids, hashing, JWT/crypto, URL and validation helpers, money and big-number maths, functional and immutable utilities, async control, diffing and cloning, deep object access, search and NLP, slugs and entities, regex tooling): the four package-level probes over 434 installed packages in two symlinked slices, 9 proven, 446 clean, 894 abstained, 0 crashed, 14 timed out; and the reading finders over the same 434 packages, 16,858 files, 37 hits, 3 of them on packages already in this file. Then re-installed fresh at the current release, alone, and re-run. NOT REPORTED. The widest of the three engines rows in this sweep: the floor is Node 10 (end-of-life 2021-04), the gap is four major lines, and every one of the eleven shipped files has the ES2020 token, so no entry or subpath works on the versions the manifest admits. krisk/Fuse#400 (2020, closed, 'Why is Node.js 10 required?') is about the floor being too high for someone, the opposite direction, and the floor was never raised when the build target was. The fix is one token in engines; the same judgement as the napi-postinstall, signal-exit and is-npm rows because nobody on Node 10-13 is installing a 2026 release. Issues searched for 'node 10', 'engines' and 'nullish' return #400 and nothing on point. No advisories.

@firebase/database

1 finding

undeclared-dependencyNo prior reportNot a vulnerability

UNDECLARED_DEPENDENCY

@firebase/database 1.1.5 loads @firebase/app unconditionally from both its CommonJS and its ESM entry (dist/index.node.cjs.js:8, dist/node-esm/index.node.esm.js:4) and declares it neither as a dependency nor as a peer, so installed on its own it throws MODULE_NOT_FOUND before a line of the consumer's code runs.

npm i @firebase/database && node -e "require('@firebase/database')" -> Error: Cannot find module '@firebase/app'
Verified by running
Node v22.14.0, a fresh directory with only @firebase/database@1.1.5 installed: require threw 'Cannot find module @firebase/app' and a real ESM import threw ERR_MODULE_NOT_FOUND for the same specifier. Control: npm i @firebase/app beside it and both loads succeeded. npm view confirms peerDependencies = {} and @firebase/app absent from dependencies at 1.1.5.
Checked against
@firebase/database 1.1.5, the current release (published 2026-08-19). Checked 2026-09-10.
Note
Found 2026-09-10 by the reading finders (scripts/scan-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack, 113 hits, 6 packages new to this file; verified by hand with a fixed-side control. MODEST, AND THE PACKAGE SAYS WHY: its README opens with 'not intended for direct usage, and should only be used via the officially supported firebase package', which carries @firebase/app. The row is recorded because the manifest is the contract npm and pnpm enforce and it is wrong; it is not drafted, because the package disclaims the consumer it would break. firebase/firebase-js-sdk#10242 (closed 2026-08-05) is the same omission one package over, @firebase/database-compat's standalone bundle, where it broke firebase-admin on boot; the maintainer's fix added @firebase/app to that package's peerDependencies, and this package's peerDependencies is still empty.

global-agent

1 finding

undelivered-entryAlready reportedNot a vulnerability

UNDELIVERED_ENTRY

global-agent 4.1.3's manifest names `typings: ./dist/src/index.d.ts`, a file the package does not ship (the declarations are at dist/index.d.ts), so every TypeScript consumer gets TS7016 'Could not find a declaration file for module global-agent' under node10 and nodenext alike.

npm i global-agent typescript && echo "import {bootstrap} from 'global-agent'; bootstrap();" > t.ts && npx tsc --noEmit --strict t.ts -> error TS7016: Could not find a declaration file for module 'global-agent'
Verified by running
Node v22.14.0, TypeScript 5, a fresh directory with global-agent@4.1.3: tsc --strict reported TS7016 under both --moduleResolution node16 and node10. Control: editing the installed manifest's typings to ./dist/index.d.ts made the TS7016 disappear (only TS2688 for the absent @types/node remained, which is the test directory's, not the package's).
Checked against
global-agent 4.1.3, the current release (published 2026-03-11). Checked 2026-09-10.
Note
Found 2026-09-10 by the reading finders (scripts/scan-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack, 113 hits, 6 packages new to this file; verified by hand with a fixed-side control. Already reported: gajus/global-agent#80 (open, 2026-03-12, 'Typescript types incorrectly configured in package.json') and two open fix PRs, #84 and #91, that correct the typings path; the finder reached it independently from the manifest. Not drafted.

@azure/msal-browser

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

@azure/msal-browser 5.21.0 declares engines.node '>=0.8.0' and ships lib/msal-browser.cjs, its require entry, with optional chaining (line 703 and 52 further sites), which no Node before 14 parses; on any version the manifest admits and the grammar does not, loading the file is a SyntaxError.

node -e "const p=require('@azure/msal-browser/package.json'); console.log(p.engines.node)"; grep -c '?\.' node_modules/@azure/msal-browser/lib/msal-browser.cjs -> >=0.8.0 and a non-zero count
Verified by running
Read, not run: engines.node '>=0.8.0' in the installed manifest, optional chaining counted in the shipped require entry. No Node 0.x or 12 was installed to witness the SyntaxError; the claim rests on the grammar's history, the same basis as every other engines row.
Checked against
@azure/msal-browser 5.21.0, the current release. Checked 2026-09-10.
Note
Found 2026-09-10 by the reading finders (scripts/scan-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack, 113 hits, 6 packages new to this file; verified by hand with a fixed-side control. An engines field admitting Node lines nothing in the file can run on; the same shape as the twelve engines rows before it. @azure/msal-common 16.14.0 beside it carries the identical field over 44 sites in lib/index-browser.cjs and is recorded under this row rather than as a second one. No prior report found. Real, not worth a maintainer's time as a filing, not drafted.

@cypress/request-promise

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

@cypress/request-promise 5.0.0, over @cypress/request 3.0.10, re-sends Cookie and Proxy-Authorization to whatever host a 302 names: lib/redirect.js:143-147 removes only `authorization` when the hostname changes, and the two other credential headers cross with the request.

probe-installed REDIRECT_CREDENTIAL_LEAK against @cypress/request-promise: a request to localhost carrying Cookie and Proxy-Authorization, answered 302 to 127.0.0.1 -> FINDING: carried Cookie and Proxy-Authorization across a redirect to a DIFFERENT HOST
Verified by running
Node v22.14.0, the probe's own two-server witness: the credential sent to localhost arrived at 127.0.0.1 with Cookie and Proxy-Authorization intact and Authorization stripped, the last being the fixed-side control that lib/redirect.js:147 provides for one header of three.
Checked against
@cypress/request-promise 5.0.0 and @cypress/request 3.0.10, the current releases. Checked 2026-09-10.
Note
Found 2026-09-10 by the running probes (scripts/probe-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack: 9 proven, 5 of them already in this file; the probe stands up a local server that answers 302 to a different host and reads which headers arrive there. Verified against the redirect code of the package that owns the loop, with the fixed-side control being the header the same code does strip. The same omission as the archived `request` this package forks, which CVE-2023-28155 (cypress-io/request#27, closed) addressed for a different redirect shape (SSRF via cross-protocol) without touching which headers follow a cross-host redirect. Cookie is only carried when the caller sets the header directly rather than through a jar; a jar is scoped by host. Draft to be filed privately with cypress-io/request, the package that owns the loop.

popsicle

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

popsicle 12.1.2, through popsicle-redirects 1.1.1, re-sends Proxy-Authorization to whatever host a 302 names: dist/index.js:48 deletes `authorization` on a cross-host redirect and nothing deletes `proxy-authorization`.

probe-installed REDIRECT_CREDENTIAL_LEAK against popsicle: a request to localhost carrying Proxy-Authorization, answered 302 to 127.0.0.1 -> FINDING: carried Proxy-Authorization across a redirect to a DIFFERENT HOST
Verified by running
Node v22.14.0, the probe's two-server witness: Proxy-Authorization arrived at 127.0.0.1; Authorization did not, which is dist/index.js:48 working and the control for the line beside it that is missing.
Checked against
popsicle 12.1.2 and popsicle-redirects 1.1.1, the current releases. Checked 2026-09-10.
Note
Found 2026-09-10 by the running probes (scripts/probe-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack: 9 proven, 5 of them already in this file; the probe stands up a local server that answers 302 to a different host and reads which headers arrive there. Verified against the redirect code of the package that owns the loop, with the fixed-side control being the header the same code does strip. MODEST: one header, and the narrowest shape of the class in this file; Authorization and Cookie are handled (the cookie jar is a separate middleware scoped by host). Recorded; a draft is one line for serviejs/popsicle-redirects, the package that owns the loop.

request-light

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

request-light 0.8.0 re-sends Authorization, Cookie and Proxy-Authorization, and the `user`/`password` basic-auth pair, to whatever host a redirect names: lib/node/main.js follows a 3xx by calling xhr again with the caller's whole `headers` object, `user` and `password` copied onto the new URL, with no host comparison anywhere in the file.

probe-installed REDIRECT_CREDENTIAL_LEAK against request-light: a request to localhost carrying Authorization, Cookie and Proxy-Authorization, answered 302 to 127.0.0.1 -> FINDING: carried Authorization and Cookie and Proxy-Authorization across a redirect to a DIFFERENT HOST
Verified by running
Node v22.14.0, the probe's two-server witness: all three headers arrived at 127.0.0.1. Read in lib/node/main.js (minified): the redirect branch builds `{type, url: location, user, password, headers: e.headers, ...}` and calls xhr on it; the fixed-side control here is the other packages in this file that compare hosts before copying, since this one has no such branch to flip.
Checked against
request-light 0.8.0, the current release. Checked 2026-09-10.
Note
Found 2026-09-10 by the running probes (scripts/probe-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack: 9 proven, 5 of them already in this file; the probe stands up a local server that answers 302 to a different host and reads which headers arrive there. Verified against the redirect code of the package that owns the loop, with the fixed-side control being the header the same code does strip. The widest shape of the class beside get-uri: nothing is stripped. This is the HTTP client behind the VS Code language servers (json, css, html) and their schema fetches, so the credential at risk is whatever a `http.proxyAuthorization` or custom header setting carries when a schema URL redirects off-host. No prior report found in microsoft/node-request-light (21 search results, all dependabot). Draft: private report to Microsoft's MSRC channel, since the repository has no SECURITY.md contact of its own.

openid-client

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

openid-client 5.7.1's types/index.d.ts:392 declares `export class DeviceFlowHandle`, and the runtime index exports Issuer, Strategy, TokenSet, errors, custom and generators only; `handle instanceof DeviceFlowHandle` or `new DeviceFlowHandle(...)` typechecks and throws, because lib/device_flow_handle.js is required by client.js and never re-exported.

node -e "const o=require('openid-client'); console.log(Object.keys(o), typeof o.DeviceFlowHandle)" -> [ 'Issuer', 'Strategy', 'TokenSet', 'errors', 'custom', 'generators' ] undefined
Verified by running
Node v22.14.0 against the installed 5.7.1: the runtime export list above, DeviceFlowHandle undefined; types/index.d.ts:392 read. openid-client@6.8.8 installed alone: the declaration is gone and the two device-flow functions are exported.
Checked against
openid-client 5.7.1, the last of the 5.x line; the current 6.8.8 is a rewrite that exports initiateDeviceAuthorization and pollDeviceAuthorizationGrant as functions and declares no DeviceFlowHandle class. Checked 2026-09-10.
Fixed upstream in
6.0.0
Note
Found 2026-09-10 by the running probes (scripts/probe-installed.ts) over the seventh sweep tree, 500 packages of cloud SDKs, auth, proxies and the express stack: 9 proven, 5 of them already in this file; the probe stands up a local server that answers 302 to a different host and reads which headers arrive there. Verified against the redirect code of the package that owns the loop, with the fixed-side control being the header the same code does strip. MODEST: the class is only ever obtained from client.deviceAuthorization(), so the reachable misuse is `instanceof`, not construction. Fixed by the 6.x rewrite rather than by a report; recorded for the corpus, not drafted. No prior report mentions the export (four panva/openid-client results for the name, none about it).

property-information

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

property-information 7.2.0's find() answers differently on consecutive identical calls for a data property whose name carries a dash: lib/find.js:10 declares `dash` with the g flag and :62 tests it with `.test()` and no reset, so find(html, 'datafoo-bar') gives the attribute 'datafoo-bar' on the first call and 'data-foo-bar' on the second.

node --input-type=module -e "import {find, html} from 'property-information'; console.log([1,2,3].map(() => find(html, 'datafoo-bar').attribute))" -> [ 'datafoo-bar', 'data-foo-bar', 'datafoo-bar' ]
Verified by running
Node v22.14.0 against the installed 7.2.0: three identical calls returned attribute 'datafoo-bar', 'data-foo-bar', 'datafoo-bar'; the property field was 'datafoo-bar' all three times. Control: find(html, 'dataFooBar'), which has no dash for the regex to advance past, returned 'data-foo-bar' on both calls.
Checked against
property-information 7.2.0, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eighth sweep tree, 846 packages of parsers, templating, validation, dates, logging, database clients, sockets, queues, CLI and crypto; 19 hits, 3 new to this file. The same shape as content-disposition and filenamify: a global regex reused through .test(). MODEST: the input is a `data` name written in property form that still carries a dash, which is the malformed half of the API's contract; hast-util-to-dom, hastscript and every rehype plugin reach find() with attribute names from parsed HTML, which take the first branch and are unaffected. Dropping the g flag on `dash` is the whole fix, since the replace on line 56 does not depend on it. No prior report in wooorm/property-information (three results for the terms, none about it). Draft: public issue, one line.

sisteransi

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

sisteransi 1.0.5's src/sisteransi.d.ts:2 declares `export const clear: string` and src/index.js:58 exports cursor, scroll, erase and beep only, so `import {clear} from 'sisteransi'` typechecks and `clear` is undefined at runtime.

node -e "const s=require('sisteransi'); console.log(Object.keys(s), typeof s.clear)" -> [ 'cursor', 'scroll', 'erase', 'beep' ] undefined
Verified by running
Node v22.14.0 against the installed 1.0.5: the runtime export list above, `clear` undefined, the d.ts line read. sisteransi@2.0.0 installed alone: `import * as s` lists beep, clear, cursor, erase, scroll and `clear` is an object.
Checked against
sisteransi 1.0.5, the last of the 1.x line and the one prompts, enquirer and every other consumer in the tree pin; the current 2.0.0 (an ESM and TypeScript rewrite) exports a `clear` object. Checked 2026-09-11.
Fixed upstream in
2.0.0
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the eighth sweep tree, 846 packages: 7 proven, 6 of them already in this file. Fixed by the 2.0 rewrite (terkelg/sisteransi#10 asked for a clear API and #15 shipped the rewrite) rather than by a report naming the missing export; 1.x is what the ecosystem installs, and there the declaration is still wrong. Recorded for the corpus, not drafted.

cli-tableau

1 finding

prototype-pollutionNo prior report

PROTOTYPE_POLLUTION

cli-tableau 2.0.1's option merge (lib/utils.js:34, `for (var p in opts)` recursing into plain objects) writes a `__proto__` key from the options object onto Object.prototype, so `new Table(JSON.parse('{"__proto__":{"polluted":1}}'))` leaves `polluted` on every object in the process.

node -e "const Table=require('cli-tableau'); new Table(JSON.parse('{\"__proto__\":{\"polluted\":1}}')); console.log('polluted' in {}, ({}).polluted)" -> true 1
Verified by running
Node v22.14.0 against the installed 2.0.1: the reproduction printed `true 1`. Control: the probe's 22 calls polluted on 3, the `__proto__`-keyed payload only, and Automattic/cli-table 0.3.11 with the upstream guard is the fixed-side reading of the same function.
Checked against
cli-tableau 2.0.1, the current and only recent release (2022-04-12); pm2 pins it exactly. Checked 2026-09-11.
Note
Found 2026-09-11 by the ninth sweep (scripts/probe-installed.ts and scan-installed.ts) over 1,026 packages of classic libraries, HTTP clients and servers, i18n, string and process utilities: probes 7 proven (3 known), 1,057 clean, 2,105 abstained, 43 timed out, 2 crashed; scanner 23 hits over 13 packages. Verified by hand with a control. This is Keymetrics' fork of Automattic/cli-table, and the same function was fixed upstream in 2021 (Automattic/cli-table#141, #142 via huntr: skip `__proto__`, `constructor` and `prototype` in `options()`); the fork never took the three lines. Reach: any caller building table options from external input; pm2 itself passes literal options. No advisory for cli-tableau on GitHub. Draft: private report to keymetrics/cli-tableau.

exceljs

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

exceljs 4.4.0's index.d.ts:901 declares `export class Anchor implements IAnchor` with a constructor, and the runtime index exports nine values with no Anchor among them, so `new ExcelJS.Anchor()` typechecks and throws 'Anchor is not a constructor'.

node -e "const e=require('exceljs'); console.log(typeof e.Anchor, Object.keys(e))" -> undefined [ ... nine names, no Anchor ]
Verified by running
Node v22.14.0 against the installed 4.4.0: typeof Anchor undefined, nine runtime exports listed; index.d.ts:901 read. Control: the other seven declared classes the probe checked exist at runtime.
Checked against
exceljs 4.4.0, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the ninth sweep (scripts/probe-installed.ts and scan-installed.ts) over 1,026 packages of classic libraries, HTTP clients and servers, i18n, string and process utilities: probes 7 proven (3 known), 1,057 clean, 2,105 abstained, 43 timed out, 2 crashed; scanner 23 hits over 13 packages. Verified by hand with a control. MODEST: Anchor is only ever obtained from an image range, so the reachable misuse is construction from the types (exceljs/exceljs#1747, open, is a consumer fighting exactly this type). Recorded; a draft is one line for exceljs/exceljs.

pm2

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

pm2 7.0.4's types/index.d.ts:204 declares `export function launchSysMonitoring(errback?)`, and the runtime API exports 30 of the 31 declared functions with this one absent, so a caller following the types gets 'launchSysMonitoring is not a function'.

node -e "const p=require('pm2'); console.log(typeof p.launchSysMonitoring)" -> undefined
Verified by running
Node v22.14.0 against the installed 7.0.4: typeof launchSysMonitoring undefined; the probe found 30 of 31 declared functions present, which is the control.
Checked against
pm2 7.0.4, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the ninth sweep (scripts/probe-installed.ts and scan-installed.ts) over 1,026 packages of classic libraries, HTTP clients and servers, i18n, string and process utilities: probes 7 proven (3 known), 1,057 clean, 2,105 abstained, 43 timed out, 2 crashed; scanner 23 hits over 13 packages. Verified by hand with a control. The system-monitoring feature it names was moved out (Unitech/pm2#5091, 'Cannot find module pm2-sysmonit', closed) and the declaration stayed. Recorded, not drafted.

xlsx

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

xlsx 0.18.5's types/index.d.ts:18-20 declares `set_fs` and `set_cptable`, and the npm build's runtime exports neither (16 exports, no such names in xlsx.js), so the documented `XLSX.set_fs(fs)` call typechecks and throws.

node -e "const x=require('xlsx'); console.log(typeof x.set_fs, typeof x.set_cptable)" -> undefined undefined
Verified by running
Node v22.14.0 against the installed 0.18.5: both typeof undefined, 16 runtime exports; the two d.ts lines read and `grep set_fs xlsx.js` empty. Control: the twelve other declared values the probe checked are present.
Checked against
xlsx 0.18.5, the last release on the npm registry (2022); SheetJS publishes 0.20.x from its own CDN, where set_fs exists. Checked 2026-09-11.
Fixed upstream in
0.19.x, on cdn.sheetjs.com only
Note
Found 2026-09-11 by the ninth sweep (scripts/probe-installed.ts and scan-installed.ts) over 1,026 packages of classic libraries, HTTP clients and servers, i18n, string and process utilities: probes 7 proven (3 known), 1,057 clean, 2,105 abstained, 43 timed out, 2 crashed; scanner 23 hits over 13 packages. Verified by hand with a control. The npm package is what `npm install xlsx` still hands out and its types describe the CDN build. Recorded, not drafted: SheetJS has said the npm line is unmaintained.

postcss-minify-selectors

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

postcss-minify-selectors 8.0.5's exports map names `./src/lib/foldTols` and `./src/lib/foldTols.js`, pointing at ./src/lib/foldTols.js, a file the package does not ship: the file is foldToIs.js, and the two keys carry a typo, so `require('postcss-minify-selectors/src/lib/foldTols')` throws MODULE_NOT_FOUND while the sibling key `./src/lib/canUnquote` resolves.

node -e "try{require('postcss-minify-selectors/src/lib/foldTols')}catch(e){console.log(e.code)}; require('postcss-minify-selectors/src/lib/foldToIs'); console.log('foldToIs resolves')" -> MODULE_NOT_FOUND then foldToIs resolves
Verified by running
Node v22.14.0 against the installed 8.0.5: the `foldTols` subpath threw MODULE_NOT_FOUND; the control, `foldToIs` by its real name through the package's own resolution, loaded. The exports map and the src/lib listing read from the installed package.
Checked against
postcss-minify-selectors 8.0.5, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep (scripts/scan-installed.ts and probe-installed.ts) over 860 packages of UI frameworks, GraphQL, ORMs, media and testing tooling: scanner 64 hits over 33 packages, probes 11 proven (6 known), 895 clean, 1,796 abstained, 28 timed out, 2 crashed. Verified by hand with a control. Four scanner hits, all the same two keys under two conditions. No prior report in cssnano/cssnano for the name. Draft: public issue, one line.

react-router

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

react-router 7.18.3's exports map names, for `./internal` under the default condition, `types: ./dist/development/lib/types/index.d.ts`, a file the package does not ship (the directory holds internal.d.ts and internal.d.mts), so a TypeScript consumer resolving `react-router/internal` under the bundler condition gets TS2307.

node -e "console.log(JSON.stringify(require('react-router/package.json').exports['./internal']))"; ls node_modules/react-router/dist/development/lib/types/ -> default.types names index.d.ts; the directory has no index.d.ts
Verified by running
Read from the installed package: the exports entry above and `ls` of dist/development/lib/types showing internal.d.ts, internal.d.mts and no index.d.ts. Control: a real ESM import of react-router/internal loads, which is the import condition doing its job.
Checked against
react-router 7.18.3, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep (scripts/scan-installed.ts and probe-installed.ts) over 860 packages of UI frameworks, GraphQL, ORMs, media and testing tooling: scanner 64 hits over 33 packages, probes 11 proven (6 known), 895 clean, 1,796 abstained, 28 timed out, 2 crashed. Verified by hand with a control. The node and import conditions name internal.d.ts and internal.d.mts, which exist; only the default fallback names the absent index.d.ts, so this reaches consumers whose resolver takes neither (bundler condition without import). Modest and specific. Recorded; a draft is one line for remix-run/react-router.

preact

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

preact 10.29.8's exports map names `./compat/server.browser` with `types: ./compat/server.d.ts`, a file the package does not ship (compat/ holds server.browser.js, server.js and server.mjs and no server.d.ts), so a TypeScript consumer importing `preact/compat/server.browser` gets TS7016.

node -e "console.log(JSON.stringify(require('preact/package.json').exports['./compat/server.browser']))"; ls node_modules/preact/compat/ | grep server -> types names server.d.ts; the directory has no .d.ts
Verified by running
Read from the installed package: the exports entry and the compat/ listing. Control: the runtime target server.browser.js exists and the sibling `./compat` subpath's types resolve.
Checked against
preact 10.29.8, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep (scripts/scan-installed.ts and probe-installed.ts) over 860 packages of UI frameworks, GraphQL, ORMs, media and testing tooling: scanner 64 hits over 33 packages, probes 11 proven (6 known), 895 clean, 1,796 abstained, 28 timed out, 2 crashed. Verified by hand with a control. preactjs/preact#4997 and #4981 are open and recent type-path issues on other subpaths; none names this one. Recorded; a draft is one line.

@lit/reactive-element

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

@lit/reactive-element 2.1.2's exports map names, for `./polyfill-support.js` under the node condition, ./node/development/polyfill-support.js and ./node/polyfill-support.js, neither of which the package ships, so `import '@lit/reactive-element/polyfill-support.js'` from Node throws ERR_MODULE_NOT_FOUND while the browser condition's targets exist.

node --input-type=module -e "import '@lit/reactive-element/polyfill-support.js'" -> ERR_MODULE_NOT_FOUND
Verified by running
Node v22.14.0 against the installed 2.1.2: the real ESM import above threw ERR_MODULE_NOT_FOUND; `ls node/development/` and `ls node/` show no polyfill-support.js. Control: the same import of `@lit/reactive-element` itself resolves.
Checked against
@lit/reactive-element 2.1.2, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep (scripts/scan-installed.ts and probe-installed.ts) over 860 packages of UI frameworks, GraphQL, ORMs, media and testing tooling: scanner 64 hits over 33 packages, probes 11 proven (6 known), 895 clean, 1,796 abstained, 28 timed out, 2 crashed. Verified by hand with a control. The polyfill is a browser concern, so a Node import of it is an unusual thing to do; the map still promises a Node target it does not ship, and SSR bundlers that resolve the node condition are the reach. Recorded; a draft is one line for lit/lit.

recoil

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

recoil 0.7.7's index.d.ts declares `export class Snapshot`, `export class MutableSnapshot`, `export class RecoilState`, `export class RecoilValueReadOnly` and `export const RecoilBridge`, and the runtime exports none of the five (35 of 40 declared values exist), so `snapshot instanceof Snapshot` or `new Snapshot()` typechecks and throws.

node -e "const r=require('recoil'); console.log(['Snapshot','MutableSnapshot','RecoilBridge','RecoilState','RecoilValueReadOnly'].map(k=>k+':'+typeof r[k]).join(' '))" -> all five undefined
Verified by running
Node v22.14.0 against the installed 0.7.7: all five typeof undefined; index.d.ts lines 75, 87 and 321 read. Control: the probe found 35 of the 40 declared values present.
Checked against
recoil 0.7.7, the last release; the repository was archived by Meta in January 2025. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep (scripts/scan-installed.ts and probe-installed.ts) over 860 packages of UI frameworks, GraphQL, ORMs, media and testing tooling: scanner 64 hits over 33 packages, probes 11 proven (6 known), 895 clean, 1,796 abstained, 28 timed out, 2 crashed. Verified by hand with a control. The classes are only ever obtained from hooks and callbacks, so `instanceof` is the reachable misuse. No prior report names the missing exports. Archived upstream: recorded for the corpus, not drafted.

@vue/runtime-core

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

@vue/runtime-core 3.5.42's dist/runtime-core.d.ts:1312 declares `export declare function createBaseVNode` and the runtime exports it only under the alias createElementVNode (d.ts:1836), so `createBaseVNode()` typechecks and throws 'createBaseVNode is not a function'; @vue/server-renderer's renderVNode (exported only as ssrRenderVNode) and @vue/reactivity's ComputedRefImpl class are the same shape in the same release.

node -e "console.log(typeof require('@vue/runtime-core').createBaseVNode, typeof require('@vue/runtime-core').createElementVNode)" -> undefined function
Verified by running
Node v22.14.0 against the installed 3.5.42: createBaseVNode undefined, createElementVNode a function; renderVNode undefined on @vue/server-renderer; ComputedRefImpl undefined on @vue/reactivity; the d.ts lines read. Control: the probes found 98 of 99, 24 of 25 and 50 of 51 declared values present.
Checked against
vue 3.5.42, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep (scripts/scan-installed.ts and probe-installed.ts) over 860 packages of UI frameworks, GraphQL, ORMs, media and testing tooling: scanner 64 hits over 33 packages, probes 11 proven (6 known), 895 clean, 1,796 abstained, 28 timed out, 2 crashed. Verified by hand with a control. The bundled declarations are api-extractor output that turns an internal `export` into a public `export declare` (the shape already recorded on @vue/compiler-sfc, mongodb and bson; microsoft/rushstack#3616 owns it). Three packages in one release; one row. Recorded, not drafted.

@parcel/runtime-rsc

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

@parcel/runtime-rsc 2.16.4's exports map names `./jsx-dev-runtime` as ./jsx-dev-runtime.js and `./jsx-runtime` as ./jsx-runtime, and the package ships neither (its files are lib/, src/, rsc-helpers.jsx and package.json), so a JSX transform pointed at this package as its jsxImportSource fails to resolve both runtimes.

node -e "console.log(JSON.stringify(require('@parcel/runtime-rsc/package.json').exports))"; ls node_modules/@parcel/runtime-rsc -> two runtime keys; no jsx-dev-runtime.js and no jsx-runtime in the listing
Verified by running
Read from the installed package: the exports map and the directory listing above. Control: `./rsc-helpers` resolves to a file that exists.
Checked against
@parcel/runtime-rsc 2.16.4, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep's second tree (scripts/scan-installed.ts over the 109 packages that refused to install beside the first tree and went in with --legacy-peer-deps; 1,859 packages resolved, 105 hits over 20 packages). Verified by hand with a control. The sibling key `./rsc-helpers` names rsc-helpers.jsx, which is shipped, and is the control. No prior report in parcel-bundler/parcel names the two keys. Recorded; a draft is one line.

expect

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

expect 30.5.1's exports map names `./build/matchers` as ./build/matchers.js and `./build/toThrowMatchers` beside it, and build/ holds index.js, index.mjs and index.d.ts only, so `require('expect/build/matchers')` throws MODULE_NOT_FOUND while the root entry loads.

node -e "try{require('expect/build/matchers')}catch(e){console.log(e.code)}; require('expect'); console.log('root loads')" -> MODULE_NOT_FOUND then root loads
Verified by running
Node v22.14.0 against the installed 30.5.1: the subpath threw MODULE_NOT_FOUND and the root entry loaded; the build/ listing read from the installed package.
Checked against
expect 30.5.1, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the tenth sweep's second tree (scripts/scan-installed.ts over the 109 packages that refused to install beside the first tree and went in with --legacy-peer-deps; 1,859 packages resolved, 105 hits over 20 packages). Verified by hand with a control. Jest 30 bundles the package into one file and the exports map still promises the pre-bundle module paths; jestjs/jest#10329 (closed) is the request that made `./build/matchers` public in the first place. Recorded; a draft is one line for jestjs/jest.

@whatwg-node/node-fetch

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

@whatwg-node/node-fetch 0.8.6 (the fetch behind @whatwg-node/fetch 0.10.13, GraphQL Yoga and GraphQL Mesh) follows a redirect by constructing `new PonyfillRequest(redirectedUrl, fetchRequest)` from the original request (cjs/fetchNodeHttp.js:92-93) with no host comparison, so Authorization, Cookie and Proxy-Authorization are re-sent to whatever host the Location header names.

probe-installed REDIRECT_CREDENTIAL_LEAK against @whatwg-node/fetch and @whatwg-node/node-fetch: a request to localhost carrying Authorization, Cookie and Proxy-Authorization, answered 302 to 127.0.0.1 -> FINDING: carried Authorization and Cookie and Proxy-Authorization across a redirect to a DIFFERENT HOST
Verified by running
Node v22.14.0, the probe's two-server witness against both packages: all three headers arrived at 127.0.0.1. Read at cjs/fetchNodeHttp.js:91-96: the redirect re-issues the original PonyfillRequest against the new URL; there is no branch comparing hosts to flip, so the control is the spec's own step and the other packages in this file that implement it.
Checked against
@whatwg-node/node-fetch 0.8.6 and @whatwg-node/fetch 0.10.13, the current releases. Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the tenth sweep's second tree, 1,859 packages: 22 proven, 15 of them already in this file; 1,849 clean, 3,940 abstained, 86 timed out, 7 crashed (jest and jest-cli, the probe's own harness driving a CLI). Verified by hand with a control. The widest shape of the class, beside get-uri and request-light: nothing is stripped, and the WHATWG fetch spec this package ponyfills requires stripping Authorization on a cross-origin redirect (Fetch, HTTP-redirect fetch, step 13). The curl transport (fetchCurl.js) delegates redirects to libcurl, which strips by default, so the reach is the default Node http transport. No prior report in ardatan/whatwg-node names it (652 results for the terms, dependabot noise). Draft: private report to ardatan/whatwg-node.

apollo-server-env

1 finding

redirect-credential-leakNo prior report

CREDENTIAL_LEAK_ACROSS_REDIRECT

apollo-server-env 4.2.1 re-exports node-fetch 2's fetch, which re-sends Proxy-Authorization to whatever host a redirect names (the omission already recorded on node-fetch), so every Apollo Server 2 and 3 plugin that fetched through this package carried the proxy credential across cross-host redirects.

probe-installed REDIRECT_CREDENTIAL_LEAK against apollo-server-env: a request to localhost carrying Proxy-Authorization, answered 302 to 127.0.0.1 -> FINDING: carried Proxy-Authorization across a redirect to a DIFFERENT HOST
Verified by running
Node v22.14.0, the probe's witness: Proxy-Authorization arrived at 127.0.0.1; Authorization did not, which is node-fetch 2's own strip and the control.
Checked against
apollo-server-env 4.2.1, the last release; deprecated on npm since Apollo Server 2 and 3 reached end of life (2023-10-22 and 2024-10-22). Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the tenth sweep's second tree, 1,859 packages: 22 proven, 15 of them already in this file; 1,849 clean, 3,940 abstained, 86 timed out, 7 crashed (jest and jest-cli, the probe's own harness driving a CLI). Verified by hand with a control. Derived from node-fetch 2 (dependencies: node-fetch ^2.6.7), the way pacote and npm-registry-fetch derive from make-fetch-happen; the package itself adds nothing to the redirect. Deprecated upstream: recorded for the corpus, not drafted.

eslint-scope

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

eslint-scope 9.1.2's lib/index.d.cts declares twelve scope classes as exports (GlobalScope, ModuleScope, FunctionExpressionNameScope, CatchScope, WithScope, BlockScope, SwitchScope, FunctionScope, ForScope, ClassScope, ClassFieldInitializerScope, ClassStaticBlockScope, lines 348-625) and the runtime exports nine names with none of them, so `scope instanceof GlobalScope` typechecks and throws 'GlobalScope is not defined' at the import.

node -e "const e=require('eslint-scope'); console.log(Object.keys(e), typeof e.GlobalScope)" -> nine names, no scope subclass; undefined
Verified by running
Node v22.14.0 against the installed 9.1.2: the runtime export list above; the twelve `export class` lines read in lib/index.d.cts. Control: the probe found 9 of 21 declared values present, the nine the runtime exports.
Checked against
eslint-scope 9.1.2, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the tenth sweep's second tree, 1,859 packages: 22 proven, 15 of them already in this file; 1,849 clean, 3,940 abstained, 86 timed out, 7 crashed (jest and jest-cli, the probe's own harness driving a CLI). Verified by hand with a control. The declarations arrived with eslint/js#709 ('feat: add types to ESLint Scope', closed) and describe the internal class hierarchy the runtime keeps private; `Scope` and `ScopeManager` are exported and are the control. The reachable misuse is a type guard by `instanceof` on a scope's `type`, which the types invite. Recorded; a draft is one line for eslint/js.

@apollo/protobufjs

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

@apollo/protobufjs 1.2.8's index.d.ts:289 declares `export class FieldBase extends ReflectionObject` and `export class NamespaceBase`, and the runtime exports neither, so `new FieldBase()` or `instanceof FieldBase` typechecks and throws; the fork carries the shape already recorded on protobufjs, whose own d.ts calls the class 'not an actual class'.

node -e "const p=require('@apollo/protobufjs'); console.log(typeof p.FieldBase, typeof p.NamespaceBase)" -> undefined undefined
Verified by running
Node v22.14.0 against the installed 1.2.8: both typeof undefined; index.d.ts:289 read. Control: the probe found 27 of 29 declared values present.
Checked against
@apollo/protobufjs 1.2.8, the current release of the fork. Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the tenth sweep's second tree, 1,859 packages: 22 proven, 15 of them already in this file; 1,849 clean, 3,940 abstained, 86 timed out, 7 crashed (jest and jest-cli, the probe's own harness driving a CLI). Verified by hand with a control. Same finding as the protobufjs row, inherited by the fork. Recorded, not drafted.

weak-lru-cache

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

weak-lru-cache 1.2.2's index.d.ts:16 declares `export class CacheEntry<V>`, the value type every entry of `WeakLRUCache extends Map<K, CacheEntry<V>>` carries, and the runtime exports LRFUExpirer and WeakLRUCache only, so `entry instanceof CacheEntry` typechecks and throws.

node -e "const p=require('weak-lru-cache'); console.log(typeof p.CacheEntry, Object.keys(p))" -> undefined [ 'LRFUExpirer', 'WeakLRUCache' ]
Verified by running
Node v22.14.0 against the installed 1.2.2: typeof undefined and the two runtime exports listed; index.d.ts:16 read. Control: the probe found 2 of 3 declared values present.
Checked against
weak-lru-cache 1.2.2, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the tenth sweep's second tree, 1,859 packages: 22 proven, 15 of them already in this file; 1,849 clean, 3,940 abstained, 86 timed out, 7 crashed (jest and jest-cli, the probe's own harness driving a CLI). Verified by hand with a control. No prior report in kriszyp/weak-lru-cache. Modest; recorded, a draft is one line.

@prisma/query-plan-executor

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

@prisma/query-plan-executor 7.2.0's dist/index.d.ts:3245 declares `export declare class TransactionManager`, which the exported executor's constructor takes as a parameter (d.ts:26), and the runtime does not export it, so a consumer constructing the executor as the types describe gets 'TransactionManager is not a constructor'.

node -e "const p=require('@prisma/query-plan-executor'); console.log(typeof p.TransactionManager)" -> undefined
Verified by running
Node v22.14.0 against the installed 7.2.0: typeof undefined; d.ts:26 and :3245 read. Control: the probe found 8 of 9 declared values present.
Checked against
@prisma/query-plan-executor 7.2.0, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes (scripts/probe-installed.ts) over the tenth sweep's second tree, 1,859 packages: 22 proven, 15 of them already in this file; 1,849 clean, 3,940 abstained, 86 timed out, 7 crashed (jest and jest-cli, the probe's own harness driving a CLI). Verified by hand with a control. An internal package of the Prisma 7 client; the class is constructed inside the client, which is why nothing outside it has noticed. Recorded, not drafted.

lightship

1 finding

cjs-binding-in-esmAlready reportedNot a vulnerability

CJS_BINDING_IN_DECLARED_ESM

lightship 9.0.4 declares `"type": "module"` and points its `require` export at ./dist/cjs/index.js, a CommonJS build that assigns `exports.createLightship` with no nested package.json to make it CommonJS, so `require('lightship')` fails on the current release while the ESM import works.

node -e "require('lightship')" -> fails (MODULE_NOT_FOUND on the installed tree); node --input-type=module -e "import {createLightship} from 'lightship'; console.log(typeof createLightship)" -> function
Verified by running
Node v22.14.0 against the installed 9.0.4: the CommonJS require failed and the ESM import returned a function, which is the control; dist/cjs holds no package.json and dist/cjs/index.js:3 is `exports.createLightship = void 0`.
Checked against
lightship 9.0.4, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. Twenty-seven hits, every `exports.` assignment in the CJS build. Already reported: gajus/lightship#67 ('Does not generate valid cjs') and #69 ('CommonJS issue'), both open; the finder reached it independently from the manifest. Not drafted.

ethereum-cryptography

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

ethereum-cryptography 2.2.1's exports map names `./bip39/wordlists/portuguese` and `./bip39/wordlists/portuguese.js` (types, import and default targets), and the package ships nine wordlists with no portuguese among them, so `require('ethereum-cryptography/bip39/wordlists/portuguese')` throws MODULE_NOT_FOUND while the nine siblings load.

node -e "try{require('ethereum-cryptography/bip39/wordlists/portuguese')}catch(e){console.log(e.code)}; require('ethereum-cryptography/bip39/wordlists/spanish'); console.log('spanish loads')" -> MODULE_NOT_FOUND then spanish loads
Verified by running
Node v22.14.0 against the installed 2.2.1: portuguese threw MODULE_NOT_FOUND, spanish loaded; the wordlists directory listing shows czech, english, french, italian, japanese, korean, simplified-chinese, spanish and traditional-chinese only.
Checked against
ethereum-cryptography 2.2.1, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. No prior report found (the repository search is closed to the token; the npm page lists ten wordlists in its README and ships nine). Draft: public issue, one line.

formdata-node

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

formdata-node 6.0.3's exports map names, for `./file-from-path`, `types: ./@lib/file-from-path.d.ts` under import and `./@lib/file-from-path.d.cts` under require, and no @lib directory is shipped (the declarations sit in lib/ beside the runtime files), so a TypeScript consumer of `formdata-node/file-from-path` gets TS7016 while the runtime resolves.

node -e "console.log(JSON.stringify(require('formdata-node/package.json').exports['./file-from-path']))"; ls node_modules/formdata-node/@lib -> types under @lib/; No such file or directory
Verified by running
Read from the installed package: the exports entry and `ls lib/` showing file-from-path.d.ts and file-from-path.d.cts where the map says @lib/. Control: the runtime targets lib/file-from-path.js and .cjs exist.
Checked against
formdata-node 6.0.3, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. octet-stream/form-data#48 ('Fix package exports for TypeScript', closed) fixed the root entry's types path in 2023; the subpath kept the old prefix. Two hits, one per condition. Recorded; a draft is one line.

cbor-x

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

cbor-x 1.6.6's exports map names `types.require: ./index.d.cts`, a file the package does not ship (index.d.ts, decode.d.ts and encode.d.ts are what exist), so a TypeScript consumer requiring cbor-x under nodenext resolves no declarations and gets TS7016.

node -e "console.log(JSON.stringify(require('cbor-x/package.json').exports['.'].types))"; ls node_modules/cbor-x/*.d.cts -> {require: ./index.d.cts, import: ./index.d.ts}; no such file
Verified by running
Read from the installed package: the exports entry and the listing of declaration files. Control: types.import names index.d.ts, which exists.
Checked against
cbor-x 1.6.6, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. kriszyp/cbor-x has 19 results for the terms, none naming the missing .d.cts. Recorded; a draft is one line.

@octokit/auth-unauthenticated

1 finding

undelivered-entryNo prior reportNot a vulnerability

UNDELIVERED_ENTRY

@octokit/auth-unauthenticated 7.0.5's exports map names, under the browser condition, `types: ./dist-types/web.d.ts`, a file the package does not ship (dist-types holds index.d.ts and its siblings), so a TypeScript consumer resolving the browser condition gets TS7016 while node and default resolve index.d.ts.

node -e "console.log(JSON.stringify(require('@octokit/auth-unauthenticated/package.json').exports['.'].browser))"; ls node_modules/@octokit/auth-unauthenticated/dist-types/ -> types names web.d.ts; no web.d.ts in the listing
Verified by running
Read from the installed package: the exports entry and the dist-types listing. Control: the node condition's index.d.ts exists.
Checked against
@octokit/auth-unauthenticated 7.0.5, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. Modest: browser-condition TypeScript consumers only. Recorded, not drafted.

ethers

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

ethers 6.17.0 declares engines.node '>=14.0.0' and ships private class methods (`#getCoder(param)`, lib.esm/abi/abi-coder.js:111 and 35 further sites), which no Node before 14.6 parses; on 14.0 through 14.5 loading the module is a SyntaxError.

node -e "console.log(require('ethers/package.json').engines.node)"; sed -n 111p node_modules/ethers/lib.esm/abi/abi-coder.js -> >=14.0.0 and `#getCoder(param) {`
Verified by running
Read, not run: the engines field and the source line from the installed package; the private-method floor (14.6) is the grammar's history, the basis every engines row rests on.
Checked against
ethers 6.17.0, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. An engines field admitting a Node line the shipped syntax cannot run on, the same shape as the engines rows before it; 14.0 to 14.5 is a narrow band and every 14.x is end of life. Recorded, not drafted.

@google-cloud/functions-framework

1 finding

engines-below-syntaxNo prior reportNot a vulnerability

ENGINES_BELOW_DELIVERED_SYNTAX

@google-cloud/functions-framework 5.0.5 declares engines.node '>=10.0.0' and ships optional chaining (build/src/testing.js:27 and four further sites), which no Node before 14 parses; on any 10.x or 12.x the manifest admits, loading the module is a SyntaxError.

node -e "console.log(require('@google-cloud/functions-framework/package.json').engines.node)"; sed -n 27p node_modules/@google-cloud/functions-framework/build/src/testing.js -> >=10.0.0 and `?.userFunction`
Verified by running
Read, not run: the engines field and the source line from the installed package.
Checked against
@google-cloud/functions-framework 5.0.5, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the eleventh sweep tree, 1,204 packages of observability, cloud functions, git and container clients, security and payment SDKs, mail, encoding and serialization: 122 hits over 15 packages, 13 of them new to this file. Verified by hand with a control. Google's own runtimes run 18 and up, so the field is stale rather than harmful. Recorded, not drafted.

sharp

1 finding

declared-value-missingNo prior reportNot a vulnerability

DECLARED_VALUE_WITHOUT_RUNTIME

sharp 0.35.4's dist/index.d.mts:2044 declares `export const sharp: SharpConstructor` beside `export default sharp`, and dist/index.mjs exports only default, so `import { sharp } from 'sharp'` typechecks and fails at link time with SyntaxError: The requested module 'sharp' does not provide an export named 'sharp'.

node --input-type=module -e "import { sharp } from 'sharp'" -> SyntaxError: The requested module 'sharp' does not provide an export named 'sharp'
Verified by running
Node v22.14.0 against the installed 0.35.4: the ESM named import threw the SyntaxError above; `import sharp from 'sharp'` (the default) is the control and loads. d.mts:2044-2045 and index.mjs:25 (`export default Sharp;`, the only export) read.
Checked against
sharp 0.35.4, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the running probes over the tenth sweep tree, and first written down in this repository as a probe misread (a member `sharp: string` inside an interface); re-read the same evening: the declaration is the top-level `export const sharp` at line 2044, the probe was right, and the write-up was wrong. The named import is the form the TypeScript declaration invites and the ESM entry does not carry. Draft: public issue, one line.

@fastify/swagger

1 finding

stateful-matcherNo prior reportNot a vulnerability

STATEFUL_MATCHER_DIVERGENCE

@fastify/swagger 9.8.1's hasParams(url) (lib/util/match-params.js:7) tests a route path with the global regex `paramPattern = /\{[^{}]+\}/gu` via .test() and never resets lastIndex, so for a route with path parameters and no params schema the answer alternates true/false/true/false across calls; the generator at lib/spec/openapi/utils.js:499 and lib/spec/swagger/utils.js:321 calls it once per route, so half such routes silently get no path-parameter documentation.

const {hasParams}=require('@fastify/swagger/lib/util/match-params.js'); [1,2,3,4].map(()=>hasParams('/users/{id}')) -> [ true, false, true, false ]
Verified by running
Node v22.14.0 against the installed 9.8.1: hasParams('/users/{id}') returned true, false, true, false on four consecutive calls; the control hasParams('/users/list') returned false both times. Read at lib/util/match-params.js:3 (the /gu regex) and :7 (the .test), and the two call sites.
Checked against
@fastify/swagger 9.8.1, the current release. Checked 2026-09-11.
Note
Found 2026-09-11 by the reading finders (scripts/scan-installed.ts) over the twelfth sweep tree, 1,501 packages of Koa/Nest/Fastify server stacks, OpenAPI tooling and test runners; 50 hits over 14 packages, verified by hand with a control. The same shape as content-disposition and filenamify. Reached only when a route declares path params in its URL but no `params` JSON schema; the generator then skips generating them for every other such route. Dropping the `g` flag on paramPattern is the whole fix (matchParams below it uses url.match, which is unaffected). fastify/fastify-swagger#761 added the generate-when-missing path this sits on; no report names the flag. Draft: public issue, one line.

05Where these came from

Nobody filed a report for any of these.

Each finder’s oracle is the code or the language, not us.