From 6c89342fe1f8bfe574be5afb25f3680a27cdc47a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:18:44 +0000 Subject: [PATCH 1/4] Initial plan From 9128f2018e67d71e78f0fc4ceed152bdb1940bfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:29:33 +0000 Subject: [PATCH 2/4] Update README to reflect current codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Node.js version prerequisite (16 → 18 to match package.json) - Fix NPM version badge alt text - Add Configuration section with client options and env vars - Modernize code examples (var → const) - Add update, list, AppRole auth, error handling, and custom commands docs - Fix bastion host example for axios (rpOptions → requestOptions) - Add TypeScript support mention - Remove outdated year reference and unused link references Co-authored-by: aviadhahami <7353632+aviadhahami@users.noreply.github.com> --- README.md | 203 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 153 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 44fe4dce..9243f6a9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://img.shields.io/github/checks-status/nodevault/node-vault/master.svg?style=flat-square)](https://github.com/nodevault/node-vault/actions?query=branch%3Amaster) [![Coverage Status](https://img.shields.io/codecov/c/github/nodevault/node-vault/master.svg?style=flat-square)](https://app.codecov.io/gh/nodevault/node-vault/tree/master) [![Download Status](https://img.shields.io/npm/dm/node-vault.svg?style=flat-square)](https://www.npmjs.com/package/node-vault) -[![test](https://img.shields.io/npm/v/node-vault?style=flat-square)](https://www.npmjs.com/package/node-vault) +[![NPM Version](https://img.shields.io/npm/v/node-vault?style=flat-square)](https://www.npmjs.com/package/node-vault) [![Dependency Status](https://img.shields.io/librariesio/release/npm/node-vault.svg?style=flat-square)](https://libraries.io/npm/node-vault/) [![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/node-vault?style=flat-square)](https://opencollective.com/node-vault/contribute) @@ -11,79 +11,182 @@ A client for the HTTP API of HashiCorp's [Vault] written for Node.js. ## Install + Prerequisites: - - NodeJS >= `16.0.0` + - Node.js >= `18.0.0` ```bash npm install -S node-vault ``` -> The year is 2023; If, for whatever reason, you need to use an older version of node.js (yet still `>= 6.x`), use `node-vault <= v0.10.0` -> -> Please note that `node-vault <= v0.10.0` contains multiple vulnerabilities ☠️ +> **Note:** If you need to use an older version of Node.js (>= 6.x), use `node-vault <= v0.10.0`. +> Please be aware that `node-vault <= v0.10.0` contains multiple known vulnerabilities ☠️ + +TypeScript definitions are included in the package. ## Test -Run tests using docker-compose (includes vault, postgres and running the tests inside) with: +Run tests using docker-compose (includes vault and postgres) with: ```bash docker-compose up --force-recreate test ``` +## Configuration + +### Client Options + +```javascript +const vault = require('node-vault')({ + apiVersion: 'v1', // API version (default: 'v1') + endpoint: 'http://127.0.0.1:8200', // Vault server URL (default: 'http://127.0.0.1:8200') + token: 'MY_TOKEN', // Vault token for authentication + pathPrefix: '', // Optional prefix for all request paths + namespace: 'my-namespace', // Vault Enterprise namespace + noCustomHTTPVerbs: false, // Use GET with ?list=1 instead of LIST HTTP method + requestOptions: {}, // Custom axios request options applied to all requests +}); +``` + +### Environment Variables + +The client reads the following environment variables as defaults: + +| Variable | Description | +| --- | --- | +| `VAULT_ADDR` | Vault server URL (overridden by `endpoint` option) | +| `VAULT_TOKEN` | Vault token (overridden by `token` option) | +| `VAULT_NAMESPACE` | Vault Enterprise namespace (overridden by `namespace` option) | +| `VAULT_PREFIX` | Request path prefix (overridden by `pathPrefix` option) | +| `VAULT_SKIP_VERIFY` | When set, disables SSL certificate verification | + + ## Usage ### Init and unseal ```javascript -var options = { - apiVersion: 'v1', // default - endpoint: 'http://127.0.0.1:8200', // default - token: 'MY_TOKEN' // optional client token; can be fetched after valid initialization of the server -}; - -// get new instance of the client -var vault = require("node-vault")(options); +const vault = require('node-vault')({ + apiVersion: 'v1', + endpoint: 'http://127.0.0.1:8200', + token: 'MY_TOKEN', // optional; can be set after initialization +}); // init vault server vault.init({ secret_shares: 1, secret_threshold: 1 }) -.then( (result) => { - var keys = result.keys; - // set token for all following requests - vault.token = result.root_token; - // unseal vault server - return vault.unseal({ secret_shares: 1, key: keys[0] }) -}) -.catch(console.error); + .then((result) => { + const keys = result.keys; + // set token for all following requests + vault.token = result.root_token; + // unseal vault server + return vault.unseal({ secret_shares: 1, key: keys[0] }); + }) + .catch(console.error); ``` -### Write, read and delete secrets +### Write, read, update and delete secrets ```javascript vault.write('secret/hello', { value: 'world', lease: '1s' }) -.then( () => vault.read('secret/hello')) -.then( () => vault.delete('secret/hello')) -.catch(console.error); + .then(() => vault.read('secret/hello')) + .then(() => vault.delete('secret/hello')) + .catch(console.error); +``` + +The `update` method sends a `PATCH` request with `application/merge-patch+json` content type: + +```javascript +vault.update('secret/data/hello', { data: { value: 'new-world' } }) + .catch(console.error); ``` + +### List secrets + +```javascript +vault.list('secret/metadata/') + .then((result) => console.log(result.data.keys)) + .catch(console.error); +``` + ### Kubernetes Auth Example + ```javascript +const fs = require('fs'); -//if vault kubernets endpoint is /auth/example-cluster/login and role is example-role -//read token from default token mount path -const token = await fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/token', { encoding: 'utf8' }); -vault.kubernetesLogin({role: 'example-role' , - jwt: token, - kubernetesPath: 'example-cluster'}) +// Read service account token from default mount path +const jwt = fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/token', { encoding: 'utf8' }); + +// If the Vault Kubernetes auth endpoint is /auth/example-cluster/login and the role is example-role +vault.kubernetesLogin({ + role: 'example-role', + jwt: jwt, + mount_point: 'example-cluster', +}).catch(console.error); +``` + +### AppRole Auth Example + +```javascript +const vault = require('node-vault')(); + +vault.approleLogin({ + role_id: 'my-role-id', + secret_id: 'my-secret-id', +}) + .then((result) => { + // client token is automatically set on successful login + console.log(result.auth.client_token); + }) + .catch(console.error); ``` +### Error Handling + +The client exposes two error types accessible from the module: + +- **`VaultError`** — Base error class for all vault-related errors. +- **`ApiResponseError`** — Thrown on non-200/204 responses. Contains a `response` property with `statusCode` and `body`. + +```javascript +vault.read('secret/missing') + .catch((err) => { + console.error(err.message); // Error message from Vault + if (err.response) { + console.error(err.response.statusCode); // e.g. 404 + console.error(err.response.body); // Response body from Vault + } + }); +``` + +### Custom Commands + +You can register custom API commands using `generateFunction`: + +```javascript +vault.generateFunction('myCustomEndpoint', { + method: 'GET', + path: '/my-custom/endpoint/{{id}}', +}); + +// Use the generated function +vault.myCustomEndpoint({ id: 'abc123' }) + .then(console.log) + .catch(console.error); +``` + + ## Docs -Just generate [docco] docs via `npm run docs`. +Generate [docco] docs via: +```bash +npm run docs +``` ## Examples -Please have a look at the [examples] and the generated [feature list] to see what is already implemented. +Please have a look at the [examples] and the generated [feature list] to see all supported Vault API endpoints. -Instead of installing all the dependencies like vault itself, postgres and other stuff you can -use [docker] and [docker-compose] to link and run multiple docker containers with all of its dependencies. +Instead of installing all the dependencies like vault itself and postgres, you can +use [docker] and [docker-compose] to link and run multiple docker containers with all of their dependencies. ```bash git clone git@github.com:nodevault/node-vault.git @@ -97,8 +200,8 @@ First of all you should initialize and unseal the vault: ```bash node example/init.js ``` -You should see `root_token: ` followed by a long key in the response. -Please copy that long key and export it as environment variable: +You should see `root_token:` followed by a long key in the response. +Please copy that long key and export it as an environment variable: ```bash export VAULT_TOKEN= ``` @@ -108,24 +211,25 @@ Now you are able to run all of the other [examples]: node example/policies.js ``` -## Connecting to vault through a bastion host +## Connecting to Vault Through a Bastion Host -To connect to a vault server in a private network with a bastion host, you'll need to first open a connection: +To connect to a vault server in a private network through a bastion host, first open a SOCKS proxy connection: ```bash -ssh -D bastion.example.com +ssh -D bastion.example.com ``` +Then configure the client with a SOCKS proxy agent: ```javascript -const SocksProxyAgent = require('socks-proxy-agent'); -const agent = new SocksProxyAgent(`socks://127.0.0.1:${socks4Port}`, true); -const options = { +const { SocksProxyAgent } = require('socks-proxy-agent'); +const agent = new SocksProxyAgent(`socks://127.0.0.1:${socksPort}`); + +const vault = require('node-vault')({ apiVersion: 'v1', - rpOptions: { - agent, + requestOptions: { + httpsAgent: agent, + httpAgent: agent, }, -}; - -const vault = require('node-vault')(options); +}); ``` [![Backers](https://opencollective.com/node-vault/tiers/backers.svg?avatarHeight=80&width=600)](https://opencollective.com/node-vault/contribute) @@ -135,6 +239,5 @@ const vault = require('node-vault')(options); [Vault]: https://vaultproject.io/ [docker-compose]: https://www.docker.com/docker-compose [docker]: http://docs.docker.com/ -[docker toolbox]: https://www.docker.com/toolbox [docco]: http://jashkenas.github.io/docco [feature list]: https://github.com/nodevault/node-vault/tree/master/features.md From ccc8ec042c74057a530eb0f37cc690534a2c44dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:57:24 +0000 Subject: [PATCH 3/4] Fix README badges and broken links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Build Status: use workflow-specific shield (github/actions/workflow/status) instead of generic checks-status, link directly to lint-and-test workflow - Remove Codecov badge: upload is commented out in CI, badge was stale - Add License badge (MIT) - Fix docker-compose link: old URL returns 404, now points to docs.docker.com/compose/ - Fix docker link: http → https Co-authored-by: aviadhahami <7353632+aviadhahami@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9243f6a9..b89ef289 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # node-vault -[![Build Status](https://img.shields.io/github/checks-status/nodevault/node-vault/master.svg?style=flat-square)](https://github.com/nodevault/node-vault/actions?query=branch%3Amaster) -[![Coverage Status](https://img.shields.io/codecov/c/github/nodevault/node-vault/master.svg?style=flat-square)](https://app.codecov.io/gh/nodevault/node-vault/tree/master) +[![Build Status](https://img.shields.io/github/actions/workflow/status/nodevault/node-vault/lint-and-test.yaml?branch=master&style=flat-square)](https://github.com/nodevault/node-vault/actions/workflows/lint-and-test.yaml) [![Download Status](https://img.shields.io/npm/dm/node-vault.svg?style=flat-square)](https://www.npmjs.com/package/node-vault) [![NPM Version](https://img.shields.io/npm/v/node-vault?style=flat-square)](https://www.npmjs.com/package/node-vault) +[![License](https://img.shields.io/npm/l/node-vault?style=flat-square)](https://github.com/nodevault/node-vault/blob/master/LICENSE) [![Dependency Status](https://img.shields.io/librariesio/release/npm/node-vault.svg?style=flat-square)](https://libraries.io/npm/node-vault/) [![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/node-vault?style=flat-square)](https://opencollective.com/node-vault/contribute) @@ -237,7 +237,7 @@ const vault = require('node-vault')({ [examples]: https://github.com/nodevault/node-vault/tree/master/example [docker-compose.yml]: https://github.com/nodevault/node-vault/tree/master/docker-compose.yml [Vault]: https://vaultproject.io/ -[docker-compose]: https://www.docker.com/docker-compose -[docker]: http://docs.docker.com/ +[docker-compose]: https://docs.docker.com/compose/ +[docker]: https://docs.docker.com/ [docco]: http://jashkenas.github.io/docco [feature list]: https://github.com/nodevault/node-vault/tree/master/features.md From 00a68d539c47d4f995e7201430a63f687e521da5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:34:38 +0000 Subject: [PATCH 4/4] Merge origin/master into copilot/update-readme-content Resolve README.md conflict in bastion host section by keeping the modernized destructured SocksProxyAgent import with axios-style agent config. Incorporate master's new Custom SSL/TLS Configuration section, pass_request_options example updates, requestOptions forwarding tests, and version bump to 0.10.10. Co-authored-by: aviadhahami <7353632+aviadhahami@users.noreply.github.com> --- README.md | 36 +++++++++ example/pass_request_options.js | 24 ++++-- package.json | 2 +- test/unit.js | 136 ++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b89ef289..608dcae6 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,42 @@ const vault = require('node-vault')({ }); ``` +## Custom SSL/TLS Configuration + +If you encounter SSL errors after upgrading to Node 18+ (e.g., `EPROTO` errors related to +`unsafe legacy renegotiation disabled`), you can pass SSL/TLS options via `requestOptions` +or `rpDefaults` when initializing the client: + +```javascript +const vault = require('node-vault')({ + apiVersion: 'v1', + endpoint: 'https://vault.example.com:8200', + token: 'MY_TOKEN', + requestOptions: { + agentOptions: { + securityOptions: 'SSL_OP_LEGACY_SERVER_CONNECT', + }, + }, +}); +``` + +The `requestOptions` object is passed through to the underlying HTTP library +([postman-request](https://www.npmjs.com/package/postman-request)) for every request. You can +use it to configure any supported request option, including `agentOptions`, custom `headers`, +or a custom `agent`. + +You can also pass request options per-call to any method: + +```javascript +vault.read('secret/hello', { + agentOptions: { + securityOptions: 'SSL_OP_LEGACY_SERVER_CONNECT', + }, +}); +``` + +See [example/pass_request_options.js](example/pass_request_options.js) for more examples. + [![Backers](https://opencollective.com/node-vault/tiers/backers.svg?avatarHeight=80&width=600)](https://opencollective.com/node-vault/contribute) [examples]: https://github.com/nodevault/node-vault/tree/master/example diff --git a/example/pass_request_options.js b/example/pass_request_options.js index 2c81a248..c48b1a12 100644 --- a/example/pass_request_options.js +++ b/example/pass_request_options.js @@ -2,20 +2,32 @@ process.env.DEBUG = 'node-vault'; // switch on debug mode -const vault = require('./../src/index')(); +// Pass request options at initialization time. +// These options are forwarded to postman-request for every request. +const vault = require('./../src/index')({ + requestOptions: { + agentOptions: { + cert: '', + key: '', + passphrase: '', + securityOptions: 'SSL_OP_NO_SSLv3', + }, + }, +}); -const options = { +// You can also pass (or override) request options per-call. +const perCallOptions = { headers: { 'X-HELLO': 'world', }, agentOptions: { - cert: 'mycert', - key: 'mykey', - passphrase: 'password', + cert: '', + key: '', + passphrase: '', securityOptions: 'SSL_OP_NO_SSLv3', }, }; -vault.help('sys/policy', options) +vault.help('sys/policy', perCallOptions) .then(() => vault.help('sys/mounts')) .catch((err) => console.error(err.message)); diff --git a/package.json b/package.json index 6e26253a..ddd873c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-vault", - "version": "0.10.9", + "version": "0.10.10", "description": "Javascript client for HashiCorp's Vault", "main": "./src/index.js", "scripts": { diff --git a/test/unit.js b/test/unit.js index 2ac9525b..1ba67384 100644 --- a/test/unit.js +++ b/test/unit.js @@ -295,6 +295,142 @@ describe('node-vault', () => { }); }); + describe('config.requestOptions forwarding', () => { + let requestWithOpts = null; + let vaultWithOpts = null; + + const agentOpts = { + securityOptions: 'SSL_OP_LEGACY_SERVER_CONNECT', + }; + + function getURIWithOpts(path) { + return [vaultWithOpts.endpoint, vaultWithOpts.apiVersion, path].join('/'); + } + + function assertRequestWithOpts(thisRequest, params, done) { + return () => { + thisRequest.should.have.calledOnce(); + thisRequest.calledWithMatch(params).should.be.ok(); + return done(); + }; + } + + beforeEach(() => { + requestWithOpts = sinon.stub(); + const resp = sinon.stub(); + resp.statusCode = 200; + + requestWithOpts.returns({ + then(fn) { + return fn(resp); + }, + catch(fn) { + return fn(); + }, + }); + + vaultWithOpts = index({ + endpoint: 'http://localhost:8200', + token: '123', + 'request-promise': { + defaults: () => requestWithOpts, + }, + requestOptions: { + agentOptions: agentOpts, + }, + }); + }); + + it('should forward agentOptions from config.requestOptions in help()', (done) => { + const path = 'sys/policy'; + const params = { + method: 'GET', + uri: `${getURIWithOpts(path)}?help=1`, + agentOptions: agentOpts, + }; + vaultWithOpts.help(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in read()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'GET', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.read(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in write()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'POST', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.write(path, { value: 'world' }) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in delete()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'DELETE', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.delete(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in list()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'LIST', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.list(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in generated functions', (done) => { + const name = 'myTestFunction'; + vaultWithOpts.generateFunction(name, { + method: 'GET', + path: '/myroute', + }); + const params = { + agentOptions: agentOpts, + }; + vaultWithOpts[name]() + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should allow per-call requestOptions to override config.requestOptions', (done) => { + const path = 'secret/hello'; + const overrideOpts = { + securityOptions: 'SSL_OP_NO_SSLv3', + }; + const params = { + method: 'GET', + uri: getURIWithOpts(path), + agentOptions: overrideOpts, + }; + vaultWithOpts.read(path, { agentOptions: overrideOpts }) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + }); + describe('unwrap(options)', () => { it('should return original response', (done) => { const path = 'sys/wrapping/unwrap';