Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ on:
- ".github/workflows/test-action.yml"
- "github-action/action.yml"
- "src/envilder/apps/gha/**"
- "src/envilder/apps/shared/**"
- "src/envilder/core/**"
- "scripts/build-github-action.mjs"
- "tests/envilder/apps/gha/**"
- "e2e/github-action.test.ts"
- "e2e/gha.test.ts"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
Expand Down Expand Up @@ -79,3 +82,40 @@ jobs:
- name: 🧹 Clean up test file
if: always()
run: rm -f .env.test

- name: 🔐 Test Action - Command (secrets only in this step)
uses: ./github-action
with:
map-file: e2e/sample/envilder.json
command: test -n "$TOKEN_SECRET" && echo "✅ The command sees TOKEN_SECRET"

- name: ✅ Verify the command did not export secrets
run: |
if [ -n "${TOKEN_SECRET:-}" ]; then
echo "❌ TOKEN_SECRET leaked to a later step!"
exit 1
fi
echo "✅ TOKEN_SECRET is not visible after the command step"

- name: 🔐 Test Action - Export to the job environment
uses: ./github-action
with:
map-file: e2e/sample/envilder.json

- name: ✅ Verify secrets reached later steps
run: |
if [ -z "${TOKEN_SECRET:-}" ]; then
echo "❌ TOKEN_SECRET was not exported!"
exit 1
fi
echo "✅ TOKEN_SECRET is available"
# .env is gitignored, so git status below would not report it
if [ -f .env ]; then
echo "❌ The export left .env in the workspace!"
exit 1
fi
if [ -n "$(git status --porcelain)" ]; then
echo "❌ The export left files in the workspace:"
git status --porcelain
exit 1
fi
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,19 @@ prebuilt action, so consumers do not need an Envilder build step.
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1

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

