Read a customer's credentials straight from the secret manager they already run. They rotate on their own schedule and it just works; they revoke you and you stop.
import { awsVault } from '@devicai/vaultsdk/aws';
// the customer's own credentials, in the vault's own format
const vault = awsVault({
credentials: customer.vaultCredentials,
});
// they paste the reference their tooling already gives them
const { value } = await vault.read(
'arn:aws:secretsmanager:eu-west-1:123456789012:secret:stripe:api_key',
);
// and to let them pick one, instead of typing it
const options = await vault.list();
Plain HTTP on a zero-dependency core. The reference decides which vault answers.
Service account key, exchanged for an access token.
Access key or temporary credentials, SigV4-signed. Reads a field out of a key/value secret.
App registration, exchanged with Entra ID. Takes the az CLI output verbatim.
One package, @devicai/vaultsdk, no peer dependencies.
Connection, discoverability, cache — the whole SDK.
const vault = awsVault({
credentials,
ttlMs: 5 * 60_000, // 0 turns caching off
});
const { value, version } = await vault.read(reference);
// a rotation is picked up on the next read past the TTL
vault.invalidate(reference); // forget one
vault.invalidate(); // forget all
const options = await vault.list();
if (options === null) {
// not allowed to enumerate: show a free-text field
} else {
// [{ name: 'stripe-key', reference: '…' }]
// never compose a reference yourself
}
// off the credentials, no network call
vault.provider; vault.identity; vault.scope;
// touches no secret, so a failure here is a bad
// credential and never a missing permission
await vault.verify();
// bypasses the cache, and never returns the value
const probe = await vault.probe(reference);
// { version, elapsedMs, length: 24,
// preview: 'sk-••••••cdef' }
import { VaultError } from '@devicai/vaultsdk';
try {
await vault.read(reference);
} catch (err) {
if (err instanceof VaultError) switch (err.kind) {
case 'unauthenticated': // wrong or expired
case 'forbidden': // they grant the role
case 'not_found': // typo, or deleted
case 'unavailable': // transient — retryable
case 'invalid_reference': // not an address at all
}
}
The address you store is called a reference. An operator pastes the string their tooling already gives them; the vault is inferred from its shape. Leave the version off and you follow their rotation.
import { looksLikeReference, parseReference }
from '@devicai/vaultsdk';
looksLikeReference(pasted); // false → a raw secret: encrypt it
parseReference(pasted).provider; // 'google' · 'aws' · 'azure'
// both know every shape the SDK supports whether or not you opened
// that vault, so forgetting an import cannot change what you encrypt
// one instance is one customer, because credentials are
const vault = awsVault({
credentials: customer.vaultCredentials,
ttlMs: 5 * 60_000, // 0 disables caching entirely
});
await vault.read(reference); // goes to the vault
await vault.read(reference); // does not
vault.invalidate(reference);
await vault.read(reference); // goes to the vault again
A warm round-trip is several hundred milliseconds, which rules out reading on every use. The default of five minutes bounds how long a rotation takes to notice.
Plain HTTP and WebCrypto throughout, so the same package runs on Node 18+, Cloudflare Workers, Deno, Bun and the browser, with nothing to install alongside it.
// a Cloudflare Worker — same package, same code
import { azureVault } from '@devicai/vaultsdk/azure';
export default {
async fetch(request, env) {
const vault = azureVault({
credentials: env.CUSTOMER_VAULT_CREDENTIALS,
});
const { value } = await vault.read(
'https://acme-kv.vault.azure.net/secrets/stripe',
);
return callTheirApi(value);
},
};
The surface is the same everywhere — read, list,
verify, probe. What differs below is the vaults' own.
| Capability | aws | azure | |
|---|---|---|---|
| read | native | native | native |
| list | native | native | native |
| verify | native | native | native |
| follows a rotation | /versions/latest | AWSCURRENT | no version in the URL |
| reports resolved version | ✓ | ✓ | ✓ |
| pin an exact version | ✓ | ✓ | ✓ |
| field of a key/value secret | — | ✓ | — |
| binary secrets | ✓ | ✓ | — |
| one connection, many locations | — | ✓ | ✓ |
| credential blob | service account JSON | access key ± session token | az ad sp create-for-rbac |
| identity proof | JWT RS256 → token | SigV4, per request | Entra ID client creds |
| token reused across reads | ✓ | — | ✓ |