Self-hosted service
nodevault/node-vault avatar
nodevault/node-vault

node-vault: a JavaScript client for the HashiCorp Vault HTTP API

Client for HashiCorp's Vault

557 stars160 forksJavaScriptMIT

At a glance

What is it?
node-vault is a Node.js client for Vault's HTTP API, covering secret CRUD, listing, token and auth methods including Kubernetes and AppRole, unsealing, leases, transit and custom endpoints, with TypeScript definitions and environment-variable defaults. It suits a Node service that needs to read and write Vault secrets, and it is a client for Vault rather than a replacement for it.
Who is it for?
node-vault fits a Node.js service that talks to an existing Vault, since it covers the everyday surface, writing and reading secrets, listing, Kubernetes and AppRole login, unsealing and custom endpoints, and it reads the standard VAULT_ADDR and VAULT_TOKEN environment variables so a containerised service needs almost no configuration.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A thin client over Vault's HTTP API

node-vault is described as a client for the HTTP API of HashiCorp's Vault written for Node.js. The homepage points at vaultproject.io, and the topics list api, api-client, hashicorp, node, nodejs, secrets and vault.

The word client matters. This library does not store secrets, does not manage keys and does not give you a secret store. It is a wrapper over the HTTP endpoints Vault already exposes, and it exists so that a Node application can talk to Vault with promises and a consistent shape rather than with hand-written axios calls.

The implementation reflects that. The package main is ./src/index.js and the published files are src and index.d.ts, so the TypeScript definitions ship with the package. The runtime dependencies are four: axios for the HTTP layer, debug for logging, mustache for template rendering, and tv4, a JSON schema validator. That last one is the interesting dependency, because it means the client validates Vault's responses against a schema rather than handing you whatever came back.

The project is MIT licensed, requires Node.js 18 or newer, and the released versions are 0.13.0 from 2026-10-11, 0.12.0 from 2026-03-21 and 0.11.1 from 2026-03-12.

The repository is set up for contributors as well as consumers. There is a .devcontainer/ directory, a Dockerfile, a docker-compose.yml, an example/ directory with more than twenty scripts covering each area of the API, and a generated features.md that lists the supported Vault API endpoints. The CI workflow is named lint-and-test.yaml.

The build tooling is modest: mocha for tests with nyc for coverage, eslint for linting, docco for the documentation, and sinon with sinon-chai for stubbing.

Configuration, and the environment variables that override it

The client is created by calling the module with an options object, and every option has a default:

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

The environment variables provide defaults that the options override: VAULT_ADDR for the server URL, VAULT_TOKEN for the token, VAULT_NAMESPACE for a Vault Enterprise namespace, VAULT_PREFIX for the request path prefix, and VAULT_SKIP_VERIFY, which when set disables SSL certificate verification.

That last one deserves a note. Turning off certificate verification is a legitimate need when pointing at a development Vault with a self-signed certificate, and it is also the kind of setting that ends up in a production environment variable file by accident. VAULT_SKIP_VERIFY is set by presence rather than by value, which means there is no way to have it present and disabled.

The noCustomHTTPVerbs option is a compatibility switch worth understanding. Vault supports LIST as an HTTP method, but some proxies and load balancers in front of Vault do not pass it through, so the option switches to using GET with a ?list=1 query parameter instead. If you are calling list against a Vault behind a corporate proxy and it fails in a way that reads oddly, this is the setting to reach for.

There is also a small robustness detail: trailing slashes in the endpoint URL are stripped automatically to prevent malformed request URIs.

The requestOptions option applies custom axios request options to all requests, which is how you would set a timeout, a proxy agent or an SSL configuration without reimplementing the client.

Secrets, listing, updates and the custom endpoint escape hatch

The everyday operations are the promise-returning methods you would expect, and the readme shows them in combination.

Writing, reading and deleting a secret chains three calls:

javascript
vault.write('secret/hello', { value: 'world', lease: '1s' })
  .then(() => vault.read('secret/hello'))
  .then(() => vault.delete('secret/hello'))
  .catch(console.error);

Listing works on a metadata path and returns the keys:

