[BUG]: azure-key-vault-plugin runs `az account get-access-token` once per secret, and the blocking calls time out Key Vault requests

Open
#1,275 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
azure, node.js, typescript
Domain
backend, security

Research direction

Start in packages/plugins/azure-key-vault/src/plugin.ts at getAccessToken (around line 528) and trace the Azure CLI fallback around line 425. Reproduce with Azure CLI auth and several secrets from one vault while the token is absent from the plugin’s cache. Done means concurrent requests for the same resource share one in-flight token request and all secrets resolve; check existing plugin tests for coverage.

Written by the indexing model from the issue text.

Description

varlock version

varlock 1.23.0, @varlock/azure-key-vault-plugin 1.3.1 (same code on main at 79fafc7)

Steps to reproduce
  1. Use Azure CLI auth (no service principal or OIDC, not on Azure), with a schema that reads ~10 secrets from one vault and no cacheTtl.

  2. Make sure the plugin finds no usable token in ~/.azure/msal_token_cache.json or ~/.azure/accessTokens.json. This happens on its own when:

    • az encrypts its token cache (core.encrypt_token_cache, the default on Windows), which writes msal_token_cache.bin instead;
    • az uses AZURE_CONFIG_DIR, since the plugin always reads homedir()/.azure;
    • the cached token has expired. az refreshes it on its next call, but the plugin can't.

    To simulate it on Linux, point HOME at a directory whose .azure symlinks the real one minus msal_token_cache.json, and set AZURE_CONFIG_DIR to the real one so az still works. That is how the numbers below were taken.

  3. DEBUG='varlock:plugin*' varlock load --skip-cache (--skip-cache is harmless here and keeps runs repeatable if you have cacheTtl)

What is expected?

One az call per vault resource while a token request is in flight, and all secrets resolve.

What is actually happening?

getAccessToken (packages/plugins/azure-key-vault/src/plugin.ts:528) checks the token cache once and writes it only after a token arrives. Nothing shares the request already in flight. Every secret resolved concurrently misses the cache, goes through the IMDS probe and both cache files, and reaches the execSync('az account get-access-token ...') fallback (plugin.ts:425).

Each az call took 0.4-2.0 s here (median ~1.5 s) and blocks the event loop. With 10 secrets that is 13-16 s of blocking. Secrets that got their token early have already started their ky.get. When the loop unblocks, their 10 s ky timeout has expired and fires before the response is read. ky does not retry timeouts by default, so those secrets fail.

10 secrets, --skip-cache, 3 runs each. Wall time covers the whole varlock load process.

wall time az calls secrets resolved
1.3.1 16.9 - 19.7 s 10 7-8 / 10 (rest: "Request timed out")
with the change below 5.3 - 5.4 s 1 10 / 10

With cacheTtl on @initAzure, warm entries hide this until they expire. Without it, any load that needs the CLI fallback hits it.

Suggested fix

Share the in-flight token promise per resource. This is what we run as a pnpm patch:

private inflightTokens = new Map<string, Promise<string>>();

private getAccessToken(resource: string): Promise<string> {
  let inflight = this.inflightTokens.get(resource);
  if (!inflight) {
    inflight = this._getAccessToken(resource).finally(() => this.inflightTokens.delete(resource));
    this.inflightTokens.set(resource, inflight);
  }
  return inflight;
}
// existing getAccessToken body renamed to _getAccessToken

The entry is cleared on success and on failure, so the 5-minute refresh check and retries after a failure still work. Reusing dedupeFetch would not work, because it keeps successful promises forever.

Separately: running az with async execFile instead of execSync would stop a slow CLI from stalling unrelated requests.

#1047 covers sharing credentials across processes. This one is within a single process.

System Info
Linux 6.18 (WSL2, Windows Azure CLI via WSL interop), Node 24.13.0, azure-cli 2.80.0
Dominant language
TypeScript
Stars
4.9k
Forks
140
Avg merge
2d 3h
Merged PRs (30d)
35

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dmno-dev/varlock

All issues in dmno-dev/varlock

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.