Skip to content
macalbertPublic

About

One secret mapping for local dev, CI/CD, and runtime. Envilder resolves cloud secrets from your own vaults without SaaS middlemen, duplicated config, or .env drift.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

138 stars

Watchers

3 watching

Forks

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Latest commit

Β 

History

638 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ—οΈ Envilder ☁️

Envilder

One secret mapping for local dev, CI/CD, and runtime.
Define secrets once in envilder.json. Resolve them consistently from AWS SSM or Azure Key Vault.
Website & docs β†’ envilder.com

npm version npm downloads CI Tests Overall Coverage OpenSSF Best Practices Known Vulnerabilities MIT License Context7 CodeRabbit Pull Request Reviews

Why Envilder?

Your new developer joins the team. They need environment variables to run the app locally. What happens next? Someone sends API keys over Slack. Someone else digs up a wiki page with outdated credentials. Forty-five minutes later, their .env file is "probably correct".

Envilder fixes this with one versioned mapping contract.

You create a JSON mapping between variable names and cloud secret paths. Envilder resolves them from AWS SSM or Azure Key Vault. The same mapping file works in local dev (CLI), CI/CD (GitHub Action), and application startup (runtime SDKs).

# Push a secret: it is stored in your cloud and mapped in envilder.json
npx envilder --key=API_KEY --value=s3cret --secret-path=/my-app/api-key

# Zero-config: uses envilder.json and .env by default
npx envilder

# Or with explicit paths
npx envilder --map=envilder.json --envfile=.env

# Or skip the file: run your app with the secrets in memory
npx envilder exec -- npm run dev

No SaaS middleman. One versioned mapping contract. Secrets stay in your cloud.

The problem

  • Onboarding takes hours, not seconds. Every new developer needs someone to explain which secrets go where. Keys get shared over Slack, pasted from wikis, or copied from a colleague's machine. It's slow, error-prone, and insecure.
  • Every environment has its own workflow. Local dev reads .env files. CI/CD uses vault integrations. Production has its own method. Same app, three different secret workflows.
  • No single mapping contract. Your cloud provider may be the source of truth for secret values, but each environment still needs to know how application variables map to those secrets. Without a versioned contract, dev, staging, and production configurations drift apart.
  • The .env file is readable by everything in your repo. .gitignore keeps it out of commits, not out of reach. Editors, indexers, install scripts, and AI coding agents can all read the plaintext values sitting in your working tree.

How Envilder solves it

  • πŸ“‹ One mapping contract for everything. A single envilder.json defines what secrets your app needs and where they live. Git-versioned, PR-reviewable, and reused across environments.
  • ⚑ Works everywhere your code runs. CLI for local dev, GitHub Action for CI/CD, runtime SDKs for application startup. Same file, same result.
  • 🚫 No .env file when you don't want one. envilder exec starts any command with the secrets in its environment, in any language, without writing anything to disk.
  • πŸ”„ Rotate secrets without config drift. Keep application-facing variable names stable while rotating real secret values in AWS SSM or Azure Key Vault. Generated .env files update only when Envilder runs again; runtime consumers must restart or resolve again.
  • πŸ›‘οΈ Your cloud, zero infrastructure. Secrets stay in AWS SSM or Azure Key Vault. No SaaS proxy or extra servers. The mapping contract stays consistent, while provider credentials and secret migration remain provider-specific.

βš™οΈ Features

Feature Description
πŸ“‹ Declarative Mapping One JSON file defines how application variables map to cloud secrets. Git-versioned, PR-reviewable, and diff-able
☁️ Cloud-native Providers AWS SSM and Azure Key Vault today
⚑ Exec Mode envilder exec -- <command> runs any command with the secrets in its environment. Nothing written to disk, fail-closed, exit codes propagated. Guide
βœ… Validate envilder validate checks the map file against the schema and the provider rules, then checks that every declared secret exists, without reading or printing values. --offline checks the map file only, without credentials. Guide
πŸ”Œ Runtime SDKs Load secrets into memory at app startup: .NET, Python, Node.js. No .env on disk
βš™οΈ GitHub Action Pull secrets in CI/CD. Same mapping, zero manual config
πŸ”„ Pull & Controlled Push Pull secrets to .env, or intentionally push local values to your cloud provider when bootstrapping or rotating
🧱 Zero Infrastructure No servers, no proxies, no SaaS. Uses cloud services you already have

