Files
pnpm/cspell.json
C. T. Lin d3f68e2aa4 fix(audit): compute reachable vulnerabilities with Tarjan SCC (#12467)
`pnpm audit` enumerates the install paths to every vulnerable package. The
reachability-based pruning added in 11.5.1 (pnpm/pnpm#12087) lets the walker
skip subtrees that reach no unsaturated finding by precomputing, per node, the
set of vulnerabilities reachable from it.

That getter only memoised acyclic subtrees: a node whose subtree contained a
cycle was `complete === false`, and so was every ancestor up to the importer
roots. None of them were cached, so their reachable set was recomputed on every
query. Real dependency graphs commonly contain cycles, and a single cycle high
in the graph makes a large fraction of nodes non-memoisable, yielding an O(N^2)
walk. This matched the report in pnpm/pnpm#12212 exactly (CPU-bound, identical
audit output across versions).

Reachability is now computed with Tarjan's strongly-connected-components
algorithm. Every node is scanned once; all members of an SCC reach the same set
of vulnerabilities and share one set, finalised in reverse-topological order.
Cyclic graphs are handled in O(N + E).

The reachable set is used only to prune, so it must never under-approximate
(that would hide a real finding). Tarjan yields the exact set for every node,
so no finding can be dropped, and the path-recording logic is unchanged. The
getter returns ReadonlySet<string> so the shared sets cannot be mutated by
callers, and a missing memo entry (an impossible-by-construction state) throws
rather than silently returning an empty set.

A regression test asserts the read-count growth ratio between two cycle sizes
(L=200 and L=400) is sub-quadratic: the fix scales ~2x (linear), the previous
code ~4x (quadratic). Asserting the ratio cancels the per-node constant, so the
test is not brittle to constant-factor changes.

Closes pnpm/pnpm#12212.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-20 13:34:06 +00:00

435 lines
6.7 KiB
JSON

{
"ignorePaths": [
"**/nodeReleaseKeys.ts",
"**/nodeReleaseKeys.d.ts",
"**/node_release_keys.rs",
"bench-work-env/**"
],
"words": [
"adduser",
"adipiscing",
"agentkeepalive",
"agentkeepalive's",
"amet",
"andreineculau",
"appdata",
"applyq",
"archy",
"argumentless",
"armv",
"autocheckpoint",
"autocompleting",
"autofix",
"autofixed",
"autoinstalled",
"autozoom",
"babek",
"Backblaze",
"badheaders",
"baires",
"behaviour",
"blabla",
"Bluesky",
"brasileiro",
"bryntum",
"buildx",
"cafile",
"cafs",
"camelcase",
"canonicalizer",
"cantopen",
"canva",
"cerbos",
"certfile",
"chmods",
"clonedeep",
"cmds",
"Codeberg",
"codeload",
"codenames",
"codesign",
"colorterm",
"comver",
"copyfiles",
"corejs",
"corepack",
"corge",
"cowsay",
"Creds",
"cryptiles",
"cves",
"cwsay",
"cyclonedx",
"deburr",
"dedup",
"denoland",
"denolib",
"deptype",
"desugared",
"desugars",
"devextreme",
"devowl",
"dgimuvys",
"didyoumean",
"dirtyforms",
"diskusage",
"dislink",
"dpkg",
"drivelist",
"duplexify",
"eacces",
"eagain",
"ebadplatform",
"ebusy",
"eexist",
"ehrkoext",
"eintegrity",
"eisdir",
"elifecycle",
"elit",
"embedder",
"emfile",
"enametoolong",
"endregion",
"eneedauth",
"enoent",
"enotempty",
"enten",
"eotp",
"eperm",
"epipe",
"erofs",
"errcode",
"esac",
"etamponi",
"exdev",
"execa",
"exploitability",
"fakehash",
"fellback",
"fetchings",
"filenamify",
"filesystem",
"filesystems",
"fnumber",
"foobarqar",
"foofoo",
"footgun",
"forgejo",
"fsevents",
"gabor",
"garply",
"gcttmf",
"getattr",
"ghes",
"ghsa",
"ghsas",
"gitdir",
"gitea",
"globalconfig",
"globstar",
"gnueabihf",
"gpgsign",
"grault",
"gruntfile",
"gwhitney",
"haptics",
"hardlink",
"hardlinked",
"hardlinking",
"hardlinks",
"hashbang",
"highmaps",
"hikljmi",
"hoistable",
"homepath",
"hosters",
"htpasswd",
"hyperdrive",
"idempotency",
"imagetools",
"imurmurhash",
"invalidformat",
"ionicons",
"isexe",
"istvan",
"italiano",
"jega",
"jhcg",
"jnbpamcxayl",
"junyi",
"kebabcase",
"kevva",
"keyfile",
"keyid",
"keytype",
"killcb",
"kochan",
"koorchik",
"ldid",
"ldni",
"leniolabs",
"libc",
"libnpmpublish",
"libnpx",
"libsql",
"libzip",
"licence",
"licences",
"lifecycles",
"linuxstatic",
"localappdata",
"lockfiles",
"loglevel",
"logstream",
"longlink",
"longpaths",
"loong",
"lowlink",
"luca",
"martensson",
"maxtimeout",
"mdast",
"metafile",
"millis",
"minioadmin",
"mintimeout",
"mmap",
"monorepolint",
"montudor",
"moonrepo",
"mountpoint",
"msgpack",
"msgpackr",
"msvc",
"msys",
"mtimes",
"musleabihf",
"mycomp",
"mycompany",
"myorg",
"mypackage",
"mytoken",
"ndjson",
"nerfed",
"newversion",
"nistp",
"NOASSERTION",
"nodetouch",
"noent",
"nonexec",
"noninjected",
"nonvulnerable",
"noopener",
"nopadding",
"noproxy",
"noreferrer",
"nosystem",
"nothrow",
"npmcli",
"npmignore",
"npmjs",
"npmx",
"ntfs",
"nushell",
"ofjergrg",
"onclickoutside",
"oomol",
"openharmony",
"openpgp",
"ossl",
"outfile",
"overrider",
"packlist",
"packr",
"packument",
"packuments",
"pacquet",
"paralleljs",
"parallelly",
"parseable",
"partialmatch",
"pathext",
"pegjs",
"pidtree",
"pify",
"pkgname",
"pkgs",
"plotly",
"plugh",
"pnpmfile",
"pnpmfiles",
"pnpmjs",
"pnpmrc",
"pnpmtest",
"pnpr",
"polyfilling",
"português",
"posix",
"postbuild",
"postfoo",
"postpack",
"postprepare",
"postpublish",
"postrestart",
"postshrinkwrap",
"poststart",
"poststop",
"posttest",
"postuninstall",
"postversion",
"preact",
"prebuild",
"prefoo",
"prefs",
"preinstall",
"premajor",
"preminor",
"prepatch",
"prepublish",
"prereleases",
"prerestart",
"preshrinkwrap",
"prestart",
"prestop",
"preuninstall",
"preversion",
"prioritizer",
"promisified",
"proxied",
"pwsh",
"qrcode",
"quux",
"rcompare",
"redownload",
"refclone",
"refetched",
"refetches",
"reflattened",
"reflink",
"reflinked",
"reflinks",
"rehoist",
"reimagining",
"reka",
"Rekor",
"relinks",
"renderable",
"replit",
"reqheaders",
"rescopable",
"rescope",
"rescoped",
"rescopes",
"rescoping",
"rimrafed",
"riscv",
"rmgr",
"rpmdevtools",
"rpmlint",
"rstacruz",
"rushstack",
"rustup",
"safecrlf",
"scopeless",
"sdiff",
"searchexclude",
"searchlimit",
"searchopts",
"searchstaleness",
"sels",
"semistrict",
"serp",
"serverjs",
"shasums",
"sheetjs",
"shlex",
"sigstore",
"sindresorhus",
"sirv",
"SLSA",
"soporan",
"sopts",
"spdxdocs",
"SPDXID",
"sqld",
"srcset",
"ssri",
"stackblitz",
"stacktracey",
"stdtype",
"streamsearch",
"stringifying",
"strongconnect",
"subcmd",
"subdep",
"subdependencies",
"subdependency",
"subdeps",
"subdir",
"subdirs",
"subkey",
"subkeys",
"subpkg",
"subresource",
"supercede",
"Swatinem",
"syml",
"syncer",
"syscall",
"syscalls",
"szia",
"tabtab",
"taffydb",
"taiki",
"tarballtemplate",
"Tarjan",
"teambit",
"tempy",
"testcase",
"thesys",
"tlog",
"TLSV",
"toctou",
"todomvc",
"toplevel",
"TOTP",
"tsgo",
"tsparticles",
"turso",
"typecheck",
"unallowed",
"undeprecate",
"underperformance",
"undollar",
"unextractable",
"uninstallation",
"unnest",
"unparseable",
"unreviewed",
"unskip",
"unstar",
"usecase",
"userconfig",
"userprofile",
"ustar",
"uuidv",
"valign",
"vuln",
"webauth",
"webcontainer",
"winst",
"workleap",
"worktree",
"worktrees",
"wrappy",
"xattr",
"xattrs",
"xmarw",
"yazl",
"zkochan",
"zoli",
"zoltan"
]
}