@achs/azure-key-vault

中文文档
5.0.1发布于 2026/10/07TypeScriptES Module近 30 天下载 5767

Wrapper for @azure/keyvault-secrets for ease secrets handler in JSON files.

原始使用文档
文档目录16 节
  1. How To Use 💡
  2. Options
  3. Secret names
  4. Tags
  5. Logging and traceability 🔎
  6. Where the logs go
  7. Failures and strict
  8. check()
  9. Network: DNS check, VPN and proxies
  10. Functions 🧰
  11. Audit
  12. Testing with the mock
  13. Command 💻
  14. Development 🧿
  15. Changelog 📄
  16. Built with 🛠️

Azure Key Vault

Node library to handle Azure Key Vault secrets by project, environment and group (when a vault is shared), as nested JSON. Every failure is classified and logged at its level, and the library says who it signed in as, so a missing vault, an identity without access and a missing secret never look the same.

ESM only, Node.js 22.12 or later.

How To Use 💡

import { AzureKeyVault } from '@achs/azure-key-vault';

// DefaultAzureCredential: environment variables, workload or
// managed identity, Azure CLI ("az login")...
const keyVault = new AzureKeyVault({
    vaultUrl: 'https://my-key-vault.vault.azure.net',
    project: 'my-project',
    group: 'web',
    env: 'dev',
});

await keyVault.setAll({
    SECRET1: 'my secret 1',
    otherConfig: { SECRET2: 'my secret 2' },
});

const secrets = await keyVault.getFor({
    SECRET1: null,
    otherConfig: { SECRET2: null },
    SECRET3: 'default for secret 3',
});
// { SECRET1: 'my secret 1', otherConfig: { SECRET2: 'my secret 2' }, SECRET3: 'default for secret 3' }
// typed with the shape of the input: secrets.otherConfig.SECRET2

Options

OptionDescription
vaultUrl(string) vault URL, i.e. https://my-key-vault.vault.azure.net
project(string) project the secrets belong to
group(string) secrets group
env(string) environment (dev, qa, prod...)
credential(TokenCredential) any @azure/identity credential, wins over credentials
credentials(object) service principal { clientId, clientSecret, tenantId }: with clientSecret, all three; without it, DefaultAzureCredential takes tenantId and clientId (workload or managed identity) as hints
logger(object) logger with trace, debug, info, warn and error (console, tslog...)
logLevel(string) lowest level written: trace, debug, info, warn, error or silent
strict(boolean, default false) a vault or access failure throws instead of being logged only
concurrency(number, default 8) requests at once in getFor, getAll and the *All functions
timeoutMs(number) time limit of each operation, retries included
dnsCheck(boolean, default true) resolve the vault host before the first request (see below)
expiryWarningDays(number, default 30) warn about secrets expiring within these days; 0 turns it off
clientOptions(SecretClientOptions) options of the secrets client: retries, proxy, user agent...
client(SecretClient) secrets client, for tests

A service principal with only some of its values throws a TypeError, naming the missing ones, and so do invalid concurrency, timeoutMs or expiryWarningDays values. The library never writes process.env.

Secret names

A key maps to the secret project-group-env-key, lowercase, with _ and spaces turned into - (toSecretName(scope, key) gives it).

  • : (or --) nests a key: car:props:name is { car: { props: { name } } }.
  • A last segment starting with $ is shared by every group (app) of the project, in the same environment: parent:$global_var maps to project-env-parent--global-var. It is not shared with other projects. Without a group, KEY and $KEY map to the same secret.
  • A key that does not map to a valid secret name (letters, digits and dashes, up to 127) fails before any request; a scoped package name as group (@scope/name) does too, so set AZURE_GROUP.
  • The scope (project, group, environment) compares like names do, whatever case or spaces it was written with (normalizeScope): My App and my-app are one group, and tags are written normalized.

Tags

Every secret carries tags that rebuild its key and scope (project, group, env, name, path), plus:

TagValue
managed-by@achs/azure-key-vault/<version> that wrote it
updated-bywho wrote it: a user's upn, service-principal:<appId>, managed-identity:<name>
serialized1 for a JSON value (kept for versions before contentType)

A JSON value (anything but a string) also has contentType: application/json, and is parsed when either says so. updated-by comes from the identity the library signed in with (it asks for a token before its first write); with an injected client it is left out.

Logging and traceability 🔎

Every failure is classified (AzureKeyVaultErrorKind) and logged at its level, naming the vault and the secrets it affects. Values are never logged, only secret names.

KindMeaningLevel
vault-not-foundthe vault host does not resolve (wrong URL, or the DNS/VPN that has it)error
vault-unreachableno answer from the vault (network, proxy, TLS)error
timeoutan operation took longer than timeoutMserror
unauthenticatedno usable identity (not signed in, wrong service principal, wrong tenant)error
forbiddenthe identity lacks the permission, or the vault firewall rejects iterror
throttledthe vault throttles requests (429) and retries are exhaustederror
invalid-secret-namea key that does not map to a valid secret nameerror
invalid-valuea secret tagged as JSON whose value does not parse, or an undefined valueerror
unknownanything elseerror
secret-not-foundthe secret does not existwarn
secret-disabledthe secret is disabledwarn

