Azure Key Vault (Secrets)

Lightweight, dependency-free fake of the Azure Key Vault Secrets data plane that speaks the real Key Vault Secrets REST API (api-version 2025-07-01), so application code using @azure/keyvault-secrets can run against it with zero cost and zero side effects.

KeyValue
Port4594
ProtocolAzure Key Vault Secrets REST API (HTTP + JSON)
Compatible client@azure/keyvault-secrets (v4.x, SecretClient)
API version2025-07-01 (the SDK default)
Size~40 KB
Startup< 100ms
StateIn-memory, ephemeral, resettable

Quick Start

Start the server:

import { KeyvaultServer } from "./services/keyvault/src/server.js";

const server = new KeyvaultServer(4594);
await server.start();
// ... use it ...
await server.stop();

Connect with the real @azure/keyvault-secrets client. The fake serves over plain HTTP and presents a synthetic challenge resource, so set disableChallengeResourceVerification: true and allowInsecureConnection: true. Any TokenCredential works — the fake accepts any non-empty bearer token and never validates it:

import { SecretClient } from "@azure/keyvault-secrets";

// A minimal fake credential — no real Azure identity required.
const credential = {
  async getToken() {
    return { token: "parlel-fake-token", expiresOnTimestamp: Date.now() + 3600_000 };
  },
};

const client = new SecretClient("http://127.0.0.1:4594", credential, {
  disableChallengeResourceVerification: true,
  allowInsecureConnection: true,
});

// Set a secret (creates a new version).
const set = await client.setSecret("db-password", "s3cr3t", {
  contentType: "text/plain",
  tags: { env: "test" },
});

// Read the latest version.
const got = await client.getSecret("db-password");
console.log(got.value); // "s3cr3t"

// Soft-delete, then purge.
const poller = await client.beginDeleteSecret("db-password");
await poller.pollUntilDone();
await client.purgeDeletedSecret("db-password");

Tip: in tests, pass { intervalInMs: 0 } to beginDeleteSecret / beginRecoverDeletedSecret so the LRO pollers don't wait the default 2s between polls.

Authentication

Azure Key Vault uses challenge-based bearer authentication:

  1. The SDK sends the first request with an empty body.
  2. The fake replies 401 with a WWW-Authenticate header advertising the authority + resource: Bearer authorization="https://login.microsoftonline.com/parlel-tenant-id", resource="https://vault.azure.net"
  3. The SDK acquires a token from the supplied TokenCredential and replays the request with Authorization: Bearer <token>.

The fake accepts any non-empty bearer token, so any credential works (ClientSecretCredential, DefaultAzureCredential, or a hand-rolled fake). The challenge resource is synthetic, which is why disableChallengeResourceVerification: true is required on the client.

Implemented operations

Secrets

SDK methodHTTPPath
setSecretPUT/secrets/{name}
getSecret (latest)GET/secrets/{name}
getSecret (version)GET/secrets/{name}/{version}
updateSecretPropertiesPATCH/secrets/{name}/{version}
beginDeleteSecretDELETE/secrets/{name}
listPropertiesOfSecretsGET/secrets
listPropertiesOfSecretVersionsGET/secrets/{name}/versions
backupSecretPOST/secrets/{name}/backup
restoreSecretBackupPOST/secrets/restore

Deleted secrets (soft delete)

SDK methodHTTPPath
getDeletedSecretGET/deletedsecrets/{name}
listDeletedSecretsGET/deletedsecrets
beginRecoverDeletedSecretPOST/deletedsecrets/{name}/recover
purgeDeletedSecretDELETE/deletedsecrets/{name}

Internal (parlel-only, not part of Key Vault)

HTTPPathPurpose
GET/_parlel/healthLiveness + counts ({ status, service, secrets, versions, deleted })
POST/_parlel/resetWipe all state
GET/_parlel/dumpList live + deleted secret names

Behavior notes

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureSupportedNotes
Set / get / update secretsIncluding content type, tags, enabled, nbf, exp
Multiple versions per secretLatest + specific-version reads
List secrets / versionsPaged via maxresults + $skiptoken / nextLink
Soft delete + recover + purgeLRO pollers complete near-instantly
Backup / restoreOpaque base64url blob
Challenge-based bearer authAccepts any non-empty token
PaginationDefault page size 25
Token signature / scope validation✓ By design — Any non-empty credential is accepted — no real secrets needed
RBAC / access policies⟳ Roadmap
Keys & Certificates data planes⟳ Roadmap
HSM / managed-storage / rotation policies⟳ Roadmap
x-ms-keyvault-* regional headers⟳ Roadmap
Customer-managed encryption✓ By design — Plain in-memory storage — transport/at-rest crypto is unnecessary locally

Error codes / shapes

Errors use the Key Vault error envelope:

{ "error": { "code": "SecretNotFound", "message": "A secret with (name/id) foo was not found in this key vault." } }
HTTPcodeWhen
400BadParameterInvalid secret name, missing value, or malformed JSON / backup blob
401(challenge)Missing bearer token — replies with WWW-Authenticate
404SecretNotFoundUnknown secret, version, or deleted secret
405MethodNotAllowedUnsupported method on a known path
409ConflictRe-creating / restoring a name that is soft-deleted or already exists

The SDK surfaces these as RestError with a matching statusCode and code.

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

AZURE_KEYVAULT_URL=http://parlel-bridge:4594
AZURE_TENANT_ID=parlel-tenant
AZURE_CLIENT_ID=parlel-client
AZURE_CLIENT_SECRET=parlel-secret
<!-- parlel:testenv:end -->