env-master

SDK for services

@env-master/sdk is what a service integrates. It fetches values at startup and keeps them in process memory, not in environment variables.

bun add @env-master/sdk

Connecting

import { EnvMasterClient } from "@env-master/sdk";

const em = new EnvMasterClient({
  baseUrl: process.env.ENVMASTER_URL!,
  project: "billing-api",
  environment: "production",
});

await em.loginMachine({
  clientId: process.env.ENVMASTER_CLIENT_ID!,
  clientSecret: process.env.ENVMASTER_CLIENT_SECRET!,
});

const dbPassword = await em.get("DB_PASSWORD");

The get() accessor is the recommended way. Writing into process.env is also possible (injectIntoEnv()), but it requires a deliberate call: environment variables are visible via /proc, inherited by child processes, and end up in crash dumps.

How a value reaches the service

The client generates an x25519 key pair when created. The server wraps the environment's data key under its public half and returns the entries' ciphertext — decryption happens inside your process. Along this path the server never produces the plaintext value at all.

Methods

MethodWhat it does
loginMachine(creds)Login and background token refresh
get(key)A key's value, NOT_FOUND if missing
getVersion(key)The value together with its version number
secrets()All values as a Map
list()Entries with metadata
refresh()Drop the cache and re-read
mask(text)Replace known values with ***
dispose()Stop the token refresh timer

Rotation without a restart

The token is refreshed at 75% of its TTL; the entry cache lives for 15 minutes. To react to a new value, compare versions:

const { value, version } = await em.getVersion("DB_PASSWORD");
if (version !== lastSeen) await reconnectPool(value);

Logs

logger.info(em.mask(`connecting as ${user}:${password}`));
// connecting as billing:***

Longer values are replaced first — otherwise a nested substring would leave a tail behind.