mrkeyoor.com_
Wed 23 Sept 02:53 UTC
PyPISecurityupdated 21 Sept 2026

hvac review

hvac is a synchronous Python interface to HashiCorp Vault. Its client exposes authentication backends, KV secrets, Transit encryption, PKI, dynamic database credentials, identity, policies, response wrapping, Raft administration, and lower-level read/write calls. The dictionaries it returns closely follow Vault's HTTP responses, including lease and token metadata. Version 2.4.0 adds `audience` to Kubernetes role creation for Vault 1.21 and later. It is a good fit when Python code must operate Vault directly, but it does not replace Vault Agent or manage credential renewal for you.

Verdict

hvac 2.4.0 installed in 0.7 seconds, occupied 4 MB across 6 packages, and showed 0 audit findings in our Python 3.12 sandbox. Use it when a synchronous Python service genuinely owns Vault API calls; choose Agent or platform injection when the process only needs delivered secrets.

We installed it

Lab card: what happened when we installed hvacScreenshot of hvac documentation
Install✓ · 0.7s6 packages on disk · 4 MB
Importimport hvac in 0.41s · pure Python · requires Python >=3.8,<4.0
Known vulns0(pip-audit)

Answers from our run

Does hvac install cleanly?

Yes. In a fresh container with an empty cache, pip install hvac finished in 0.7s, leaving 6 packages and 4 MB on disk. pip-audit reported no known vulnerabilities.

What does hvac need to run?

Python >=3.8,<4.0, and nothing compiled: it is pure Python. In our run import hvac succeeded in 0.41s.

hvac or requests: which should you use?

requests: Use direct HTTP for one stable Vault route when owning URLs, headers, errors, and response parsing is acceptable. hvac 2.4.0 installed in 0.7 seconds, occupied 4 MB across 6 packages, and showed 0 audit findings in our Python 3.12 sandbox.

When should you not use hvac?

Vault Agent, a CSI mount, or platform injection can place the needed secret beside the process. Those options remove login and renewal code from the application.

API stability4/5The 2.x layout keeps auth methods under `client.auth`, secret engines under `client.secrets`, and administration under `client.sys`; responses continue to expose Vault's own nested dictionaries. Release 2.4.0 only adds an optional Kubernetes role field. The changelog also records two planned 3.0 changes around deleted KV versions and PKI certificate input, so callers should remove reliance on those defaults before upgrading.
Docs5/5The official site covers client creation, authentication backends, each supported secret engine, TLS certificates, custom adapters, exceptions, retries, and the full source reference. Its KV pages show the v2 nesting and mount argument, while advanced usage explains the unusual retry setting. Vault ACLs, server mounts, and token lifecycle still require the Vault server documentation, which is an appropriate split rather than missing Python API material.
Maintenance3/5Version 2.4.0 reached PyPI on October 30, 2025, adding the Kubernetes audience field required by newer Vault releases. GitHub lists 1,315 stars, an unarchived repository, 182 open issues and pull requests in its combined count, and a latest push on January 6, 2026. The repository README says CI covers the latest Vault release, its HEAD revision, and 3 prior minors, but the quiet period makes testing against your exact server upgrade worthwhile.
Ecosystem4/5The supplied registry count is 6,815,601 weekly downloads. hvac includes clients for the common auth backends, KV, Transit, PKI, database secrets, identity, policies, wrapping, audit devices, and Raft operations. requests also gives teams familiar proxy, CA, certificate, and adapter controls. The boundary is narrower for async services and polyglot platforms, where Vault Agent or secret injection can serve more processes without language-specific client code.

Use it if

  • A synchronous Python worker needs named methods across several Vault auth, secret-engine, and system endpoints.
  • You need KV v2 version metadata, check-and-set writes, Transit operations, leased credentials, or response wrapping.
  • The client must use an Enterprise namespace, private CA, client certificate, proxy, or custom requests session.
  • Application code should see Vault's lease fields and typed hvac exceptions instead of a secret cache hiding them.
Skip it if

Setup reality

We installed hvac 2.4.0 in an empty Python 3.12 Bookworm sandbox. The operation took 0.7 seconds and produced 6 installed packages using 4 MB. hvac declares 2 direct dependencies, is pure Python, and accepts Python 3.8 through any release below 4.0. import hvac succeeded in 0.41 seconds. pip-audit returned 0 known vulnerabilities. The distribution has no py.typed marker and uses the Apache-2.0 license.

Before the first request, supply a Vault URL, trusted CA material, and an authentication method. Set verify to the internal CA file rather than turning certificate checks off. Mutual TLS also needs the client certificate and private key. Kubernetes auth, workload identity, AppRole, or an Agent-delivered token is preferable to a long-lived privileged token in source or a config file. Vault Enterprise requests need the namespace value as well.

KV version mismatch causes many first failures. A dev server commonly exposes KV v2 at secret/, but production can use another mount or KV v1. The v2 payload is nested at response['data']['data'], and policies refer to versioned API paths. Pass mount_point whenever the mount is customized. Version 2.4.0 still defaults raise_on_deleted_version to true; set it explicitly because the changelog says version 3 will flip that default.