javascript
vault.list('secret/metadata/')
  .then((result) => console.log(result.data.keys))
  .catch(console.error);

Note that the list path is secret/metadata/ rather than secret/. That is how Vault's KV v2 engine separates the path from the metadata about that path, and getting it wrong is a common first mistake.

The update method is distinct from write and the readme says exactly what it sends: a PATCH request with an application/merge-patch+json content type.

javascript
vault.update('secret/data/hello', { data: { value: 'new-world' } })
  .catch(console.error);

For endpoints the library does not model, generateFunction registers a custom command from a method and a path template, and the path can carry a placeholder:

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

This is the mechanism that keeps the library useful as Vault's API grows, and it is worth knowing about even if you never use it, because it explains how the generated features.md list can stay current.

Errors are typed. The module exposes VaultError as the base class for all vault-related errors and ApiResponseError, which is thrown on non-200 or 204 responses and carries 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
    }
  });

Authentication, unsealing and the Kubernetes case

The client supports the auth methods a Node service is most likely to use, and the two documented examples are the ones worth knowing.

AppRole is the machine-to-machine pattern. The client is created with no token, and the login sets the client token automatically on success:

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

Kubernetes auth is the other one, and the example shows the pattern that matters in a cluster, reading the projected service account token from its default mount path:

javascript
const fs = require('fs');

// 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);

The example/ directory shows the same for GitHub, LDAP, Okta and userpass, plus examples for audits, leases, policies, secret mounts, token handling, transit, status, health and PostgreSQL mounts, so the breadth of the API surface is visible from the tree.

Unsealing is treated as a first-class operation, which is unusual for a client library and sensible given how Vault works. A server that has been restarted or sealed is unsealed with the keys from the original initialisation, and if the vault was initialised with a secret_threshold above one you must call unseal multiple times with different keys until the threshold is met:

javascript
vault.unseal({ key: 'first-unseal-key' })
  .then((result) => {
    // result.sealed will be true until enough keys are provided
    console.log('Sealed:', result.sealed);
    console.log('Progress:', result.progress + '/' + result.t);
    return vault.unseal({ key: 'second-unseal-key' })
  })
  .then((result) => {
    // once the threshold is met, sealed will be false
    console.log('Sealed:', result.sealed);
  })
  .catch(console.error);

The init and unseal sequence is also documented as a single flow, where the token is set on the client after initialisation so that all following requests are authenticated.

One thing to note for security review: node-vault has no opinion about where the token comes from, and the readme's init example takes the root token from the init result and assigns it to vault.token. That is correct for a setup script and wrong for a long-running service, which is where the Kubernetes and AppRole examples come in.

Running the test suite against a real Vault

The tests run against real services rather than mocks, and the repository ships the compose file to provide them.

bash
docker-compose up --force-recreate test

The compose file defines a vault service running the vault:1.13.3 image with IPC_LOCK added, port 8200 published, and a configuration mounted from example/config.hcl, plus a postgres service on postgres:15.3 with its port mapped to 5433 and root credentials of test. Both sit on a bridge network named vault-network.

Two details are worth noting. IPC_LOCK is the capability Vault needs for mlock, which prevents secrets being written to swap, and it has to be granted explicitly by Docker. And the PostgreSQL service exists because Vault needs a storage backend, so even a test Vault is not a standalone process.

The Dockerfile is a development convenience rather than a production image. It starts from debian:bookworm-slim, installs curl, then installs nvm from a pinned script and a Node version defaulting to 18.20.8 that can be overridden with a build argument, copies the project in and runs npm install. The entrypoint keeps a login shell rather than invoking node directly.

The documentation is generated with docco from the source, and the predocs script first runs scripts/write-features.js to regenerate the feature list that the readme links to. So features.md is derived from the code rather than maintained by hand, which is the right arrangement for a document that claims to list every supported endpoint.

Running the tests this way means the suite exercises the actual Vault HTTP API, including the parts that are hard to fake, such as unseal thresholds and lease handling.

Where node-vault is the wrong choice

The first limitation is that it is a client and not a service. Your application still needs a Vault to talk to, which means a Vault deployment with its own storage backend, its own seal and unseal procedure, and its own upgrade path. For a small application that wants a place to keep an API key, running a Vault cluster is a large commitment, and there are managed secret services that do not require one.

