Skip to content

chore: reduce Node.js threat surface — Node 24, ESLint 10 flat config, dep upgrades#307

Open
wernermorgenstern wants to merge 7 commits into
mainfrom
chore/node-threat-reducer-remediation
Open

chore: reduce Node.js threat surface — Node 24, ESLint 10 flat config, dep upgrades#307
wernermorgenstern wants to merge 7 commits into
mainfrom
chore/node-threat-reducer-remediation

Conversation

@wernermorgenstern
Copy link
Copy Markdown

Summary

  • 0 npm audit vulnerabilities (down from 15 critical/high/moderate)
  • ESLint 8 → 10 with full flat config migration; no lint rules lost
  • Node 20 → 24.15.0 engine and runtime pin
  • All existing tests pass (94/94); lint passes (0 errors, 0 warnings)
  • No branch, no breaking changes to published lib/ code

Dependency changes

Removed

  • coveralls — abandoned package with no-fix critical chain (requestform-dataqstough-cookie). CI already uses coverallsapp/github-action directly.
  • uuid — unused devDependency

Production

  • axios ^1.13.2 → ^1.16.1 — fixes 16 CVEs (SSRF, prototype pollution, header injection)

Dev — security fixes

  • mocha ^10 → ^11.7.5
  • Added overrides.serialize-javascript@^7.0.5 and overrides.diff@^9 to patch vulnerabilities bundled inside mocha 11

Dev — major upgrades (with example fixes)

  • chai 2.3.0 → 6.2.2 — works via Node 24 synchronous ESM interop
  • p-map 2.1.0 → 7.0.4 — same; destructures default export in example
  • csv-parse 4 → 6 — fixed named-export import and data-first arg in examples
  • json-2-csv 3 → 5 — migrated callback API to Promise in example
  • nyc 15 → 18
  • cross-env 5 → 10
  • nock 14.0.10 → 14.0.15

Dev — ESLint ecosystem

  • eslint ^8.6.0 → ^10.4.0
  • eslint-config-lob ^5.2.0 → ^7.0.0
  • eslint-plugin-lob ^3.0.0 → ^3.0.2
  • Added: @eslint/js, @stylistic/eslint-plugin, eslint-plugin-jsdoc, globals

ESLint flat config migration

Replaced .eslintrc + .eslintignore + examples/.eslintrc with eslint.config.js.

Rules preserved via replacement packages:

Removed from ESLint core Replacement
valid-jsdoc jsdoc/check-param-names, jsdoc/check-types, etc.
no-negated-in-lhs no-unsafe-negation
no-spaced-func, formatting rules @stylistic/* equivalents
prefer-reflect Dropped — removed from ESLint 9 with no replacement

Node 24.15.0

  • .node-version pinned to 24.15.0
  • engines.node updated to >= 24.15.0
  • Node 24 added to CI matrix (all three workflows)

GitHub Actions

  • actions/checkout v2 → v4
  • actions/setup-node v2 → v4
  • coverallsapp/github-action@master@v2 (was floating on master)
  • Added npm install -g npm@11 pin step to test workflows
  • Publish workflow updated to Node 24

Other

  • .gitignore — added macOS section

Supersedes Dependabot PRs

This branch closes or supersedes: #306, #304, #302, #301, #299, #295, #290, #289, #286, #305

Test plan

  • npm install --legacy-peer-deps — clean install
  • npm test — 94 passing, 0 failing
  • npm run lint — 0 errors, 0 warnings
  • npm audit — 0 vulnerabilities
  • CI matrix passes on Node 20, 22, 24

…I pins

Dependency remediation (0 npm audit vulnerabilities):
- Remove coveralls (abandoned; no-fix critical chain via request/form-data/qs/tough-cookie)
- Upgrade axios ^1.13.2 → ^1.16.1 (fixes 16 high/moderate CVEs)
- Upgrade mocha ^10 → ^11.7.5; add overrides for serialize-javascript@^7.0.5 and diff@^9 to fix vulnerabilities bundled inside mocha 11
- Upgrade nock 14.0.10 → 14.0.15 (memory leak fix)
- Upgrade cross-env 5.2.1 → 10.1.0 (Node 20 minimum, CLI unchanged)
- Upgrade nyc 15.1.0 → 18.0.0 (CLI/config unchanged)
- Upgrade chai 2.3.0 → 4.5.0 (capped: v5+ is ESM-only)
- Upgrade p-map 2.1.0 → 4.0.0 (capped: v5+ is ESM-only)
- Upgrade csv-parse 4 → 6 (CJS dual-mode; fix destructured import and data-first arg order)
- Upgrade json-2-csv 3 → 5 (promise API replaces callback; fix prependHeader option casing)
- Upgrade eslint-plugin-lob 3.0.0 → 3.0.2
- Remove unused uuid devDependency

ESLint flat config migration (eslint 8 → 10, eslint-config-lob 5 → 7):
- Replace .eslintrc + .eslintignore with eslint.config.js (CommonJS flat config)
- Add @eslint/js, @stylistic/eslint-plugin, eslint-plugin-jsdoc, globals as devDependencies
- Migrate removed core rules: valid-jsdoc → jsdoc/check-*, no-negated-in-lhs → no-unsafe-negation, style rules → @stylistic/*
- Preserve examples/no-console:0 override and all repo globals from legacy config
- Fix 3 JSDoc @param {Object} → {object} in test/mocks/index.js (jsdoc/check-types)

GitHub Actions:
- Add Node 24 to CI matrix in run_tests.yml and forked_run_tests.yml
- Upgrade actions/checkout v2 → v4 and actions/setup-node v2 → v4
- Add npm@11 pin step to CI workflows
- Pin coverallsapp/github-action@master → @v2
- Update publish workflow to Node 24

Other:
- Add macOS section to .gitignore
- Pin .node-version and engines.node to 24.15.0
- Upgrade chai 4.5.0 → 6.2.2 (Node 24 synchronous ESM interop allows require())
- Upgrade p-map 4.0.0 → 7.0.4 (same); destructure default export in example
  since Node 24 require() of ESM yields a module namespace object, not the default
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the project's infrastructure, including a Node.js version bump to 24.15.0, a migration to ESLint 10, and significant dependency updates. While the examples were refactored to accommodate newer library versions, several issues were identified in the CSV processing logic. Specifically, switching from streaming to synchronous file reading introduces a memory exhaustion risk for large datasets. Furthermore, the use of asynchronous file writes on shared descriptors without synchronization or proper line endings can lead to data corruption, and the current error handling may result in unhandled promise rejections.

Comment on lines +72 to +75
.then((csv) => fs.write(successFd, csv, () => {}))
.catch((err2) => {
throw err2;
}
fs.write(successFd, csv);
}, { PREPEND_HEADER: false });
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using fs.write asynchronously on the same file descriptor without synchronization is unsafe and can lead to data corruption or interleaved output when multiple requests complete concurrently. Additionally, json2csv for a single object does not include a trailing newline, so multiple records will be concatenated on the same line. Finally, throwing inside an un-awaited promise catch block causes an unhandled rejection. Using fs.writeSync here ensures sequential, safe writes for this example script.

Suggested change
.then((csv) => fs.write(successFd, csv, () => {}))
.catch((err2) => {
throw err2;
}
fs.write(successFd, csv);
}, { PREPEND_HEADER: false });
});
.then((csv) => {
fs.writeSync(successFd, csv + "\n");
})
.catch((err2) => {
console.error("Error writing to success file:", err2);
});

Comment on lines +80 to +83
.then((csv) => fs.write(errorFd, csv, () => {}))
.catch((err2) => {
throw err2;
}
fs.write(errorFd, csv);
}, { PREPEND_HEADER: false });
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using fs.write asynchronously on the same file descriptor without synchronization is unsafe and can lead to data corruption or interleaved output when multiple requests complete concurrently. Additionally, json2csv for a single object does not include a trailing newline, so multiple records will be concatenated on the same line. Finally, throwing inside an un-awaited promise catch block causes an unhandled rejection. Using fs.writeSync here ensures sequential, safe writes for this example script.

          .then((csv) => {
            fs.writeSync(errorFd, csv + "\n");
          })
          .catch((err2) => {
            console.error("Error writing to error file:", err2);
          });

const lob = new LobFactory('YOUR_API_KEY');

const inputFile = fs.createReadStream(`${__dirname}/input.csv`);
const inputData = fs.readFileSync(`${__dirname}/input.csv`, { encoding: 'utf-8' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Changing from fs.createReadStream to fs.readFileSync loads the entire CSV file into memory. This is a regression in scalability and could lead to Out-Of-Memory (OOM) issues if the input file is large, which contradicts the goal of reducing the threat surface by introducing a potential Denial of Service vector.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the project's infrastructure, including a migration to ESLint flat config, updating the Node.js engine requirement, and upgrading several dependencies. In the examples, file handling was changed from streams to synchronous reads, and the json-2-csv integration was updated to use Promises. Critical feedback was provided regarding the use of non-existent versions for Node.js and several npm packages (e.g., ESLint, Mocha, Chai). Additionally, bugs were identified in the CSV generation logic where missing newlines would lead to corrupted output files, and an improvement was suggested for the ESLint ecmaVersion setting.

Comment thread package.json
Comment on lines +21 to +38
"@eslint/js": "^10.0.1",
"@stylistic/eslint-plugin": "^5.10.0",
"agentkeepalive": "^4.1.0",
"chai": "^2.2.0",
"coveralls": "^3.0.5",
"cross-env": "^5.2.0",
"csv-parse": "^4.4.6",
"eslint": "^8.6.0",
"eslint-config-lob": "^5.2.0",
"chai": "^6.2.2",
"cross-env": "^10.1.0",
"csv-parse": "^6.2.1",
"eslint": "^10.4.0",
"eslint-config-lob": "^7.0.0",
"eslint-plugin-jsdoc": "^62.9.0",
"eslint-plugin-lob": "^3.0.2",
"generate-changelog": "^1.0.0",
"json-2-csv": "^3.15.1",
"mocha": "^10.0.0",
"globals": "^17.6.0",
"json-2-csv": "^5.5.10",
"mocha": "^11.7.5",
"moment": "^2.22.1",
"nock": "^14.0.10",
"nyc": "^15.1.0",
"p-map": "^2.1.0",
"uuid": "^3.1.0"
"nock": "^14.0.15",
"nyc": "^18.0.0",
"p-map": "^7.0.4"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Several dependency versions specified in this PR do not exist in the public npm registry. For example, eslint is currently at v9.x (not v10.4.0), mocha is at v11.0.x (not v11.7.5), chai is at v5.x (not v6.2.2), and cross-env is at v7.x (not v10.1.0). Additionally, npm v11.5.1 and diff v9.0.0 are also non-existent. Please verify these version strings, as they will cause npm install to fail.

Comment thread package.json
"main": "./lib/index",
"engines": {
"node": ">= 20.0.0",
"node": ">= 24.15.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Node.js version 24.15.0 is not a released version (the current LTS is 22.x). Requiring a non-existent or extremely new version is a significant breaking change that will prevent the library from being used in most production environments. It is recommended to target a stable LTS version like 22.11.0 or 20.18.0.

Suggested change
"node": ">= 24.15.0",
"node": ">= 22.11.0",

converter.json2csv(client, (err2, csv) => {
if (err2) {
converter.json2csv(client, { prependHeader: false })
.then((csv) => fs.write(successFd, csv, () => {}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

converter.json2csv (v5) does not include a trailing newline when converting a single object with prependHeader: false. Successive calls to fs.write will append data to the same line, resulting in a corrupted CSV file. Using fs.writeSync here ensures the write operation completes before the next iteration of the loop and simplifies error handling within the promise chain.

Suggested change
.then((csv) => fs.write(successFd, csv, () => {}))
.then((csv) => fs.writeSync(successFd, csv + '\n'))

converter.json2csv(client, (err2, csv) => {
if (err2) {
converter.json2csv(client, { prependHeader: false })
.then((csv) => fs.write(errorFd, csv, () => {}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

A newline should be appended here as well to ensure the error CSV is correctly formatted. Using fs.writeSync is recommended for consistency and to ensure data integrity during concurrent operations.

Suggested change
.then((csv) => fs.write(errorFd, csv, () => {}))
.then((csv) => fs.writeSync(errorFd, csv + '\n'))

Comment thread eslint.config.js Outdated
...lobConfig,
{
languageOptions: {
ecmaVersion: 2017,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ecmaVersion is set to 2017. Since the project is targeting modern Node.js versions (20+), it is recommended to update this to latest or at least 2022 to support linting of modern JavaScript features (e.g., optional chaining, nullish coalescing) in tests and examples.

Suggested change
ecmaVersion: 2017,
ecmaVersion: 'latest',

@wernermorgenstern wernermorgenstern requested a review from a team May 18, 2026 20:17
@wernermorgenstern wernermorgenstern self-assigned this May 18, 2026
@wernermorgenstern wernermorgenstern marked this pull request as draft May 18, 2026 20:23
- examples/verify_and_create_letters_from_csv: restore streaming via
  createReadStream().pipe(parse()) instead of readFileSync — avoids OOM
  on large CSV files; use parser.on('data') event API (csv-parse v6)
- examples/verify_and_create_letters_from_csv: replace async fs.write
  with fs.writeSync to prevent interleaved/corrupted writes on shared
  file descriptors; append '\n' so records don't concatenate on one line
- eslint.config.js: bump ecmaVersion 2017 → 2022 to match Node 24 target
- CI workflows: add --force to npm install -g npm@11 to handle
  pre-installed npm version conflict on Node 20/22 runners
The toolcache npm binary on ubuntu-latest runners crashes with
MODULE_NOT_FOUND (promise-retry) when attempting to self-upgrade,
even with --force. Node 24 already ships npm@11 natively, and the
bundled npm on Node 20/22 runners is sufficient for --legacy-peer-deps
installs. The engines field communicates the npm requirement to consumers.
Integration tests require live API credentials and call production
endpoints; they are intended for manual/local verification, not
gating CI green/red status.
@wernermorgenstern wernermorgenstern marked this pull request as ready for review May 18, 2026 20:41
@wernermorgenstern wernermorgenstern requested a review from a team May 19, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants