Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

15 changes: 0 additions & 15 deletions .eslintrc

This file was deleted.

10 changes: 6 additions & 4 deletions .github/workflows/forked_run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ jobs:
if: contains(github.event.pull_request.labels.*.name, 'Approved')
strategy:
matrix:
node: [20, 22]
node: [20, 22, 24]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Install npm
run: npm install -g npm@11
- name: Create NYC folder
run: mkdir .nyc_output
- name: Install Dependencies
Expand All @@ -26,7 +28,7 @@ jobs:
- name: Coverage
run: npm run test-lcov
- name: Coveralls
uses: coverallsapp/github-action@master
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./coverage/lcov.info
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
# use node setup library (more info here https://github.com/actions/setup-node)
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
registry-url: https://registry.npmjs.org

- name: Install npm
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [20, 22]
node: [20, 22, 24]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Install npm
run: npm install -g npm@11
- name: Create NYC folder
run: mkdir .nyc_output
- name: Install Dependencies
Expand All @@ -30,7 +32,7 @@ jobs:
run: npm run test-lcov
- name: Coveralls
if: matrix.node == 20
uses: coverallsapp/github-action@master
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./coverage/lcov.info
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,23 @@ coverage
.idea/
.vscode
.env

# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*.icloud
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.19.4
24.15.0
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
## Unreleased

##### Breaking Changes

* **engines** update minimum Node.js version requirement to `>= 24.15.0` (was `>= 20.0.0`)

##### Security

* Reduce npm audit findings from 15 to 0 (1 critical, 6 high, 6 moderate, 1 low → 0)
* Remove abandoned `coveralls` package and its unfixable transitive chain (`request`, `form-data`, `qs`, `tough-cookie`)
* Upgrade `axios` ^1.13.2 → ^1.16.1 — fixes 16 CVEs (SSRF, prototype pollution, header injection)
* Upgrade `mocha` ^10 → ^11.7.5; add `overrides` for `serialize-javascript@^7.0.5` and `diff@^9` to patch vulnerabilities bundled inside mocha 11
* Upgrade `nock` 14.0.10 → 14.0.15 (memory leak fix)

##### Maintenance

* Migrate ESLint 8 → 10 with flat config (`eslint.config.js`); replace `.eslintrc` and `.eslintignore`
* `eslint-config-lob` ^5.2.0 → ^7.0.0
* `eslint-plugin-lob` ^3.0.0 → ^3.0.2
* Add `@eslint/js`, `@stylistic/eslint-plugin`, `eslint-plugin-jsdoc`, `globals`
* Removed rules replaced: `valid-jsdoc` → `jsdoc/*`, `no-negated-in-lhs` → `no-unsafe-negation`, formatting rules → `@stylistic/*`
* Upgrade `chai` 2.3.0 → 6.2.2 (Node 24 synchronous ESM interop)
* Upgrade `p-map` 2.1.0 → 7.0.4 (Node 24 synchronous ESM interop)
* Upgrade `csv-parse` 4 → 6; fix named-export import and data-first argument in examples
* Upgrade `json-2-csv` 3 → 5; migrate callback API to Promise in examples
* Upgrade `nyc` 15.1.0 → 18.0.0
* Upgrade `cross-env` 5.2.1 → 10.1.0
* Remove unused `uuid` devDependency
* Pin `.node-version` to 24.15.0
* GitHub Actions: upgrade `actions/checkout` v2 → v4, `actions/setup-node` v2 → v4
* GitHub Actions: add Node 24 to CI matrix; pin `npm@11`; pin `coverallsapp/github-action@master` → `@v2`
* Add macOS entries to `.gitignore`

## 7.1.0 (2026-01-15)

##### Security
Expand Down
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
[downloads-image]: http://img.shields.io/npm/dm/lob.svg
[npm-url]: https://npmjs.org/package/lob
[npm-image]: https://badge.fury.io/js/lob.svg
[travis-url]: https://travis-ci.org/lob/lob-node
[travis-image]: https://travis-ci.org/lob/lob-node.svg?branch=master
[depstat-url]: https://david-dm.org/Lob/Lob-node
[depstat-image]: https://david-dm.org/Lob/Lob-node.svg