The second is the Node version floor. The client requires Node.js 18 or newer, and the readme is explicit about the alternative: if you need an older version of Node, at 6.x or above, use node-vault at v0.10.0 or below, and it warns that those versions contain multiple known vulnerabilities. So the fallback for an old runtime is a version with known security problems, which is not a fallback.

The third is the token handling, which the library leaves entirely to you. The client reads VAULT_TOKEN from the environment and accepts a token in the options or assigns one after a login. That is a deliberate thin-client choice, and it means the security of your deployment depends on how you obtain and store the token. A long-running Node service that reads a static token from an environment variable is a long-running Node service with a static credential, and the Kubernetes and AppRole examples exist precisely so you can do better.

The fourth is the loose coupling to Vault's HTTP surface. The library validates responses with tv4 against schemas, which catches malformed responses, but it cannot protect you from a Vault version whose API has shifted underneath you. When Vault changes an endpoint or an auth method's response shape, the client needs an update, and the version cadence here, three releases in 2026 with the latest at 0.13.0, suggests that is handled but not instantaneous.

The fifth is that the axios dependency is exposed to you. requestOptions is passed straight through, which is exactly what you want for a proxy or a timeout, and it is also exactly the sort of hook where a stray setting can disable certificate verification or redirect requests somewhere unexpected. Combined with the VAULT_SKIP_VERIFY switch, there are two independent ways to turn off TLS checking, and a reviewer will want to see that neither is set in production.

Editorial conclusion

node-vault fits a Node.js service that talks to an existing Vault, since it covers the everyday surface, writing and reading secrets, listing, Kubernetes and AppRole login, unsealing and custom endpoints, and it reads the standard VAULT_ADDR and VAULT_TOKEN environment variables so a containerised service needs almost no configuration. It is the wrong choice if you want a managed secret store, since Vault itself is the service you still have to run, and it is the wrong choice if you need Node 6 support, because the readme says older Node needs node-vault at v0.10.0 or below, which carries known vulnerabilities. Before adopting it, decide how your application obtains its token, since the token can also be set at runtime after initialisation, and note that the last push was on 2026-10-11 with 0.13.0 released the same day under an MIT licence.

Frequently asked questions

How do I install node-vault?

Run npm install -S node-vault. The client requires Node.js 18 or newer, and TypeScript definitions are included in the package. The readme notes that older Node versions need node-vault at v0.10.0 or below, which contain multiple known vulnerabilities.

Which environment variables does node-vault read?

VAULT_ADDR for the server URL, VAULT_TOKEN for the authentication token, VAULT_NAMESPACE for a Vault Enterprise namespace, VAULT_PREFIX for a request path prefix, and VAULT_SKIP_VERIFY, which when present disables SSL certificate verification. Each is overridden by the corresponding client option.

How do I authenticate node-vault with Kubernetes?

Read the service account token from /var/run/secrets/kubernetes.io/serviceaccount/token and pass it to vault.kubernetesLogin with the role and the mount_point of the Vault Kubernetes auth endpoint. The example/ directory also covers AppRole, GitHub, LDAP, Okta and userpass logins.

How does node-vault handle listing and updating secrets?

List takes a metadata path such as secret/metadata/ and returns the keys, while update sends a PATCH request with an application/merge-patch+json content type, for example vault.update('secret/data/hello', { data: { value: 'new-world' } }).

How do I run the node-vault tests?

Use docker-compose up --force-recreate test. The compose file starts a Vault 1.13.3 container with the IPC_LOCK capability on port 8200, configured from example/config.hcl, together with a PostgreSQL 15.3 service, so the suite runs against real Vault endpoints.

What licence is node-vault released under?

MIT, with the licence file at the root of the repository. The last push was on 2026-10-11 and the newest release is 0.13.0 from the same day, following 0.12.0 in March 2026 and 0.11.1 earlier that month.

Official sources

  1. License: MIT
  2. nodevault/node-vault on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nodevault-node-vault.svg)](https://hysenlabs.com/projects/nodevault-node-vault)

Was this useful?