This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
NEVER run npm/yarn/electron/grunt/node on the host. Local node_modules/ is for the Linux Docker build only. Allowed host commands: git, docker, codesign.
| Task | Command |
|---|---|
| Build (Intel/amd64) | docker build --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) -t boostnote-legacy . |
| Build (Apple Silicon/arm64) | docker build --platform linux/arm64 --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) --build-arg BUILDARCH=arm64 -t boostnote-legacy-arm64 . |
| Test all | docker run --rm boostnote-legacy npm test |
| AVA tests only | docker run --rm boostnote-legacy npm run ava |
| Jest tests only | docker run --rm boostnote-legacy npm run jest |
| Lint | docker run --rm boostnote-legacy npm run lint |
| Lint fix | docker run --rm boostnote-legacy npm run fix |
| Compile (webpack) | docker run --rm boostnote-legacy npm run compile |
| Dev (HMR) | docker run --rm boostnote-legacy npm run dev |
| Export .app (Intel) | docker cp $(docker create --rm boostnote-legacy):/app/dist/Boostnote-darwin-x64 ./dist/ |
| Export .app (arm64) | docker cp $(docker create --rm boostnote-legacy-arm64):/app/dist/Boostnote-darwin-arm64 ./dist/ |
Omitting GIT_COMMIT build-arg → About dialog shows "unknown".
AVA picks tests/**/*-test.js; run one file:
docker run --rm boostnote-legacy npx ava tests/dataApi/createNote-test.jsJest picks everything else under tests/:
docker run --rm boostnote-legacy npx jest tests/components/MyComponent.test.jsindex.js → Squirrel lifecycle → lib/main-app.js
├── lib/main-window.js BrowserWindow creation
├── lib/main-menu.js native app menu
├── lib/ipcServer.js node-ipc server (main process)
└── lib/touchbar-menu.js
browser/main/index.js (webpack entry → compiled/main.js)
├── store.js Redux store + Immutable.js via Mutable.js wrappers
├── Main.js root component → SideNav | NoteList | Detail
├── lib/dataApi/ CRUD operations on .cson note files
├── lib/ConfigManager.js electron-config wrapper
├── lib/shortcutManager.js keyboard shortcut registry
└── lib/ThemeManager.js
Notes are stored as .cson files in user-defined storage directories on disk. browser/main/lib/dataApi/ contains all read/write operations against those files.
Webpack aliases: lib → ./lib, browser → ./browser. These are used throughout import paths.
- Webpack 1 + Babel 6 — loader chains use
!syntax (e.g.style!css?modules!stylus), not the modernuse:[]form. - Externals: electron, react, redux, codemirror, lodash, moment, prettier are loaded via
<script>tags in the HTML skeleton (webpack-skeleton.js), not bundled. Do not attempt to import them as if they were bundled. processshim: Webpack 1 injectsprocess.versions = {}. Any dep readingprocess.versions.nodeat module load (e.g.fs-extra@7+) crashes. Pin such deps or external them.- CSS Modules via
react-css-modules+ Stylus. Class name pattern:[name]__[local]___[path]. - HMR dev: Manual refresh needed when editing constructors or adding new CSS classes (registered at construction time).
- Dialog API: Electron 9+ removed sync/callback forms. Use
showMessageBoxSyncand Promise-basedshowOpenDialog/showSaveDialog. - webPreferences:
enableRemoteModule: true,nodeIntegration: true,contextIsolation: falseare required. - Node 22 (Debian bookworm) inside Docker for the build toolchain; Electron 11.5.0 (Chrome 87, Node 12) at runtime. Anything that runs in the renderer must stay compatible with Node 12 / Chrome 87, even though Docker has a newer Node.
resolutionsblock inpackage.jsonis the canonical place to force-upgrade vulnerable transitive deps. Adding a new entry there + runningyarn installregeneratesyarn.lockwith a single hoisted version. Use it whenever the parent package cannot be bumped (most of the Webpack 1 / Babel 6 stack).- Current entries (CVE-driven). Group by reason so the next maintainer doesn't have to rediscover the why:
- Renderer / runtime-touching:
lodash ^4.17.21,moment ^2.30.1,highlight.js ^10.4.1,codemirror ^5.58.2,dompurify ^2.5.4,set-getter ^0.1.1(resolved at renderer runtime viamarkdown-toc→lazy-cache; the package is inwebpack-skeleton.js#externalsbut Electron'snodeIntegration: trueloads it through Node'srequire),dot-prop ^4.2.1(renderer runtime viaelectron-config→conf→dot-prop; used bybrowser/main/lib/ConfigManager.jsto persist user preferences),decode-uri-component ^0.2.1(renderer runtime viaquery-string@^6.13.8→decode-uri-component; query-string is imported bybrowser/lib/newNote.js,browser/main/Detail/*,browser/main/modals/NewNoteModal.js,browser/main/NoteList/index.js). All loaded via<script>externals or bundled intocompiled/main.js. Fordot-prop, the CVE-2020-8116 sink is unreachable because every preferencepathargument is a hard-coded string ('keyMap','theme','editor.fontSize', …) — the bump is defence-in-depth. Fordecode-uri-component, CVE-2022-38900 is a DoS via repeated malformed%-encoded sequences (pre-0.2.1 looped instead of throwing) — Boostnote's only inputs toquery-stringare note-creation URL parameters constructed by the app itself, but the upgrade also collapses the build-time consumer (source-map-resolve@^0.5.x) to the patched 0.2.2.remarkable ^1.7.2(resolves to 1.7.4) is consumed only bymarkdown-toc@^1.2.0, which is renderer-bundled —markdown-toc/index.js:29instantiatesnew Remarkable()(no-args, default schema) to parse user-authored note markdown into a token stream for TOC extraction. Reached frombrowser/lib/markdown-toc-generator.jsviaCodeEditor.js,SnippetNoteDetail.js, andMarkdownNoteDetail.js. Patches GHSA-2gjj-79c4-c645 (ReDoS in remarkable's inline parser) and GHSA-jhcr-3vfp-qx79 (XSS via HTML escaping bypass); both affect <1.7.2. ReDoS reachable in this project: an attacker-crafted note triggers the TOC generator and hangs the renderer process. XSS less reachable since markdown-toc consumes the token stream and not the rendered HTML directly, but the bump covers it as defence-in-depth. API surface stable 1.7.1 → 1.7.4 —new Remarkable()+.render()+ token-stream access are all unchanged, so markdown-toc's call site works without modification.codemirror ^5.58.2collapses two coexisting copies — the direct dep^5.65.0(already at 5.65.21) and the nested[email protected]installed undernode_modules/react-codemirror/node_modules/(^5.18.2) andnode_modules/codemirror-mode-elixir/node_modules/(^5.20.2) — to a single hoisted[email protected]install (^5.58.2resolves to the same 5.65.21 yarn already had at top level, and the nested 5.38.0 directories are removed fromnode_moduleson disk). Patches GHSA-3p3p-7p4f-r28x (ReDoS in codemirror HTML/JS/CSS-mode regular expressions, affects<5.58.2). CVE sink was not exploitable in the shipped binary even before this bump: webpack 1'swebpack-skeleton.js#externalsmapscodemirror: 'var CodeMirror', so every rendererrequire('codemirror')is bundle-replaced with a global lookup, andlib/main.production.html:117establishes that global by loading<script src="../node_modules/codemirror/lib/codemirror.js">— the hoisted top-level path, which was already on 5.65.21. The nested 5.38.0 copies underreact-codemirrorandcodemirror-mode-elixirwere dead weight on disk and never executed (codemirror-mode-elixirships a self-containeddist/elixir.jsUMD that only callsCodeMirror.defineMode(...)on the global;react-codemirror'srequire('codemirror')is intercepted by the webpack externals map). Bump removes the lock duplicates + the on-disk dead-weight copies and clears the Dependabot /npm auditflag. Verified:npm run compileproduces an unchanged 9.21 MB / 1217 module bundle (renderer already loaded 5.65.21 — no behavior delta), and disk spot-check confirms a singlenode_modules/codemirror @ 5.65.21with no nested directories.dompurify ^2.5.4bumps the single[email protected]exact pin (consumed bymermaid@~9.1.7, renderer-bundled) up to2.5.9(latest within^2.5.4). Patches GHSA-mmhx-hmjr-r674 (tampering by prototype pollution, affects<2.5.4on the 2.x line). CVE sink is reachable in this project: mermaid passes user-authored diagram label content throughDOMPurify.sanitize(html, config)at render time — a crafted mermaid block in a note can reach the proto-pollution path. Bump is minor-within-major (2.4.0 → 2.5.9); the dompurify 2.x API (sanitize(),addHook(),removeHook(),isValidAttribute()) is stable across the patch line, so mermaid 9.1.7's call sites (DOMPurify.sanitize(value, config)for label sanitization andDOMPurify.addHook('uponSanitizeElement', cb)for the custom mermaid hook) work unchanged. [email protected] ships CJS (main: dist/purify.cjs.js, optionalmodule: dist/purify.es.jsfor bundlers that honour it, notype: module) — clean ESM cliff. Verified:npm run compileproduces an unchanged 9.21 MB / 1217 module bundle.js-yaml ^3.14.2collapses eight coexisting ranges (^3.7.0,^3.8.1,^3.9.1,^3.10.0,^3.14.1,~3.14.0,~3.7.0,^3.14.2) to a single 3.14.2 entry — the direct dep is already on 3.14.2 (renderer-bundled, called frombrowser/components/MarkdownPreview.jsandbrowser/main/lib/dataApi/formatMarkdown.jsviayaml.load()/yaml.dump()for frontmatter parsing), butmarkdown-toc@^1.2.0(also renderer-bundled — imported bybrowser/lib/markdown-toc-generator.jsand reached fromCodeEditor.js,SnippetNoteDetail.js,MarkdownNoteDetail.js) lazy-loadsgray-matter@^2.1.0which pulled injs-yaml@^3.8.1resolving to the vulnerable 3.13.1;[email protected](build-time CSS minify viapostcss-svgo← cssnano) pulled injs-yaml@~3.7.0resolving to 3.7.0. Patches the code-injection class fixed in 3.14.2. API surface is identical 3.7 → 3.14 (load/safeLoad/dump/safeDumpsignatures unchanged; 3.13 addedloadas alias forsafeLoad, 3.14 deprecatedsafeLoadbut kept it working), so the markdown-toc and postcss-svgo call sites are unaffected. - Build-time only (loader / packager chain):
json5 ^1.0.2,word-wrap ^1.2.4,y18n ^3.2.2,minimist ^1.2.8,qs ^6.5.3,json-schema ^0.4.0,tmp ^0.2.6,brace-expansion ^1.1.15,node-fetch ^2.6.7,tough-cookie ^4.1.3,underscore ^1.12.1,async ^2.6.4,sha.js ^2.4.12,ua-parser-js ^0.7.22,ws ^8.17.1,got ^11.8.5,glob-parent ^5.1.2,set-value ^2.0.1,ansi-regex ^3.0.1,ajv ^6.12.3,acorn ^5.7.4,decompress-zip ^0.3.2,form-data ^2.5.4,kind-of ^6.0.3,lodash.merge ^4.6.2,handlebars ^4.7.7. Thewsresolution collapsesws@^4.0.0+ws@^5.2.0(both consumed by[email protected], which is a devDep used only by AVA/Jest browser-env helpers intests/helpers/setup-browser-env.js) to a single[email protected]+— patches CVE-2024-37890 (DoS via excessive request headers, <7.5.10 / <8.17.1) and CVE-2021-32640 (ReDoS in Sec-WebSocket-Protocol header, <5.2.4). jsdom'sWebSocket-impl.jsuses the stablenew WebSocket(url)client API which is unchanged ws 4 → 8. Thegotresolution collapsesgot@^6.7.1(ava → update-notifier → package-json → got, an auto-update check that's a no-op under CI) andgot@^9.6.0(@electron/get→ got, used byelectron-packagerat build time to fetch the Electron binary) to a single[email protected]+— patches CVE-2022-33987 (UNIX-socket redirect bypass, <11.8.5 / <12.1.0). Verified end-to-end by running the fulldocker build .:@electron/getsuccessfully downloaded Electron 11.5.0 binaries through the bumpedgot, andelectron-packagerproduced bothBoostnote-darwin-x64andBoostnote-linux-x64artifacts. Theglob-parentresolution forces the legacyglob-parent@^2.0.0consumers (chokidar@^1.xvia[email protected]test runner andwatchpack@^0.2.xwebpack-1 file watcher;glob-base@^0.3.0viaparse-glob) up to5.1.2+, patching CVE-2020-28469 (ReDoS in theis-glob-style regex). API is stable across glob-parent majors — singleglobParent(pattern)function with identical return surface, so the chokidar/watchpack call sites are unaffected. Theset-valueresolution collapses two coexisting majors (^0.4.3consumed byunion-value@^1.0.0←cache-base@^1.0.1, and^2.0.0consumed bycache-base@^1.0.1directly) — both upstream ofsnapdragon@^0.8.1←base@^0.11.1, which is reached only throughmicromatch@^3.1.4/^3.1.8(anymatch@^2,sane@^2,test-exclude@^4in thejest@22/[email protected]/[email protected]dev path). Patches CVE-2019-10747 (proto pollution via attacker-controlledpathargument inset(obj, path, value)). API surface stable 0.4 → 2.x (same 3-arg signature; 2.x added an optional options 4th arg), so union-value's internalsetValue(target, prop, value)calls work unchanged. The CVE sink is not actually reachable in this project — every consumer passes hardcoded internal strings (snapdragon parser state, cache-base keys, union-value template processing), never user input — but the bump clears the audit complaint and collapses the dual-major entry to a single 2.0.1. Theansi-regexresolution collapses three coexisting ranges (^2.0.0consumed bychalk@1→has-ansi+strip-ansi@^3, plusstring-width@^1,webpack-dev-server@1,wrap-ansi@^2;^3.0.0consumed bystrip-ansi@^4→ava,eslint,jest-cli,inquirer,supertap,cliui,string-width@^2, pluspretty-formatin jest internals) to a single 3.0.1 entry. Patches CVE-2021-3807 (ReDoS in the ANSI-escape regex, affecting <3.0.1 / <4.1.1 / <5.0.1 / <6.0.1; the lock only carries 2.x and 3.x lines so a single^3.0.1resolution covers everything). API is the single function() => RegExp, stable 2 → 5 — chalk@1'shas-ansi(str) = ansi-regex().test(str)and strip-ansi@^3'sstr.replace(ansi-regex(), '')work unchanged. The 2.x range has no patched release (the fix line starts at 3.0.1), so forcing the 2.x consumers up to 3.0.1 is the only way to patch them. Sink not reachable in this project: all consumers process developer-authored CLI output, test stack traces, or webpack-dev-server log lines — never user-controlled ANSI strings. Theunderscoreconsumers in the tree (nomnomviajsonlint-mod/lib/cli.js,[email protected]viaremarkable's CLI binary,underscore-plusviafs-plus) all live behind CLI entries that Boostnote's renderer never loads —jsonlint-modis used viaweb/jsonlint.js,markdown-tocconsumes remarkable through its library entry, andjs-sequence-diagramsinlines its own underscore copy inside the pre-built UMD bundle. Forcing the bump therefore clears the audit complaint without exercising any of the methods (_.any,_.contains) that were removed in underscore 1.9. Theasyncresolution collapses 5 coexisting majors (0.2 / 0.9 / 1.5 / 2.6 / 3.2 in the lock) to a single 2.6.4 — the CVE-2021-43138 sink (async.mapValues) is not exercised by any consumer (the vulnerable copy was[email protected], which only callsasync.mapandasync.queue); the cross-major coverage holds because every method the actual consumers call is API-stable from 0.9 → 3.x. Thesha.jsresolution covers thecrypto-browserifypolyfill graph, which webpack 1'sNodeTargetPluginexternalizes at runtime — sha.js never actually loads in the shipped renderer. Theua-parser-jsresolution coversfbjs, a React-16-era utility library that is never bundled into the renderer. Theajvresolution collapses three coexisting 5.x ranges (^5.1.0viarequest@^2.79.0/^2.83.0/^2.87.0→har-validator@~5.0.3,^5.2.3viaeslint@^4.18.2→[email protected],^5.3.0viaeslint@^4.18.2directly) to a single[email protected]entry. Patches CVE-2020-15366 (proto pollution inajv.validate()via deeply-nested schema constructs) and the$dataReDoS advisory; both affect <6.12.3. ajv 5 → 6 is a major bump and[email protected]'slib/config/config-validator.jswas written against the ajv 5 API —[email protected](consumed via[email protected]) declarespeerDependency: ajv@^5.0.0and yarn now prints a peer-warning, but the actual keyword-registration call sites (require('ajv-keywords')(ajv, ['typeof'])) work unchanged because the keyword DSL is stable 5 → 6. Verified by runningnpm run lintend-to-end inside the bn-deps image: eslint loaded its rule schemas through ajv 6, validated.eslintrc+ every rule's options schema, and emitted only the seven pre-existingprettier/prettierbaseline errors (host prettier 1.18 vs Docker prettier 1.19 disagreement documented elsewhere in this file) — zero ajv-derived crashes, zero schema-validation errors. Scope is build-time / dev-only: ajv reaches the tree only througheslint(lint script),har-validator→request(used at build time by[email protected]in test helpers andmksnapshot@^0.3.0in the electron-packager chain, neither of which callsrequest({har: ...})), andtable(eslint CLI formatter). Sink not reachable in this project: every schema fed to ajv is developer-authored (eslint rule option schemas, internal HAR + table specs) — never user-controlled input. Compile bundle unchanged at 9.21 MB / 1217 modules. Theacornresolution collapses eight coexisting ranges (^3.0.0+^3.0.4via[email protected]+acorn-jsx@^3.0.0←espree@^3.5.4←eslint@^4.18.2;^4.0.4viaacorn-globals@^3.1.0←jsdom@^9.4.2;^5.0.0viaacorn-globals@^4.1.0←[email protected];^5.3.0/^5.5.0/^5.5.3viajsdom@^11.5.1/[email protected]/jsdom@^11.9.0andespree@^3.5.4directly) to a single hoisted[email protected]install. Patches GHSA-6chw-6frg-f759 (ReDoS infastRegExpClone, affects>=5.0.0 <5.7.4/>=6.0.0 <6.4.1/>=7.0.0 <7.1.1) — only the 5.x line in this tree was vulnerable, but a global resolution to 5.7.4 was the simplest patch that also collapsed the 3.x and 4.x copies. Scope is build-time / dev-only:[email protected]parses developer-authored source files at compile time,eslintlints the same source,[email protected]is consumed byturndown@4(renderer dep, but turndown uses nativeDOMParserin Electron and only falls back to jsdom in Node, so the renderer-runtime path doesn't actually load acorn) plusjest-environment-jsdom@^22.4.1(devDep, test only),jsdom@^9.4.2is consumed byavabrowser-env helpers intests/helpers/setup-browser-env.js. Sink not reachable: the ReDoS triggers only on attacker-supplied JavaScript source fed toacorn.parse(), which never happens in Boostnote's data flow (all parsed JS is build-time developer source). Surprise result: webpack 1.15.0 works correctly with acorn 5.7.4, contradicting the long-held belief that webpack 1 is hard-locked to acorn 3 —lib/Parser.jsonly calls the stableacorn.parse(src, opts)surface plus a few node-walker helpers that are unchanged 3 → 5. This does not resolve the uuid 11+ acorn cliff documented under "Pinned direct deps", because that cliff is about ES2020 syntax (??/?.) which acorn 5 still cannot tokenize — full ES2020 support arrived in acorn 7+. Verified: fullnpm run compile(9.21 MB / 1217 modules, no regression) andnpm run lint(eslint + espree + acorn-jsx + acorn 5.7.4 produce only the 7 pre-existing prettier/prettier baseline errors). All 8 previously-coexisting acorn ranges collapse to a single hoistednode_modules/acornentry on disk — no nested copies. Thedecompress-zipresolution bumps the single exact-pinned[email protected](consumed bymksnapshot@^0.3.0←asar@^0.11.0←electron-winstaller@^2.2.0←[email protected]) up to0.3.3(latest within^0.3.2). Patches GHSA-3wq5-r5w8-5xhf (arbitrary file overwrite via path traversal during ZIP extraction, affects<0.3.2). Consumer chain is the Squirrel.Windows installer toolchain only —electron-packager@^15.4.0itself uses the modern[email protected], which has no mksnapshot/decompress-zip dependency. The vulnerable chain is reached only bygrunt build win(thecreate-windows-installertask ingruntfile.js), which the GitHub Actions release workflow does not run — CI ships.zip/.tar.gzonly, same reason the Linuxelectron-installer-*task blocks were already removed. CVE sink is path traversal during extraction of an attacker-controlled ZIP, but mksnapshot's ZIP is the precompiledmksnapshot.zipfetched over HTTPS fromgithub.com/electron/electron/releases(not attacker-controlled). Bump is patch-within-minor (0.3.0 → 0.3.3) and the API surface (new DecompressZip(file).extract({path})) is unchanged between releases. Compile bundle unchanged at 9.21 MB / 1217 modules. Theform-dataresolution bumps the single[email protected]install (consumed byrequest@^2.79.0/^2.83.0/^2.87.0←[email protected]+[email protected]+mksnapshot@^0.3.0) up to2.5.5(latest within^2.5.4). Patches GHSA-fjxv-7rqg-78g4 — form-data usedMath.random()to choose multipart boundary strings, which is predictable and could enable an attacker controlling adjacent multipart fields to inject content (affects<2.5.4/<3.0.4/<4.0.4). Scope is build-time / test / dead-path only: turndown@4 (renderer dep) uses nativeDOMParserin Electron and only falls back to jsdom in Node, so request → form-data is not invoked at renderer runtime;jest-environment-jsdomand the ava browser-env helpers are test-only; mksnapshot's request usage is the Windows-installer chaingrunt build win, which CI does not run. Sink not reachable: none of these consumers issue multipart POSTs in Boostnote's data flow — mksnapshot only GETs precompiled binaries fromgithub.com/electron/electron/releases, jsdom is the offline DOM parser, ava/jest exercise local-only resources. Bump is minor-within-major (2.3.2 → 2.5.5); the public API (new FormData()+.append(field, value)+.pipe(req)+.getHeaders()) is stable across the 2.x line, so [email protected]'smultipartbuilder works unchanged. Compile bundle unchanged at 9.21 MB / 1217 modules. Thekind-ofresolution collapses four coexisting majors (^3.0.2/^3.0.3/^3.2.0viaalign-text+is-accessor-descriptor@^0.1.6+is-data-descriptor@^0.1.4+is-number@^2/^3+micromatch@^2+object-copy+snapdragon-util+to-object-path;^4.0.0viahas-values;^5.0.0viais-descriptor@^0.1.0;^6.0.0/^6.0.2viais-accessor-descriptor@^1.0.0+is-data-descriptor@^1.0.0+is-descriptor@^1.0.x+make-iterator+micromatch@^3.1.x+nanomatch+randomatic@^3+use@^3.1.0) to a single hoisted[email protected]. Patches CVE-2019-20149 / GHSA-6c8f-qphg-qjgp — validation bypass via attacker-controlledconstructor.nameproperty on an object passed tokindOf(value); only the 6.x line (<6.0.3) is in the advisory scope, but the global resolution also lifts the 3.x / 4.x / 5.x copies and unifies the on-disk tree. Scope is build-time / dev-tool / test only — every consumer chain roots atjest@22,[email protected],[email protected], oreslint@4and runs against developer-authored glob patterns (test paths, watch globs, jest config), never against attacker input. Sink not reachable: the CVE requires an attacker-supplied object with a craftedconstructor.name, which never reaches micromatch / snapdragon / descriptor-helpers in this project's data flow. API is stable across 3 → 6 —kindOf(value)returns a type-string and consumers only===against the common cases ('object','string','array','number'); 4.x added Symbol detection, 5.x droppedis-buffer, 6.x added theconstructor.namevalidation that the CVE patches, but the call sites in this tree all check the same set of legacy type strings. Verified: fullnpm run compile(9.21 MB / 1217 modules, no regression) andnpm run lint(eslint + table + jest-cli's kind-of consumers run cleanly, producing only the 7 pre-existingprettier/prettierbaseline errors). Thelodash.mergeresolution bumps the single[email protected]install (consumed byconcordance@^3.0.0←[email protected]test runner, andhullabaloo-config-manager@^1.1.0←babel-register/babel-jestconfig loader) up to4.6.2. Patches CVE-2018-16469 / GHSA-4xc9-xhrj-v574 (proto pollution via attacker-controlled source object inlodash.merge(dst, src)). Scope is dev/test only: concordance is exercised when ava prints diff output for failing test assertions; hullabaloo-config-manager is the cache layer that loads.babelrcforbabel-register. Both merge developer-authored config objects (ava test specs, babel config) — sink not reachable in this project. Bump is patch-within-minor (4.6.1 → 4.6.2); the package ships a single_.merge(dst, src)function whose signature is unchanged across the patch line. Compile bundle unchanged at 9.21 MB / 1217 modules. Thehandlebarsresolution bumps the single[email protected]install (consumed byistanbul-reports@^1.3.0←istanbul-api@^1.1.14←jest-cli@^22.4.4) up to4.7.9(latest within^4.7.7). Patches GHSA-q2c6-c6pm-g3gh (proto pollution via template compile, affects<4.7.7). Scope is dev/test only: handlebars is exercised solely by istanbul-reports when rendering HTML coverage report templates, which only runs underjest --coverage; Boostnote'snpm testnever passes--coverage. Sink not reachable: the templates are hardcoded inistanbul-reports/lib/html/*.html.hbsand the compile inputs are developer-authored coverage data, never user input. Bump is minor-within-major (4.5.3 → 4.7.9). API surface (Handlebars.compile(),Handlebars.registerHelper(),Handlebars.SafeString) is stable across the 4.x line. Compile bundle unchanged at 9.21 MB / 1217 modules. - Dev-server (HMR) only:
cookie ^0.7.0,serve-static ^1.16.0,sockjs ^0.3.20,on-headers ^1.1.0,express ^4.20.0,url-parse ^1.5.8,min-document ^2.19.1,eventsource ^1.1.1,follow-redirects ^1.14.7. The first six load through[email protected]—cookie/serve-static/on-headers/expressare middleware in the dev HTTP server,sockjsis the WebSocket HMR transport, andurl-parseis consumed bysockjs-clientfor parsing the HMR socket URL.min-documentreaches the renderer only via thebabel-preset-react-hmretoolchain —react-transform-hmr→global→min-document— which is gated on.babelrc#env.development.presets. Theeventsourceresolution bumps the single[email protected]exact pin (consumed bysockjs-client@^1.0.3←webpack-dev-server@^1.12.0) up to1.1.2(latest within^1.1.1), patching GHSA-6h5x-7c5m-7cr5 (Authorization header exposure across cross-origin redirects, affects<1.1.1). sockjs-client uses the W3C EventSource surface (new EventSourceDriver(url),onmessage,onerror,readyState,close()), stable across the 0.x → 1.x jump — the eventsource 1.x rewrite changed only internal redirect handling. CVE sink is not reachable in this project: HMR clients connect only tohttp://localhost:webpack-dev-server, no cross-origin redirects, and the EventSource is never exercised in any context other thannpm run dev. Thefollow-redirectsresolution bumps the single[email protected]install (consumed byhttp-proxy@^1.16.2←http-proxy-middleware@~0.17.1←webpack-dev-server@^1.12.0) up to1.16.0(latest within^1.14.7). Patches GHSA-74fj-2j2h-c42q (Authorization + Cookie header exposure on cross-domain redirects, affects<1.14.7). API surface is the{http, https}wrapper objects with Node-style.request()/.get()signatures, stable across the 1.x line, so[email protected]'s use offollowRedirects.http.request(opts, cb)works unchanged. Sink unreachable: http-proxy-middleware proxies dev HMR requests to localhost backends configured statically ingruntfile.js/webpack.config.js, never to attacker-controlled endpoints that could issue a cross-domain redirect. Webpack 1'sDefinePluginconstant-folds the dev branch out of the production compile, so none of these touch the shipped Electron binary.
- Renderer / runtime-touching:
- Pinned direct deps with a documented ceiling:
uuidis pinned to^9.0.1. Bumping past 9.x is BLOCKED by webpack 1's acorn parser, not by the ESM cliff. uuid10.xand11.xstill ship a CJSmain(./dist/cjs/index.js), so they look bumpable, but the files underdist/cjs/use ES2020 nullish coalescing (??) and optional chaining (?.) — webpack 1's bundled acorn (v3.x) cannot tokenize either operator and the compile aborts withModule parse failed … Unexpected tokenat everyv1.js/v4.js/v6.js/v7.js/v35.jsentrypoint. Tried 9.0.1 → 11.1.1 empirically; rolled back. uuid13.0.0+adds the ESM-only cliff on top (nomainfield at all). Bumping requires either the webpack 1 → 5 migration (acorn 8 supports ES2020), or a babel-loader exception that transpilesnode_modules/uuidthrough@babel/plugin-proposal-nullish-coalescing-operator+@babel/plugin-proposal-optional-chaining(babel 7-only — would mean dragging a parallel babel 7 install in for one dep). Both are out of scope for incremental work. Production consumer:browser/lib/keygen.js—const { v4: uuidv4 } = require('uuid').mermaidis pinned to~9.1.7(tier-A target chosen during the 8 → 9 investigation — last 9.x release before the v9.2 monorepo + lazy-loadimport()rewrite that Webpack 1 cannot resolve). Tiers B (~9.3.0) and C (^9.4.3) are still upgrade candidates if a tier-A → tier-B smoke pass is performed; v10 is blocked by the same ESM-only / pure-exportscliff that blocksuuid 13+.highlight.jsis pinned to^10.4.1(9.x is EOL). 11.x is ESM-only and blocked by the Webpack 1 cliff.raphaelis pinned to exactly2.2.7(no caret). 2.3.0 produces NaN SVG dimensions where 2.2.7 silently coerced undefined width/height inputs to 0. Any flowchart or sequence-diagram code block in a note triggers it:rewriteIframe(browser/components/MarkdownPreview.js) callsdiagram.drawSVG(el, opts)on each.flowchart/.sequenceblock, which routes through Raphael's geometry math —<svg>attributes come out as"NaN"and React's reconciler throwsError: <svg> attribute height: Expected length, "NaN". raphael is external-loaded (webpack-skeleton.js:53mapsraphael: 'var Raphael'andlib/main.production.html:161loads<script src="../node_modules/raphael/raphael.min.js">), so the hoisted top-level version controls the actual runtime — the direct-dep pin is the source of truth. [email protected] itself declaresraphael: "2.2.7"exact in its own dependencies, which is the upstream contract this pin matches. Caret is forbidden:"raphael": "^2.2.7"resolves to 2.3.0 underyarn install --force, which is the bug this pin prevents. Production consumers:browser/components/MarkdownPreview.js(flowchart + sequence-diagram drawSVG paths) via the runtime global.codemirror-mode-elixiris pinned to exactly1.1.1(no caret). Same dist-layout regression asflowchart.jsbelow: 1.1.2 renamed the published dist file fromdist/elixir.jstodist/codemirror-mode-elixir.{js,m.js,umd.js}and ships nothing at the old path.lib/main.production.html:119andlib/main.development.html:123load the mode through<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js">, which 404s on 1.1.2 withFailed to load resource: net::ERR_FILE_NOT_FOUND elixir.js:1. The mode never registers and Elixir syntax highlighting silently fails in code blocks. Pinning to1.1.1(bare, no caret) restores the original file. Moving forward to 1.1.2+ requires editing both HTML files to point atdist/codemirror-mode-elixir.umd.js— a small HTML edit but out of scope for incremental dep work and not strictly necessary since 1.1.2 carries no CVE.flowchart.jsis pinned to exactly1.12.0(no caret). 1.12.0 is the last release that ships the pre-built UMD bundle atrelease/flowchart.min.js; the maintainer deleted therelease/directory entirely in 1.12.1+ and now only publishessrc/source.lib/main.production.html:162andlib/main.development.html:166load flowchart through a<script src="../node_modules/flowchart.js/release/flowchart.min.js">tag, andwebpack-skeleton.js:54resolvesrequire('flowchart')to avar flowchartexternal — so the renderer expects the pre-built UMD to populate the global at runtime. Bumping past 1.12.0 makes the<script>tag 404 (net::ERR_FILE_NOT_FOUND) and the nextrequire('flowchart')throwsReferenceError: flowchart is not definedat the first diagram. Bumping forward to 1.13+/1.18.x requires moving from the<script>-tag/external pattern to webpack-bundling flowchart.js as a normalimport— a renderer integration change, not a dep bump — and is out of scope for incremental work. Production consumers:browser/lib/markdown.js(flowchart code-block rendering) via the runtime global. Caret is forbidden:"flowchart.js": "^1.6.5"resolves to 1.18.0 underyarn install --force, which is the bug this pin prevents.
- Skipped CVE bumps (documented "do nothing"):
loader-utils ^1.4.1(CVE-2022-37601 prototype pollution inparseQuery()) — no patched 0.2.x release exists; the 0.2 line is EOL. Empirically retried with global"loader-utils": "^1.4.1"resolution;npm run compilefails at the very first module (./browser/main/index.js) withError: A valid query string passed to parseQuery should begin with '?' at Object.parseQuery (loader-utils/lib/parseQuery.js:13:11) at module.exports (babel-loader/lib/index.js:104:35). Root cause: loader-utils 1.x tightenedparseQueryto require a leading?on its input, but[email protected]callsloaderUtils.parseQuery(this.query)wherethis.queryis the raw query string without the leading?. Same call pattern in[email protected],[email protected],[email protected], and[email protected]'s own internal loader-resolver — bumping the loader-utils version means bumping every loader that uses it (babel-loader 7+,css-loader 1+,style-loader 1+,stylus-loader 3+), i.e. the webpack 1 → 2 migration. The CVE sink is not exploitable in this project: query strings are developer-authored in webpack-config loader chains (style!css?modules&sourceMap!stylus) and never user-controlled. Skipped — defer to the webpack 1 → 5 migration tracked under "Outstanding security work" item #3.electron 11.5.0 → 13.x / 14.x— GHSA-3p22-ghq8-v749 (renderer accesses random Bluetooth device without permission) requires the renderer to callnavigator.bluetooth.requestDevice(...). Boostnote source has zeronavigator.bluetoothreferences; the CVE is not exploitable. Upgrading to Electron 13+ deletes theremotemodule entirely (replaced by the separate@electron/remotenpm package) and flipsenableRemoteModuletofalseby default — both require a code migration touching everyremote.*call site (24 source files:browser/components/MarkdownPreview.js,browser/lib/contextMenuBuilder.js,browser/lib/context.js,browser/main/Main.js,browser/main/SideNav/*,browser/main/Detail/*,browser/main/modals/*, etc.) pluslib/main-window.js(dropenableRemoteModule: true, add@electron/remote/main.initialize()+.enable(webContents)), plus 2 HTML inline-script edits inlib/main.{production,development}.html(theelectron.remote.process.argvHMR--hotdetection) and 1 shadow-import delete inbrowser/main/NoteList/index.js:1029. Tracked migration plan:UpgradePlan_Electron11_to_Electron14.md— phased into Commit A (@electron/remote@^1.2.2swap, still on Electron 11, zero runtime change, fully reversible) and Commit B (Electron 14.2.9 runtime bump +menu.popup(win)→menu.popup({window})× 3 sites +printToPDF({}, cb)→ Promise form).contextIsolation: true(Phase 3) deferred — separate ~1-2 day refactor. Status: still skipped until someone executes the plan. When executed, also update the "Pinned direct deps" section to add an entry for@electron/remotedocumenting the 1.x vs 2.x peer-dep cliff (1.x supports Electron ≥10; 2.x requires ≥13).Applied 2026-05-27 asminimatch ^3.0.2—^3.xconsumers already resolve to 3.1.5 via hoist; the only sub-target version is0.3.0under[email protected] > [email protected], which would break stylus's build if forced. ReDoS not exploitable on stylus's hard-coded internal patterns. Skipped."minimatch": "^3.1.4"in the resolutions block. Empirical test passed —[email protected]uses onlyminimatch.Minimatchclass +.set+GLOBSTARconstant, all stable across the 0.3 → 3.x bump, so stylus still compiles cleanly. Forcing the resolution collapses three previously-coexisting entries ([email protected]→ 0.3.0 under stylus's glob chain;minimatch@^3.0.4→ 3.1.5 underelectron-packager > @electron/asar;minimatch@*→ 10.2.5 under@types/minimatch, a TypeScript-only type stub never executed by the Babel 6 build) to a single[email protected]. Patches CVE-2022-3517 (ReDoS inbraceExpandvia attacker-controlled brace patterns). Sink remained unreachable in this project's data flow — stylus feeds developer-authored glob patterns (browser/**/*.styl, etc.) — but the bump clears the Dependabot complaint and removes the on-disk dead-weight copies.braces ^3.0.3— lock has three coexisting majors:[email protected](consumed bymicromatch@^2viajest@22,[email protected]chokidar,http-proxy-middleware@~0.17.1,anymatch@^1),[email protected](consumed bymicromatch@^3via newer jest,sane@^2,test-exclude@^4), and[email protected](consumed bymicromatch@^4). CVE-2024-4068 (ReDoS via attacker-controlled brace-pattern string) affects 1.x and 2.x. A global"braces": "^3.0.3"resolution would break everymicromatch@^2/^3consumer because braces 3 changed the public API (returns an array by default, different option keys). Bumpingmicromatchitself to^4.xwould propagate through deeply-pinned dev tools (jest@22,[email protected],http-proxy-middleware@~0.17.1) which are not API-compatible with micromatch 4. The CVE sink is not reachable in this project: every micromatch consumer feeds developer-authored glob patterns (test paths, watch globs, webpack-dev-server proxy maps) — never user input. Skipped, same reasoning asloader-utils/minimatch.postcss 5.x → 8.4.31+(GHSA-7fh5-64p2-3v2j XSS via unescaped</style>in stringify output; CVE-2023-44270 line-return parsing error) — lock already collapses 12 coexistingpostcss@^5.0.x/^5.2.16ranges to a single[email protected](the 5.x ceiling). Neither CVE is patched on the 5.x line — postcss 5 is EOL: GHSA-7fh5-64p2-3v2j was fixed only in 8.2.10 and CVE-2023-44270 only in 8.4.31, with no backport to 5.x or 6.x. The Dependabot-suggested "5.2.18" is simply the latest 5.x release and is inert as a fix. Consumer chain is the build-time CSS minify pipeline:css-loader@^0.19.0,autoprefixer@^6.3.1, and ~30postcss-*plugins fromcssnano@^3.x(postcss-calc, postcss-colormin, postcss-discard-, postcss-merge-, postcss-minify-, postcss-modules-, postcss-normalize-, postcss-reduce-, postcss-svgo, postcss-unique-selectors, postcss-zindex, etc.). All run at webpack-1 compile time viaExtractTextPlugin; minified CSS is written to disk, never re-parsed at Electron runtime. Bumping postcss 5 → 8 requirescss-loader 0.19 → 4+(postcss 8 peer),autoprefixer 6 → 10+(postcss 8 peer), and a full cssnano 3 → 6 plugin-API rewrite (postcss 6 changedplugin(name, fn)to the async visitor pattern; postcss 8 dropped theprocess()callback form entirely). All three are gated on the webpack 1 → 2 → 5 migration tracked under "Outstanding security work" item #3. CVE sinks are not reachable in this project: every CSS input fed to the build-time postcss is developer-authoredbrowser/**/*.stylcompiled through Stylus, never user-controlled content — the stringify XSS sink would require an attacker to place a</style>substring inside CSS that round-trips throughpostcss.parse(...).toString()at build time, and the line-return sink similarly requires attacker-controlled CSS source. Renderer-bundled postcss path was previously reached viasanitize-html@^2.7.1→postcss@^8.3.11, force-resolved through the selective"sanitize-html/postcss": "^7.0.39"entry; that selective entry was removed along with thesanitize-htmldirect dep (see "Removed prod-only deps" below) once the custombrowser/lib/markdown-it-sanitize-html.jsplugin became the sole sanitizer path and no longer imported the lib. The renderer no longer bundles any runtime postcss copy; only the build-time[email protected]chain remains. Skipped — same reasoning class asloader-utils/minimatch/braces/@babel/traverse. Track with the webpack 1 → 5 migration.@babel/traverse 7.23.2(CVE-2023-45133) — the patched version is the scoped@babel/[email protected], which is not in this tree at all. The repo carries the legacy unscoped[email protected](consumed by everybabel-core@6,babel-helper-*@6,babel-plugin-transform-*@6,babel-template@6entry, i.e. the entire babel 6 transpile chain). The babel team's security policy explicitly states that babel 6 receives no further security fixes — there is no patched 6.x release to bump to. Applying the v7 fix means a full babel 6 → 7 migration: everybabel-*direct devDep (babel-core,babel-loader,babel-jest,babel-register,babel-preset-env,babel-preset-es2015,babel-preset-react,babel-preset-react-hmre,babel-plugin-react-transform,babel-plugin-webpack-alias) plus.babelrcplusbabel-loader 6 → 8which is itself gated by the webpack 1 → 2+ migration. The CVE sink is not reachable in this project's threat model: babel only runs at build time (webpack-loader, jest'sbabel-jest, ava'sbabel-register) against developer-authored source inbrowser//lib//tests/. Note content reaches the renderer through markdown-it → the custombrowser/lib/markdown-it-sanitize-html.jsplugin, never through babel. There are zero programmaticbabel.transform(...)orrequire('babel-core')call sites inbrowser/orlib/. Skipped — defer to the webpack 1 → 5 / babel 6 → 7 migration tracked under "Outstanding security work" item #3.
- Webpack 1 dep ceilings (general rule): if a dep ships pure ESM (
"type": "module"and nomain), it will fail to resolve. Always check the candidate'spackage.jsonbefore accepting a major bump. Known blocked majors:uuid 12+,mermaid 10+,json5 2+. optionalDependenciesis intentionally absent. The previously-listedgrunt-electron-installer-debianandgrunt-electron-installer-redhat(plus theirelectron-installer-*task blocks ingruntfile.js) were removed once it was confirmed that the GH Actions release workflow only ships.zip/.tar.gzof the packaged app — no.debor.rpmwas ever built in CI. Restore the two devDeps, the task config blocks, and the entries in thebuild:linuxtask chain if you ever want to resume packaging Linux installers.- Removed dev-only deps (dead code):
devtron— deprecated Electron DevTools extension (removed from official Electron docs in v10+; Boostnote ships on v11). No source file ever imported it; only references were a stale'devtron'entry inwebpack-skeleton.js#externalsand the devDep declaration. Removed; freed ~32 MB of devnode_modules.redux-devtools,redux-devtools-dock-monitor,redux-devtools-log-monitor— powered an in-app Redux debug overlay loaded only whenNODE_ENV === 'development'. CI always builds production, so the dev branch was dead code in every shipped binary. Removed alongsidebrowser/main/DevTools/index.dev.jsandindex.prod.js;browser/main/DevTools/index.jsis now the single no-op stub (() => <div />plusinstrument: () => {}) — keepsstore.jsandbrowser/main/index.jscall sites unchanged. To restore the overlay, recreateindex.dev.jswithcreateDevTools(...)and add the env-switch back toindex.js.standard— the standalone "StandardJS" CLI. Never invoked (nonpm run standardscript, no grunt task)..eslintrc#extends: ["standard"]resolves toeslint-config-standard(a separate devDep), not thestandardpackage itself. Removed; freed ~33 MB of transitive closure (its own nested eslint, eslint-plugin-import, eslint-plugin-node, etc.). Side effect:eslint-plugin-promise@^3.4.2is now declared as a direct devDep — it was previously hoisted out ofstandard's nested copy and is required as a peer of[email protected]. Without that explicit pin, ESLint refuses to load the config.concurrently— never invoked (nonpmscript, no grunt task, no source reference). The 100-ish transitive descendants (yargs 17, chalk 4, supports-color 8, modern lodash variants) were pure build-time bloat. Removed; freed ~13 MB and ~110 lockfile lines. To restore for a future parallel dev workflow, re-addconcurrently@^9.xand wire it into a newnpm run devscript.react-input-autosize— never imported anywhere inbrowser/orlib/. Likely leftover from an early autosizing tag-input component that was replaced. Removed; freed a small (~25 KB) entry plus itsreact-prop-typestransitive.immutable(production dep, not dev) — never imported in source.browser/lib/Mutable.jsis a thin wrapper around the native ESMap/Set, unrelated to immutable.js despite the name. The only other consumer is[email protected], which listsimmutableas an optional peer — CRR keeps working without it for the standardConnectedRouter/connectRouter/routerMiddleware/pushAPI surface that Boostnote uses. Dropping the direct dep also resolved the CVE proto-pollution advisory: yarn now installs [email protected] through CRR's optional-dep range (^3.8.1 || ^4.0.0), well past the patched 3.8.3. To restore for a future immutable-keyed router variant, re-addimmutableas a direct dep at the version of choice.sanitize-html(production dep, not dev) — last call site wasbrowser/lib/markdown-it-sanitize-html.js'ssanitizeHtml()invocation onhtml_blocktokens. Commitabf79e43replaced that call with the in-filesanitizeBlock()helper, which reuses the existing regex-basedsanitizeInline()validator. The lib became unreachable from the renderer bundle. Removed the direct dep along with the selective"sanitize-html/postcss": "^7.0.39"resolution (the only reason that resolution existed was to pin sanitize-html'spostcss@^8.3.11peer to the patched 7.x line; with sanitize-html gone, no renderer-bundled postcss copy exists at all). Verified bynpm run compile: bundle shrank from 9.21 MB / 1217 modules to 8.3 MB / 1146 modules — sanitize-html plus itshtmlparser2,parse-srcset,deepmerge,is-plain-object,[email protected]+ ~30 postcss helper transitives dropped out. To restore (e.g., to harden the regex-based validator against attribute-value edge cases like<a title="x > y">that the naive/<[^>]*>/gtokenizer splits wrong), re-addsanitize-html@^2.7.xas a direct dep, re-add the"sanitize-html/postcss": "^7.0.39"resolution to keep postcss on the patched 7.x line, and revertbrowser/lib/markdown-it-sanitize-html.js#sanitizeBlockback to thesanitizeHtml()call.
Full docker build . takes ~5 min (compile + electron-packager + grunt pack). For iterative dependency work, build the deps stage once and reuse it:
# One-time: build deps-only image
docker build --target deps -t bn-deps .
# Per iteration: edit package.json resolutions, then:
docker run --rm -v "$(pwd)":/app -v /app/node_modules -w /app bn-deps \
sh -c 'yarn install --ignore-engines && npm run compile'The -v /app/node_modules anonymous volume preserves the container's node_modules while bind-mounting the host source over /app. npm run compile reuses the cached deps and finishes in ~5s; it is the fastest reliable signal that a dep change has not broken the webpack bundle. Run the full docker build . once at the end to validate electron-packager.
Stale-deps trap: the anonymous volume holds whatever node_modules was baked into the bn-deps image at build time. If you have edited package.json resolutions since then, plain yarn install --ignore-engines may decide the lockfile is satisfied and not actually rewrite node_modules. Use yarn install --ignore-engines --force to force a relink, or rebuild the image with docker build --target deps -t bn-deps . when the resolution churn is non-trivial.
Pre-commit hook: husky's pre-commit runs npm run lint. Because the docker-only policy keeps npm / yarn off the host, the hook prints Can't find yarn in PATH and reports Skipping pre-commit hook. The commit proceeds. This is expected — lint runs inside the image when you want it (docker run --rm boostnote-legacy npm run lint), not on the host.
npm test=npm run ava && npm run jest(sequential).- AVA runs serially (
--serial). - Jest picks up test files inside
dist/Boostnote-darwin-*/→ fail with environment mismatch. Ignore. createNote/createNoteFromUrlJest tests fail with "Target folder doesn't exist" (test-data issue). Ignore.
- ESLint:
standard+standard-jsx+plugin:react/recommended+prettier. - Prettier config:
singleQuote: true,semi: false,jsxSingleQuote: true. - Unused vars/undef are warnings, not errors.
- Do NOT fix the 6 pre-existing
prettier/prettiererrors inMarkdownPreview.js,markdown.js,store.js— host prettier (1.18) and Docker prettier (1.19) disagree; fixing one breaks the other. - Pre-commit hook runs
npm run lint(husky).
- GitHub Advanced Security CodeQL scans land on
mainas commits titledPotential fix for code scanning alert no. N(Copilot Autofix). These are usually safe ReDoS / regex tightenings, but each one mutates a parser/sanitizer regex — review before assuming the diff is harmless, and run the full docker build afterwards. Recent sweep: alerts 14, 15, 16, 20, 24 (May 2026). js/incomplete-sanitizationwas hit inbrowser/components/CodeEditor.js(escapePipe). The fix lives inbrowser/lib/utils.jsasescapeMarkdownPipe(str)— it escapes backslashes before pipes so the encoding is reversible. Unit-tested intests/lib/escapeMarkdownPipe.test.js. Reuse that helper rather than inlining new escape logic.
Ordered by runtime impact (renderer-bundled first, build-only / blocked last). Items already applied or explicitly skipped live in the Dependency policy section above.
markdown-it5.1.0 and 8.4.2 still pinned by transitive consumers (@enyaxu/markdown-it-anchor@5, etc.); resolve to the already-locked 12.3.2 via theresolutionsblock once the parser-plugin chain (footnote, kbd, anchor) is verified.- Mermaid tier B / C (
~9.3.0then^9.4.3) — escalate only after manually rendering one of each diagram type from a packaged build and confirming noChunkLoadErrorfrom the v9.2+ lazy-load chunks. - Webpack 1 ceiling — deferred until webpack 1 → 5 migration:
[email protected],[email protected], anduuid >= 10(see Pinned direct deps above for the acorn-cliff post-mortem). Both EOL and CVE-flagged, but no patched version exists within their pinned line. Bumping requires the full toolchain migration (webpack 1 → 2 → 5, babel 6 → 7, css-loader / style-loader / stylus-loader majors). Out of scope for incremental work — see the prior investigation summary in the git log around thewebpack-dev-server/loader-utils"do nothing" decisions.