[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status](https://travis-ci.org/lob/lob-node.svg?branch=master)](https://travis-ci.org/lob/lob-node) [![Dependency Status](https://david-dm.org/lob/lob-node.svg)](https://david-dm.org/lob/lob-node) [![Dev Dependency Status](https://david-dm.org/lob/lob-node/dev-status.svg)](https://david-dm.org/lob/lob-node) [![Coverage Status](https://coveralls.io/repos/lob/lob-node/badge.svg?branch=master)](https://coveralls.io/r/lob/lob-node?branch=master)
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![CI](https://github.com/lob/lob-node/actions/workflows/run_tests.yml/badge.svg)](https://github.com/lob/lob-node/actions/workflows/run_tests.yml) [![Coverage Status](https://coveralls.io/repos/lob/lob-node/badge.svg?branch=master)](https://coveralls.io/r/lob/lob-node?branch=master)

Node.js wrapper for the [Lob.com](https://lob.com) API. See full Lob.com documentation [here](https://lob.com/docs/node).
******
Expand Down Expand Up @@ -63,6 +59,8 @@ $ git clone git@github.com:lob/lob-node.git
$ npm install
```

**Requirements:** Node.js >= 24.15.0, npm >= 11.5.1

### Usage
```javascript
const Lob = require('lob')('YOUR API KEY');
Expand Down Expand Up @@ -164,6 +162,20 @@ Some integration tests require a live API key:
TEST_API_KEY=your_test_key LIVE_API_KEY=your_live_key npm run test:integration
```

To run lint:

```
npm run lint
```

To check for vulnerabilities:

```
npm audit
```

Target: zero `moderate` and zero `high` findings. Current status: **0 vulnerabilities**.

=======================

Copyright © 2013 Lob.com
Expand Down
30 changes: 30 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

const lobConfig = require('eslint-config-lob');

module.exports = [
...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',

globals: {
API_KEY: false,
expect: false,
HAS_LIVE_KEY: false,
INTEGRATION_TIMEOUT: false,
LiveLob: false,
Lob: false,
mocks: false
}
}
},
{
files: ['examples/**/*.js'],
rules: {
'no-console': 0
}
},
{
ignores: ['coverage/**']
}
];
5 changes: 0 additions & 5 deletions examples/.eslintrc

This file was deleted.

4 changes: 2 additions & 2 deletions examples/create_postcards_from_csv/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const parse = require('csv-parse');
const { parse } = require('csv-parse');
const fs = require('fs');
const pMap = require('p-map');
const { default: pMap } = require('p-map');

const lobFactory = require('../../lib/index.js');
const lob = new lobFactory('YOUR_API_KEY');
Expand Down
26 changes: 11 additions & 15 deletions examples/verify_and_create_letters_from_csv/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
const converter = require('json-2-csv');
const fs = require('fs');
const moment = require('moment');
const parse = require('csv-parse');
const { parse } = require('csv-parse');

const LobFactory = require('../../lib/index.js');
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.

const successFd = fs.openSync(`${__dirname}/success.csv`, 'w');
const errorFd = fs.openSync(`${__dirname}/error.csv`, 'w');
const letterTemplate = fs.readFileSync(`${__dirname}/letter_template.html`).toString();
Expand All @@ -23,7 +23,7 @@ const companyInfo = {
address_country: 'US'
};

const parser = parse({ columns: true }, (err, data) => {
parse(inputData, { columns: true }, (err, data) => {
if (err) {
return console.log(err);
}
Expand Down Expand Up @@ -68,24 +68,20 @@ const parser = parse({ columns: true }, (err, data) => {
console.log(`Successfully sent a letter to ${client.name}`);
client.letter_id = letter.id;
client.letter_url = letter.url;
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'))

.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);
});

})
.catch(() => {
console.log(`Could not send letter to ${client.name}`);
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'))

.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);
          });

});
});

});

inputFile.pipe(parser);
Loading
Loading