Credentials stay out of the config
The config says where secrets live. Logins come from the environment or an untracked env file, and a key that looks like a credential is rejected.
@rinci/secrets
Fetch from Infisical, Doppler, Vault, AWS, GCP, Azure, 1Password or Bitwarden, then run anything with the values in its environment. One config, zero dependencies, nothing written to disk.
$ bun add -d @rinci/secretsSources are read in parallel and merged in order. Every run says where its values came from, how many, and which existing variables it replaced. With -v it lists the names. Never the values.
$ rinci-secrets run -e prod -- bun dev
secrets 42 secrets from infisical 38 (184ms), dotenv 4 (1ms)
secrets overriding 1 existing: NODE_ENV
ready on http://localhost:3000Infisical Cloud or self-hosted, through a Universal Auth machine identity or an access token. Imports and secret references resolved.
Doppler configs through a service token. Project and config are implied by the token, or set explicitly with a personal token.
Vault or OpenBao KV v1 and v2, with a token or AppRole login. Namespaces supported, AppRole tokens revoked after the read.
One AWS Secrets Manager secret, JSON or plain text, signed with SigV4 from the standard AWS credential variables. No SDK.
Read one bundle secret or a list of secrets from GCP Secret Manager with Application Default Credentials, a service account key or a token.
Every enabled secret in an Azure Key Vault, or a chosen list, through a service principal or an access token. Dashes become underscores.
Fields of a 1Password item through the op CLI, with a service account token or the desktop app. Labels become variable names.
All secrets a Bitwarden machine account can see, or one project's, through the bws CLI. Self-hosted servers supported.
Local .env files as a source. For development, local overrides layered on a remote source, or an offline fallback.
No dependencies sit between your credentials and the network. The request signing, the token exchange and the process spawning are a few hundred lines each, in the repository, with tests.
The config says where secrets live. Logins come from the environment or an untracked env file, and a key that looks like a credential is rejected.
Your command gets the secrets, not the identity that fetched them. INFISICAL_CLIENT_SECRET and its kind are removed from its environment.
If a source fails, the command does not start. No app booting half-configured against the wrong database.
Arguments reach the program exactly as typed, Windows .cmd shims included. A quote or an & cannot become a second command.
Plain HTTP is refused unless the host is local or private, like an in-cluster API reached over WARP or a VPN.
Access tokens minted for a run are revoked as soon as the read is done. Values are never written to disk.
secrets.config.json
{
"$schema": "https://secrets.rin.ci/schema.json",
"envFiles": [".env.infisical"],
"sources": [
{
"provider": "infisical",
"domain": "https://infisical.internal",
"projectId": "your-project-id",
"environment": "${SECRETS_ENV:-dev}"
},
{ "provider": "dotenv", "path": ".env.local", "required": false }
]
}package.json
{
"scripts": {
"dev": "rinci-secrets run -- bun dev",
"start": "rinci-secrets run -e prod -- bun start"
}
}.env.infisical, which git ignores.-e. The config reads it as${SECRETS_ENV}.Start with the provider you use today. Add a second source when you need one; the command stays the same.