πŸš€ Quick Start

1. Run with npx

npx envilder --version

Or install globally:

npm install -g envilder

Requirements: Node.js v22.12+. Configure credentials for either AWS SSM or Azure Key Vault, with read access, plus write access to push secrets. See full requirements.

2. Map your secrets (envilder.json)

Push a secret and Envilder writes the mapping for you:

npx envilder --key=API_KEY --value=s3cret --secret-path=/my-app/api-key
# βœ“ Created envilder.json

npx envilder --key=DB_PASSWORD --value=pa55word --secret-path=/my-app/db/password
# βœ“ Added DB_PASSWORD to envilder.json

The secrets are stored in AWS SSM, and envilder.json now maps each variable to its secret path:

{
  "API_KEY": "/my-app/api-key",
  "DB_PASSWORD": "/my-app/db/password"
}

The values are never written to the file, so it is safe to commit and review in PRs. If your secrets already exist in the cloud, write the same file by hand instead. Use --map=<file> to write a different file, or --no-map-update to push without touching any map file. See the Push Command Guide for the details.

πŸ’‘ Using Azure Key Vault? Add --provider=azure --vault-url=https://my-vault.vault.azure.net to the first push and Envilder writes the matching $config. See Mapping File Format below.

3. Run your app with the secrets

npx envilder exec -- npm run dev

envilder exec resolves the mapping and starts your command with the secrets in its environment. Nothing is written to disk. Envilder options go before the command (or before an optional --); everything after belongs to your command. Exec mode is fail-closed: the command does not start if any secret is missing. It is not a sandbox: your command and everything it starts can read the secrets.

πŸ“– See the Exec Command Guide for exit codes, signals and GitHub Actions masking.

4. Need a .env file? Pull it

npx envilder

With envilder.json in your current directory, no flags are needed β€” --map defaults to envilder.json and --envfile defaults to .env. Your secrets are pulled from AWS SSM and written to .env. Add .env to .gitignore and treat it as a secret.

πŸ’‘ Runnable examples: envilder-examples (Testcontainers and Aspire for .NET, Python and TypeScript, AWS SSM and Azure Key Vault, plus envilder exec).

πŸŽ₯ See it in action

Watch how easy it is to automate your .env management in less than 1 minute:

Watch the video

πŸ—ΊοΈ Mapping File Format

The mapping file (envilder.json) is the core of Envilder. It's the single model that defines what secrets your app needs and where they live in your cloud provider. The same file is used by the CLI, the GitHub Action, and the runtime SDKs. You can optionally include a $config section to declare which provider and settings to use.

Editors that use SchemaStore (VS Code, JetBrains IDEs) give you autocomplete, inline documentation and validation for envilder.json and envilder.*.json files automatically. For any other file name, add "$schema": "https://envilder.com/schema/map-file.v1.json"; a single-secret push adds it for you when the file name needs it. The schema is published at envilder.com/schema/map-file.v1.json.

Basic Format (AWS SSM, default)

When no $config is present, Envilder defaults to AWS SSM Parameter Store:

{
  "API_KEY": "/myapp/prod/api-key",
  "DB_PASSWORD": "/myapp/prod/db-password",
  "SECRET_TOKEN": "/myapp/prod/secret-token"
}

Values are SSM parameter paths (e.g., /myapp/prod/api-key).

With $config (explicit provider)

Add a $config key to declare the provider and its settings. Envilder reads $config for configuration and uses all other keys as secret mappings:

AWS SSM with profile

{
  "$config": {
    "provider": "aws",
    "profile": "prod-account"
  },
  "API_KEY": "/myapp/prod/api-key",
  "DB_PASSWORD": "/myapp/prod/db-password"
}