What each level shows:

  • error / warn: one line per cause, with every secret it affects. A forbidden line names the identity the vault saw (appid/oid), the permission and role the operation needs (Key Vault Secrets User to read and list, Key Vault Secrets Officer to write) and who the library signed in as.
  • warn also: secrets read that are expired, not valid yet (Key Vault gives them anyway) or about to expire (expiryWarningDays).
  • info: who the library signed in as (authenticated as user [email protected] (tenant ...), from the first token: user, service principal or managed identity), the summary of every operation over many secrets (read 3 of 5 secrets, 1 not found or disabled, 1 failed), and the result of check().
  • debug: each secret read, written, deleted, purged or restored.
  • trace: the original Azure SDK error of each cause.

keyVault.identity gives the identity once known (only when the library builds the client).

Where the logs go

  • An injected logger receives every level and filters itself, so a host application keeps one log level for everything. @achs/env passes its own tslog logger this way. Set logLevel to filter it here too.
  • Without a logger, the library writes to the console with a [akv] prefix, from logLevel, else the AKV_LOG_LEVEL environment variable, else warn.
const keyVault = new AzureKeyVault({
    vaultUrl,
    project: 'my-project',
    env: 'dev',
    logger: parentLogger.getSubLogger({ name: 'akv' }),
});

Failures and strict

  • Reads and maintenance (get, getFor, getAll, keys, versions, delete, purge, restore and the *All functions) log their failures and go on: a value that could not be read keeps its default (or null). With strict: true, an error level failure throws an AzureKeyVaultError once everything is logged; a missing or disabled secret only warns.
  • Writes (set, setAll, rollback) and getInfo always throw an AzureKeyVaultError.
import { AzureKeyVaultError } from '@achs/azure-key-vault';

try {
    await keyVault.getFor(schema);
} catch (error) {
    if (error instanceof AzureKeyVaultError)
        console.error(error.kind, error.vaultUrl, error.secretNames);
}

classifyError(error) gives the kind of any error the Azure SDK throws.

check()

Diagnoses a vault with a single request for a secret that should not exist: it passes when the vault host resolves and answers, the identity authenticates and may read secrets, and it says who signed in. It never throws.

const { ok, kind, message, identity } = await keyVault.check();
// { ok: false, kind: 'forbidden', identity: { type: 'service-principal', appId: '...' }, message: 'https://...: the identity appid=... lacks the "get" secret permission (...), signed in as service principal appid=...' }

Network: DNS check, VPN and proxies