Every call goes through requests and blocks. Configure a timeout. For urllib3 Retry, the hvac docs require raise_on_status=False so hvac gets the response and maps it to the right exception, including Vault's 412 status. Retrying writes can duplicate work. Login normally updates the client token, but renewable tokens and dynamic leases still need a timer, an expiry path, and fresh authentication when renewal stops.

Patterns

Connect through a private CA create-tls-client

import os
import hvac

client = hvac.Client(
    url=os.environ['VAULT_ADDR'],
    token=os.environ.get('VAULT_TOKEN'),
    verify=os.environ['VAULT_CACERT'],
    namespace=os.environ.get('VAULT_NAMESPACE'),
    timeout=10,
)

if not client.is_authenticated():
    raise RuntimeError('Vault login is not valid')

A CA bundle keeps server verification active. Setting verify=False can expose both the token and returned secret data.

Authenticate with AppRole login-with-approle

result = client.auth.approle.login(
    role_id=role_id,
    secret_id=secret_id,
    mount_point='approle',
)
lease_seconds = result['auth']['lease_duration']

login stores the issued token on this client unless token use is disabled. Match mount_point to the server's auth mount.

Set the Kubernetes token audience define-kubernetes-role

client.auth.kubernetes.create_role(
    name='orders',
    bound_service_account_names=['orders-api'],
    bound_service_account_namespaces=['production'],
    policies=['orders-read'],
    audience='vault',
)

The audience argument arrived in hvac 2.4.0 and is required by Vault 1.21 or newer for this role flow.

Use a projected service-account token login-from-pod

from pathlib import Path

token_path = Path('/var/run/secrets/kubernetes.io/serviceaccount/token')
jwt = token_path.read_text().strip()
client.auth.kubernetes.login(role='orders', jwt=jwt)

Projected Kubernetes tokens rotate, so read the file again before a later login instead of retaining the first contents forever.

Read data and metadata from KV v2 read-kv2

result = client.secrets.kv.v2.read_secret_version(
    path='services/orders',
    mount_point='secret',
    raise_on_deleted_version=False,
)
value = result['data']['data']['api_key']
version = result['data']['metadata']['version']

KV v2 wraps values inside two data keys. State deleted-version behavior now because its default is scheduled to change in hvac 3.

Reject a stale KV v2 update write-kv2-cas

client.secrets.kv.v2.create_or_update_secret(
    path='services/orders',
    secret={'api_key': replacement},
    cas=version,
    mount_point='secret',
)

cas must equal the current version. A value of 0 only succeeds for a path that has never been written.

Read from a KV v1 mount read-kv1

result = client.secrets.kv.v1.read_secret(
    path='services/orders',
    mount_point='legacy',
)
value = result['data']['api_key']

KV v1 has one data layer and no version metadata. Do not send this call to a KV v2 mount.

Encrypt a value with Transit encrypt-with-transit

import base64

encoded = base64.b64encode(b'customer-42').decode('ascii')
result = client.secrets.transit.encrypt_data(
    name='orders-key',
    plaintext=encoded,
)
ciphertext = result['data']['ciphertext']

Transit accepts base64 plaintext and returns a Vault ciphertext; the engine does not retain the plaintext value.

Create a single-use handoff token wrap-handoff

wrapped = client.sys.wrap(
    payload={'bootstrap_token': bootstrap_token},
    ttl='60s',
)
wrapping_token = wrapped['wrap_info']['token']

Only send the wrapping token across the handoff channel, and consume it before the 60-second TTL expires.

Consume a wrapped payload unwrap-handoff

result = client.sys.unwrap(token=wrapping_token)
bootstrap_token = result['data']['bootstrap_token']

A wrapping token works once. A failed second unwrap can mean the value was already consumed.

Renew the client's token renew-own-token

details = client.auth.token.lookup_self()['data']
if details['renewable']:
    result = client.auth.token.renew_self(increment='1h')
    ttl = result['auth']['lease_duration']

Renewal cannot exceed the token's maximum lifetime. Code still needs a route back to the original login method.

Retry selected response codes configure-vault-retries

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

policy = Retry(
    total=3,
    backoff_factor=0.2,
    status_forcelist=[412, 500, 502, 503],
    raise_on_status=False,
)
session = Session()
session.mount('https://', HTTPAdapter(max_retries=policy))
client = hvac.Client(url=vault_addr, verify=ca_file, session=session)

hvac needs raise_on_status=False to translate final HTTP responses into its exceptions. Review each write before allowing automatic retries.

Alternatives

PackageRegistryPick it when
requestsPyPIUse direct HTTP for one stable Vault route when owning URLs, headers, errors, and response parsing is acceptable.
vault-cliPyPIUse it for shell-oriented operator work rather than a client embedded in an application.
ansiblePyPIUse its Vault-related collections when secrets are part of provisioning or deployment playbooks instead of runtime Python code.

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.