Azure Key Vault

{
  "$config": {
    "provider": "azure",
    "vaultUrl": "https://my-vault.vault.azure.net"
  },
  "API_KEY": "myapp-prod-api-key",
  "DB_PASSWORD": "myapp-prod-db-password"
}

Secret names. Key Vault secret names only allow alphanumeric characters and hyphens. The only translation is for paths: slashes become hyphens and a leading slash is dropped (e.g., /myapp/db/password β†’ myapp-db-password). Anything else is sent as written, so a name Key Vault rejects (for example one with _ or .) fails with Key Vault's own error. The CLI, the GitHub Action and every SDK apply the same rule, so they all read the same secret.

Name collisions. The CLI and the GitHub Action also reject a map file in which two values resolve to the same name, ignoring case (/app/db-url and app-db-url), so a push cannot write both to one secret. The SDKs only read secrets, so they skip that check.

$config Options

Key Type Default Description
provider "aws" | "azure" "aws" Cloud provider to use
vaultUrl string - Azure Key Vault URL (required when provider is "azure")
profile string - AWS CLI profile for multi-account setups (AWS only)

Configuration Priority

CLI flags and GitHub Action inputs always override $config values:

CLI flags / GHA inputs  >  $config in map file  >  defaults (AWS)

This means you can set a default provider in $config and override it per invocation:

# Uses $config from the map file
envilder

# Overrides provider and vault URL from the map file
envilder --provider=azure --vault-url=https://other-vault.vault.azure.net --map=envilder.json --envfile=.env

Keys and Values: What Is Accepted

Every mapping has the shape "<KEY>": "<secret reference>". The key-name rule below is the same in the CLI, the GitHub Action, the JSON Schema and every runtime SDK, so a key accepted in one place is accepted everywhere (ADR-0008). The schema is stricter in two other ways: it also flags non-string values and unknown $-prefixed keys, which the runtime parsers skip.

Keys (environment variable names)