Before its first request, the library resolves the vault host once with the system DNS, the one every request uses (a VPN's DNS included): a host that does not resolve fails at once as vault-not-found, instead of after the retries of each request. It never contradicts the requests, unlike a check against public DNS servers, which a VPN may block. It is skipped when a proxy resolves the host instead (clientOptions.proxyOptions, or HTTPS_PROXY/HTTP_PROXY/ALL_PROXY), with an injected client, and with dnsCheck: false.

concurrency caps the requests at once (a shared vault throttles all of its callers), timeoutMs bounds each operation, and clientOptions reaches the Azure client (retries, proxy). The user agent carries @achs/azure-key-vault/<version>, so the vault's diagnostic logs tell who called.

Functions 🧰

FunctionDescription
get(key, { version }?)secret value (parsed when stored as JSON), null when not read; a version if given
getInfo(key)secret with its properties; throws on failure
set(key, value)inserts or updates a secret; a value that is not a string is stored as JSON
delete(key)deletes a secret: the deleted secret, null on failure
purge(key)purges a deleted secret: whether it was purged
restore(key)restores a deleted secret: its properties, null on failure
versions(key)versions of a secret, newest first, without values; null on failure
rollback(key, version)writes a previous version (value and tags) as the current one
keys()keys of the project group, sorted, without values (needs list)
getFor(secrets, override?)reads the secrets of an object whose values are defaults, typed with its shape
getAll()every secret of the project group (lists the whole vault: needs list)
setAll(secrets)inserts or updates the secrets of an object, in order
deleteAll(skipShared = true)deletes every secret of the project group
purgeAll(skipShared = true)purges every deleted secret of the project group
restoreAll(skipShared = true)restores every deleted secret of the project group
check()diagnoses the vault (see above)
audit({ allProjects }?)audits the project (or the whole vault) without reading values (see below)

getFor reads only the keys whose default is empty (null, '', 0, false), or every key with override; a secret stored as JSON (an array, an object, a number) comes back parsed.

const secrets = await keyVault.getFor({
    '$global-var': null,
    'my-secret': null,
    'my-secret-2': 'default value',
    'my-secret-group1': { 'my-secret-3': null },
    'my-array-secret': null,
});

Audit

audit() lists the vault once (names, tags and dates; never values) and reports, for the project or with allProjects for the whole vault, a summary (secrets, managed by the library, tagged, untagged, projects, groups, by environment) and these findings, logged at their level:

FindingLevelMeaning
untaggedwarnsecrets without the library tags (invisible to getAll and keys)
shadowedwarna key both own and shared ($): @achs/env injects them as one variable
scope-variantswarnone scope written in different ways (case, spaces): its secrets share names
name-mismatchwarna name that is not the one its tags give (renamed, or tagged by hand)
expiredwarnexpired secrets
expiringinfosecrets expiring within expiryWarningDays
not-yet-validinfosecrets whose notBefore is in the future
disabledinfodisabled secrets
unnormalized-scopeinfoscope tags with capitals or spaces (normalized on their next write)
other-envinfoproject audit with an environment: the project's secrets in other ones

auditSecrets(properties, options) runs the same checks over secret properties listed elsewhere.

Testing with the mock

createAzureKeyVaultMock(options, { faults }) gives a handler over an in-memory vault shared by every mock, that fails like Azure does (a missing secret is a secret-not-found). faults injects failures by key, or for every request with '*'; resetAzureKeyVaultMock() empties the vault.

const vault = createAzureKeyVaultMock(
    { project: 'app', env: 'dev' },
    { faults: { DB_PASS: 'forbidden', '*': 'timeout' } },
);

Command 💻

The akv command runs the same operations. Logs go to stderr (at info unless --log says otherwise) and output to stdout.

OptionDescription
--uri(required) vault URL
--project(required) project name
--groupsecrets group
--envenvironment
--spnservice principal (application) id
--passwordservice principal secret
--tenanttenant id
--logtrace, debug, info (default), warn, error or silent
--stricta vault or access failure fails the command
--concurrencyrequests at once
--timeouttime limit of each operation, in milliseconds
--no-dns-checkskips resolving the vault host before the first request
--keysecret key (get, set, versions, rollback)
--valuesecret value of set (else read from stdin)
--versionsecret version (get, rollback)
--fileinput JSON file (relative to the working directory)
--outputoutput JSON file (relative to the working directory)
--overridegetfor reads keys with a default value too
--jsoncheck and audit print their result as JSON
--allaudit covers the whole vault (no --project needed)
CommandDescription
checkdiagnoses the vault and who signed in
auditaudits the project, or the vault with --all; --strict fails on warnings
getprints a secret value (JSON when it is not a string)
setwrites a secret; pipe the value (--value would stay in the shell history)
listprints the keys of the project group, one per line
versionsprints the versions of a secret as JSON, newest first
rollbackwrites a previous version as the current one
getforwrites --output with the secrets of the --file structure (values are defaults)
getallwrites --output with every secret of the project group
publishcreates or updates the secrets of --file
cleardeletes every secret of the project group
restorerestores every deleted secret of the project group

Exit codes: 0 success, 2 vault not found, unreachable or timed out, 3 no usable identity, 4 permission denied, 1 anything else. Reads only fail the command with --strict (or when get, list or versions read nothing).

akv check --uri=https://my-key-vault.vault.azure.net --project=my-project --env=dev --json
printf '%s' "$DB_PASS" | akv set --uri=... --project=my-project --env=dev --key=DB_PASS
akv getfor --uri=... --project=my-project --env=dev --file=structure.json --output=secrets.json --log=debug

runCli(argv) runs the same command from code, and exitCode(kind) maps a failure kind to its code.

Development 🧿

pnpm test:cov   # Vitest with coverage (100% thresholds)
pnpm lint       # ESLint
pnpm typecheck  # TypeScript
pnpm format     # Prettier
pnpm build      # Vite library build into dist/

Changelog 📄

For last changes see CHANGELOG.md.

Built with 🛠️

版本与发布记录

@achs/azure-key-vault 共发布 32 个版本,首次发布于 2021/04/20;过去 12 个月发布了 3 个版本。

版本发布日期距今
5.0.1latest2026/10/071 个月前
5.0.02026/10/021 个月前
4.6.02026/05/086 个月前
4.5.02025/10/101.1 年前
4.4.32024/10/242 年前
4.4.22024/08/022 年前

关于 @achs/azure-key-vault 的常见问题

@achs/azure-key-vault 是什么?

Wrapper for @azure/keyvault-secrets for ease secrets handler in JSON files.

如何安装 @achs/azure-key-vault?

在项目目录运行 npm install @achs/azure-key-vault,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 akv,可以用 npx akv 直接运行。

@achs/azure-key-vault 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@achs/azure-key-vault 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@achs/azure-key-vault 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@achs/azure-key-vault 可以商用吗?

@achs/azure-key-vault 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@achs/azure-key-vault 的下载量有多少?

根据 npm 官方下载统计,@achs/azure-key-vault 在 2026/10/05 – 2026/11/03 这 30 天内被下载 5,767 次,日均约 192 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@achs/azure-key-vault 还在维护吗?

最新版本 5.0.1 发布于 2026/10/07(1 个月前);至今共发布 32 个版本,其中过去 12 个月发布了 3 个;首次发布于 2021/04/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。