- run: pnpm test # the secrets are environment variables here
```

Without `env-file`, the secrets are exported to **every later step of the job** and no
`.env` file is written. To limit them to **a single process**, set `command` instead
(for example `command: ./deploy.sh`): later steps never see them. Set `env-file` only when
a tool needs a file.

**Azure Key Vault:**

```yaml
Expand All @@ -488,11 +494,10 @@ prebuilt action, so consumers do not need an Envilder build step.
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Pull secrets from Azure Key Vault
- name: Load 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 }}
```
Expand Down
14 changes: 9 additions & 5 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "Envilder GitHub Action"
description: "Pull secrets from AWS SSM or Azure Key Vault into .env files for your CI/CD workflows"
description: "Load secrets from AWS SSM or Azure Key Vault into your CI/CD job environment, a single command, or a .env file"
author: "macalbert"

branding:
Expand All @@ -11,8 +11,11 @@ inputs:
description: "Path to the JSON file mapping environment variables to secret paths"
required: true
env-file:
description: "Path to the .env file to generate"
required: true
description: "Optional. Path to a .env file to write the secrets to, for tools that need a file. Without env-file or command, secrets are exported to the environment of the later steps of the job."
required: false
command:
description: "Optional. Command to run (with bash) with the secrets in its environment. The secrets exist only for this command, not for the later steps of the job. Cannot be combined with env-file."
required: false
provider:
description: "Cloud provider to use: aws or azure (default: aws). Can also be set via $config.provider in the map file."
required: false
Expand All @@ -22,7 +25,7 @@ inputs:

outputs:
env-file-path:
description: "Path to the generated .env file"
description: "Path to the generated .env file (empty unless env-file is set)"
value: ${{ inputs.env-file }}

runs:
Expand All @@ -33,12 +36,13 @@ runs:
with:
node-version: "22.x"

- name: 🔐 Pull secrets into .env
- name: 🔐 Load secrets
shell: bash
working-directory: ${{ github.workspace }}
env:
INPUT_MAP_FILE: ${{ inputs.map-file }}
INPUT_ENV_FILE: ${{ inputs.env-file }}
INPUT_COMMAND: ${{ inputs.command }}
INPUT_PROVIDER: ${{ inputs.provider }}
INPUT_VAULT_URL: ${{ inputs.vault-url }}
run: |
Expand Down
24 changes: 24 additions & 0 deletions docs/changelogs/gha.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
## [Unreleased]

### Added

* **Export secrets to the job environment (new default)**: Without `env-file`,
the Action exports every secret to `GITHUB_ENV`, so every later step of the
job gets them as environment variables and no `.env` file is written. The
step fails, and nothing is exported, when any secret is missing or empty,
or when the map file declares a name the runner does not let `GITHUB_ENV`
set: `NODE_OPTIONS` or a default variable such as `GITHUB_SHA` or
`RUNNER_OS`. Names are compared case-insensitively, so `node_options` is
refused too
* **`command` input**: Runs a command with `bash` (like a `run:` step) with
the secrets in its environment only, and ends the step with the command's
exit code. The secrets are not exported, so later steps never see them.
Cannot be combined with `env-file`

### Changed

* **`env-file` is optional**: When set, the Action writes the `.env` file as
before, so existing workflows (which always set it) keep their behavior

---

## [0.19.0] - 2026-10-30

### Changed
Expand Down
63 changes: 48 additions & 15 deletions docs/github-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,43 @@

## Overview

The Envilder GitHub Action allows you to seamlessly pull secrets from AWS Systems Manager (SSM)
Parameter Store or Azure Key Vault into `.env` files within your GitHub Actions workflows. This
eliminates the need to manually manage environment variables in CI/CD pipelines and ensures your
applications always have the latest configuration from your centralized secret store.
The Envilder GitHub Action loads secrets from AWS Systems Manager (SSM) Parameter Store or Azure
Key Vault into your GitHub Actions jobs. By default it exports them as environment variables for
the later steps of the job, without writing a `.env` file. It can also run a single command with
the secrets, or write a `.env` file when a tool needs one.

## Modes

| Mode | Inputs | Who can read the secrets | Writes a file |
|------|--------|--------------------------|---------------|
| Job environment (default) | `map-file` only | Every later step of the job | No |
| Command | `command` | Only that command and the processes it starts | No |
| `.env` file | `env-file` | Any step or tool that reads the file | Yes |

- **Job environment.** The action appends the secrets to the runner's `GITHUB_ENV` file, and
the runner sets them in the environment of **every step after it in the same job**, including
third-party actions, Docker container actions and steps running in a job `container:`. Steps
read them as normal variables (`process.env.DB_PASSWORD`, `$DB_PASSWORD`,
`${{ env.DB_PASSWORD }}`). Other jobs do not get them, and neither does a container you start
yourself with `docker run` unless you pass them (`docker run -e DB_PASSWORD ...`).
- **Command.** The action runs the command with `bash` (like a `run:` step) and the secrets in
its environment, and ends the step with the command's exit code. The secrets exist **only in
that process**: they are not exported, so later steps never see them.
- **`.env` file.** The previous behavior: the action writes or updates the file. Use it only
when a tool needs a file. `env-file` and `command` cannot be combined.

Job environment and command are fail-closed: if any secret is missing or empty, the step fails
and nothing is exported or passed to the command.

Because the action writes no `.env` file to the workspace, there is no workspace file for
artifacts, caches, Docker build contexts or commits to pick up by accident. The default mode
still writes the secrets to the runner's per-step `GITHUB_ENV` file in the runner's temporary
directory, outside the workspace, which the runner reads to set them in later steps and empties
at the end of the job. Treat the runner's
temporary files as sensitive, especially on self-hosted runners, where a failed cleanup can
leave them behind; `command` writes no file at all. Later tools can still write the variables
to files, and it does not protect against malicious code running in the same job, which can
read environment variables as easily as a file. For the smallest exposure, use `command`.

## Prerequisites

Expand Down Expand Up @@ -79,11 +112,11 @@ jobs:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}

- name: 🔐 Pull Secrets from AWS SSM
- name: 🔐 Load Secrets from AWS SSM
uses: macalbert/envilder/github-action@v0
with:
map-file: config/envilder.json
env-file: .env
# No env-file: the secrets are environment variables of the later steps

- name: 🍄 Setup Node.js
uses: actions/setup-node@v6
Expand Down Expand Up @@ -135,28 +168,28 @@ jobs:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1

- name: 🔐 Pull ${{ inputs.environment }} secrets
uses: macalbert/envilder/github-action@v0
with:
map-file: config/${{ inputs.environment }}/envilder.json
env-file: .env

- uses: actions/setup-node@v6
with:
node-version: "22.x"
cache: "pnpm"

- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm deploy

- name: 🚀 Deploy with ${{ inputs.environment }} secrets
uses: macalbert/envilder/github-action@v0
with:
map-file: config/${{ inputs.environment }}/envilder.json
command: pnpm deploy # the secrets exist only for this command
```

## Inputs

| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `map-file` | Path to the JSON file mapping environment variables to secret paths | ✅ Yes | - |
| `env-file` | Path to the `.env` file to generate | ✅ Yes | - |
| `env-file` | Path to a `.env` file to write instead of exporting to the job environment | ❌ No | - |
| `command` | Command to run with the secrets in its environment only (not exported). Cannot be combined with `env-file` | ❌ No | - |
| `provider` | Cloud provider to use: `aws` or `azure`. Can also be set via `$config.provider` in the map file. | ❌ No |`aws`|
| `vault-url` | Azure Key Vault URL (overrides `$config.vaultUrl` in map file) | ❌ No | - |

Expand All @@ -167,7 +200,7 @@ jobs:

| Output | Description |
|--------|-------------|
| `env-file-path` | Path to the generated `.env` file (same as input `env-file`) |
| `env-file-path` | Path to the generated `.env` file (same as input `env-file`; empty without it) |

## Parameter Mapping File

Expand Down
Loading
Loading