Rule Details
Allowed characters Letters A-Z a-z, digits 0-9, _, ., - and / (^[A-Za-z0-9_./-]+$)
Recommended ^[A-Za-z_][A-Za-z0-9_]*$: a POSIX name, usable from any shell
Rejected An empty name, any other character (space, tab, #, =, :, line breaks, U+2028/U+2029, accented or non-Latin letters) and the name __proto__
Reserved Keys starting with $ ($schema, $config) are metadata, never variables
Non-string values A mapping whose value is not a string is skipped, but its key is still validated

A map file with an invalid key is rejected as a whole, before any secret is fetched. The error quotes the key and never the value.

Why this exact set? A key must reach your application as itself and nothing else. Any other character either opens a different assignment in a .env file (=, line breaks, U+2028/U+2029: the injection fixed in #511) or produces a line that every reader silently drops. __proto__ is inside the character set but is dropped by every JavaScript reader that builds a plain object, dotenv included.

Hierarchical keys with /

/ is accepted everywhere, so a key like Database/Password keeps working in every mode:

Where What your application gets
Runtime SDKs (resolve, load) The name as-is: Database/Password
.NET SDK IConfiguration The section Database:Password, as with AWS's own SSM configuration provider
envilder exec The name as-is in the child process environment
.env file (CLI, GitHub Action) The line Database/Password=..., written as-is, plus a warning

The warning is there because .env readers disagree about /:

Consumer reading Database/Password=secret Result
python-dotenv Loaded
dotenv (Node.js) Line ignored: the variable never loads
Docker Compose env_file Error: unexpected character "/" in variable name
set -a; source .env (bash) Error: the variable does not exist

If your application reads the .env file with one of the last three, use envilder exec, or name the key with __ (Database__Password), which every reader loads and .NET's environment-variables provider maps to the same Database:Password section.

Values (secret references)

A value is the identifier of the secret in the provider. It must be a JSON string.

Provider Identifier format
AWS SSM Parameter name, used as-is. Case-sensitive; a-zA-Z0-9_.- plus / for hierarchies (at most 15 levels); up to 1011 characters including the ARN prefix; cannot start with aws or ssm (AWS constraints)
Azure Key Vault Secret name: 1-127 characters, letters, digits and - (Azure naming). Slashes become hyphens and a leading slash is dropped (/myapp/db/password β†’ myapp-db-password); anything else is sent as written, the same in the CLI, the GitHub Action and every SDK (see Azure Key Vault)

Secret contents (the resolved values)

Envilder never rewrites a secret value: what the provider returns is what your application gets. The limits come from where the value is stored:

Stage Limit
Provider AWS SSM: 4 KB (standard) or 8 KB (advanced tier). Azure Key Vault: 25 KB
.env file (CLI, GitHub Action) See below
Process environment (SDK load / inject, or any tool that loads the .env) The operating system cannot store NUL (U+0000) in a variable, and Windows limits a variable to 32,767 characters

The SDKs' resolve-only APIs (resolve, ResolveFile, IConfiguration) return values in memory and are subject to the provider limits only.

How the CLI writes .env values. Each value is written so that dotenv reads back exactly the original string. The quoting is chosen automatically and checked by parsing the result before the file is written. The check uses dotenv's grammar extended to keys with /, so for those keys it proves the value round-trips, not that Node.js dotenv loads them (it skips such lines, as shown above):

Value Written as
plain, she said "hi" KEY=plain, KEY=she said "hi" (no quotes needed)
alpha#beta KEY='alpha#beta' (unquoted, # would start a comment)
padded with two leading and trailing spaces KEY=' padded ' (unquoted, the spaces would be trimmed)
first + line break + second KEY="first\nsecond" (read back as a real line break)

#, leading and trailing whitespace, quotes, backslashes, a literal \n, real LF/CRLF line breaks and Unicode all round-trip. When the file already exists, each assignment is replaced in place (a multiline one entirely), and comments, ordering and line endings are preserved.

One combination cannot be expressed by any .env quoting: a value containing all three quote characters (', " and `) together with a line break, leading or trailing whitespace, or a #. The CLI then stops with an error that names the variable, and the .env file is left unchanged. Store such a value encoded (for example base64) or read it with an SDK instead.

Variable expansion: Envilder writes ${...} literally and dotenv does not expand it, but other loaders do. Docker Compose, for instance, expands an unquoted KEY=${HOME} when it reads the file. Keep this in mind when a secret contains $.

🧩 Runtime SDKs

Beyond the CLI and GitHub Action, Envilder provides runtime SDKs that resolve secrets directly into your application's memory at startup. No .env file written to disk, no secrets left behind. SDKs use the same map-file format as the CLI.

.NET SDK

Install via NuGet:

dotnet add package Envilder

Load secrets into IConfiguration or inject them into the process environment:

// Option A: integrate with IConfiguration
var config = new ConfigurationBuilder()
    .AddEnvilder("envilder.json")
    .Build();

var dbPassword = config["DB_PASSWORD"];

// Option B: resolve + inject into environment
Env.Load("envilder.json");
var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD");

πŸ“– Full .NET SDK docs Β· πŸ’‘ Examples: Testcontainers Β· Aspire

Python SDK

Install via uv (recommended) or pip:

uv add envilder
# or
pip install envilder

Load secrets into your application with a single line:

from envilder import Envilder

# Resolve + inject into os.environ
Envilder.load('envilder.json')

Or route by environment, where each environment points to its own map file:

from envilder import Envilder

Envilder.load('production', {
    'production': 'prod-secrets.json',
    'development': 'dev-secrets.json',
    'test': None,  # no secrets loaded
})

πŸ“– Full Python SDK docs Β· πŸ’‘ Examples: Testcontainers

Node.js SDK

Install via npm:

npm install @envilder/sdk

Load secrets into your application with a single line:

import { Envilder } from '@envilder/sdk';

// Resolve + inject into process.env
const secrets = await Envilder.load('envilder.json');

Or use the fluent builder for full control:

import { Envilder, SecretProviderType } from '@envilder/sdk';

const secrets = await Envilder.fromMapFile('envilder.json')
  .withProvider(SecretProviderType.Aws)
  .withProfile('prod-account')
  .resolve();

πŸ“– Full Node.js SDK docs Β· πŸ’‘ Examples: Testcontainers Β· Aspire

πŸ€– GitHub Action

Reference the published macalbert/envilder/github-action@v0 tag directly. It contains the prebuilt action, so consumers do not need an Envilder build step.

AWS SSM (default):

- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v5
  with:
    role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
    aws-region: us-east-1

- name: Pull secrets from AWS SSM
  uses: macalbert/envilder/github-action@v0
  with:
    map-file: envilder.json
    env-file: .env

Azure Key Vault:

- name: Azure Login
  uses: azure/login@v2
  with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Pull secrets from Azure Key Vault
  uses: macalbert/envilder/github-action@v0
  with:
    map-file: envilder.json
    env-file: .env
    provider: azure
    vault-url: ${{ secrets.AZURE_KEY_VAULT_URL }}

πŸ“– Full GitHub Action docs

πŸ“š More resources

πŸ› οΈ How it works

graph LR
    A["Mapping Contract<br/>(envilder.json)"] --> B[Envilder]:::core
    B --> C["CLI β†’ .env file"]
    B --> X["CLI exec β†’ process env"]
    B --> D["GitHub Action β†’ CI/CD"]
    B --> E["SDK β†’ app memory"]
    F["AWS SSM / Azure Key Vault"]:::cloud --> B

    classDef cloud fill:#ffcc66,color:#000000,stroke:#333,stroke-width:1.5px;
    classDef core fill:#1f3b57,color:#fff,stroke:#ccc,stroke-width:2px;
Loading
  1. Define: create an envilder.json mapping env var names to cloud secret paths
  2. Resolve: Envilder fetches each secret from your cloud vault
  3. Deliver: secrets arrive as a .env file (CLI/GHA), in the environment of a command you start with envilder exec (CLI), or in-memory (SDKs)
  4. Rotate: update secret values in your cloud provider while keeping the same application-facing mapping. Rerun Envilder to refresh a generated .env, or restart/re-resolve in runtime consumers
  5. Bootstrap: optionally push local values to your cloud provider when intentionally setting up or rotating secrets

What Envilder is not

Envilder is not a secrets manager. It does not replace Vault, Infisical, or Doppler.

It also does not replace AWS SSM or Azure Key Vault: it works on top of them. Envilder does not store secrets, proxy requests, or introduce a SaaS control plane.

Your cloud provider remains the source of truth. Envilder provides the mapping and resolution layer that makes those secrets usable consistently across local development, CI/CD, and runtime.

Use Envilder when your secrets already live in your cloud provider and you want one versioned envilder.json mapping contract everywhere your code runs.

envilder exec is not a sandbox either. It keeps secrets off the disk, but it does not isolate the process: your command and every process it starts can read them. It also leaves an existing .env file untouched, so delete it yourself when you move from pull mode to exec mode.

🏁 What's Next

Envilder already covers CLI (including envilder exec), GitHub Action, and runtime SDKs for .NET, Python, and Node.js.

Next priorities include Go and Java SDKs and GCP Secret Manager.

See the full roadmap.

🀝 Contributing

All contributions are welcome: PRs, issues, docs, examples, and feedback from real usage.

Good first contributions include trying Envilder with an existing AWS SSM or Azure Key Vault setup, improving examples, testing the GitHub Action, or reviewing SDK ergonomics.

See CONTRIBUTING.md.

πŸ’œ Sponsors

LocalStack Β Β Β  Snyk

Envilder is also supported through the AWS Open Source Credits Program.

πŸ“œ License

MIT Β© MarΓ§al Albert
See LICENSE | CHANGELOG | Security Policy

About

One secret mapping for local dev, CI/CD, and runtime. Envilder resolves cloud secrets from your own vaults without SaaS middlemen, duplicated config, or .env drift.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

138 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages