sondahub / Azure Key Vault sandbox
An Azure Key Vault mock that the Azure SDK signs in to
Key Vault’s secrets API, answered by sondahub: the bearer challenge first, a Microsoft Entra ID token endpoint for azure-identity to sign in at, then secrets with versions, content types, tags and attributes, soft delete with recover and purge, backup and restore, and paging — Key Vault’s errors and ids, so the official SDK works unchanged but for its URLs.
An independent imitation for testing. Not affiliated with, or endorsed by, Microsoft.
Connect
- Instead of
- https://<vault>.vault.azure.net
- Vault URL
- https://api.sondahub.com
- Sign-in (authority)
- https://api.sondahub.com/sandbox/azure-key-vault/login
- Tenant
- 9e3779b1-c302-605a-db25-3734c1b23920
- App
- 9e3779b1-6169-8a45-9fb9-5cba1e342fb5 / sondahub~sandbox.client-secret — or any app id
- API versions
- 2016-10-01, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 2025-07-01
- OpenAPI 3
- https://api.sondahub.com/sandbox/azure-key-vault/openapi.json
Python (azure-keyvault-secrets, azure-identity)
from azure.identity import ClientSecretCredential
from azure.keyvault.secrets import SecretClient
from azure.core.pipeline.policies import SansIOHTTPPolicy
credential = ClientSecretCredential(
'9e3779b1-c302-605a-db25-3734c1b23920', # the tenant the vault's challenge names
'9e3779b1-6169-8a45-9fb9-5cba1e342fb5',
'sondahub~sandbox.client-secret',
authority='https://api.sondahub.com/sandbox/azure-key-vault/login',
disable_instance_discovery=True,
)
class SondahubSession(SansIOHTTPPolicy): # carry the sandbox session from each answer to the next request
token = None
def on_request(self, request):
if SondahubSession.token:
request.http_request.headers['X-Sondahub-Session'] = SondahubSession.token
def on_response(self, request, response):
SondahubSession.token = response.http_response.headers.get('X-Sondahub-Session') or SondahubSession.token
client = SecretClient('https://api.sondahub.com', credential, per_call_policies=[SondahubSession()])
client.set_secret('my-api-key', 's3cr3t', content_type='text/plain', tags={'env': 'dev'})
print(client.get_secret('my-api-key').value)
Node (@azure/keyvault-secrets, @azure/identity)
import { ClientSecretCredential } from '@azure/identity'
import { SecretClient } from '@azure/keyvault-secrets'
const credential = new ClientSecretCredential('9e3779b1-c302-605a-db25-3734c1b23920', '9e3779b1-6169-8a45-9fb9-5cba1e342fb5', 'sondahub~sandbox.client-secret', {
authorityHost: 'https://api.sondahub.com/sandbox/azure-key-vault/login',
disableInstanceDiscovery: true,
})
// carry the sandbox session from each answer to the next request
let session = null
const sondahubSession = {
name: 'sondahubSession',
async sendRequest(request, next) {
if (session) request.headers.set('X-Sondahub-Session', session)
const response = await next(request)
session = response.headers.get('X-Sondahub-Session') ?? session
return response
},
}
const client = new SecretClient('https://api.sondahub.com', credential, { additionalPolicies: [{ policy: sondahubSession, position: 'perCall' }] })
const secret = await client.getSecret('orders-db-connection')
Import it. In Sonda: Import → From a URL, paste the OpenAPI address. You get a folder per resource and a request per operation, with example bodies and names from the seed, so most requests work as they are. Then set the auth: any bearer token (sandbox-token works). Any client that imports OpenAPI 3 takes the same address.
Try it here
curl "https://api.sondahub.com/secrets?api-version=2025-07-01"
curl "https://api.sondahub.com/secrets?api-version=2025-07-01" \ -H "Authorization: Bearer sandbox-token"
curl "https://api.sondahub.com/secrets/orders-db-connection?api-version=2025-07-01" \ -H "Authorization: Bearer sandbox-token"
curl -X PUT "https://api.sondahub.com/secrets/my-api-key?api-version=2025-07-01" \
-H "Authorization: Bearer sandbox-token" \
-H "Content-Type: application/json" \
-d '{"value":"s3cr3t-value","contentType":"text/plain","tags":{"env":"dev"}}'
curl "https://api.sondahub.com/secrets/legacy-ftp-password?api-version=2025-07-01" \ -H "Authorization: Bearer sandbox-token"
curl -X DELETE "https://api.sondahub.com/secrets/sendgrid-api-key?api-version=2025-07-01" \ -H "Authorization: Bearer sandbox-token"
curl "https://api.sondahub.com/deletedsecrets?api-version=2025-07-01" \ -H "Authorization: Bearer sandbox-token"
curl -X POST "https://api.sondahub.com/deletedsecrets/old-webhook-secret/recover?api-version=2025-07-01" \ -H "Authorization: Bearer sandbox-token"
From the shell, keep the token yourself: curl -i shows X-Sondahub-Session; send it back with -H "X-Sondahub-Session: …". How sessions work.
The seed vault
| Secret | What it holds |
|---|---|
orders-db-connection | 1 version, text/plain |
stripe-api-key | 3 versions, text/plain |
storage-account-key | 1 version, expires in two months |
app-settings | 2 versions, application/json |
sendgrid-api-key | 1 version, text/plain |
legacy-ftp-password | 1 version, disabled |
old-webhook-secret | 1 version, deleted (recoverable) |
A set makes a new version and the newest is what a read gets; PATCH changes a version’s attributes, content type and tags, not its value. A delete is soft: the secret moves to /deletedsecrets for 90 days, its name can’t be reused (409 ObjectIsDeletedButRecoverable), and it can be recovered or purged. A backup is every version in one signed blob; restore it into a vault that doesn’t have the name.
Every value in the seed is fake and made up when the site is built. What you write is kept in your own session token and nowhere else — but this is a public playground: never send it a real secret. Not affiliated with, or endorsed by, Microsoft.
What it answers
Every vault call takes ?api-version=. Errors are Key Vault’s: {"error": {"code", "message", "innererror"}}.
Questions
Is this Azure?
No — an independent imitation of Azure Key Vault’s secrets API for testing, not affiliated with or endorsed by Microsoft. No subscription, tenant or HSM is involved.
Will the Azure SDK work against it?
Yes. The Python set-up on this page was run against the sandbox with azure-keyvault-secrets and azure-identity: the challenge, the sign-in at the sandbox’s token endpoint, set, get by version, update, list with paging, disable, delete and its poller, recover, purge, backup and restore, with the SDK’s challenge-resource check left on. The Node set-up follows @azure/keyvault-secrets’ and @azure/identity’s documented options.
Why is the vault at the root of the host?
Because the SDKs read a vault’s address from the host of every id they get back (…/secrets/{name}/{version}), and an id with more path than that is refused. So the vault is https://api.sondahub.com itself, and there is one: every session starts from the same seed vault.
How does the sign-in work?
A request without a token answers 401 with Key Vault’s challenge: WWW-Authenticate: Bearer authorization="https://login.microsoftonline.com/9e3779b1-c302-605a-db25-3734c1b23920", resource="https://sondahub.com". The SDK takes the tenant from it, asks its credential for a token for that resource, and retries. Point the credential’s authority at https://api.sondahub.com/sandbox/azure-key-vault/login (with instance discovery off, since the host isn’t Microsoft’s) and it gets one there: client credentials, any app id — 9e3779b1-6169-8a45-9fb9-5cba1e342fb5 has its secret checked. The vault then takes any bearer token. On another host than api.sondahub.com (a laptop, say) pass verify_challenge_resource=False.
What is not modelled?
Keys and certificates (and the secrets that back certificates), managed HSM, purge protection, RBAC and access policies, private endpoints and firewalls. Expiry and not-before are kept and reported but, as at Key Vault for secrets, not enforced on reads. A secret keeps its newest 25 versions.