# How to Pass Environment Variables and Configure Secrets in gh-aw Workflows

> Learn to pass environment variables and configure secrets in GitHub Actions workflows with gh-aw. Securely manage secrets using the ${{ secrets.NAME }} syntax.

- Repository: [GitHub/gh-aw](https://github.com/github/gh-aw)
- Tags: how-to-guide
- Published: 2026-02-16

---

**You declare environment variables and secrets inside `env:` maps at the workflow, job, or step level in your markdown front-matter, and `gh-aw` compiles them into secure GitHub Actions YAML while enforcing that secrets are only referenced via `${{ secrets.NAME }}`.**

`gh-aw` is a GitHub CLI extension that transforms markdown-based workflow definitions into production-ready GitHub Actions YAML. During compilation, the tool builds `env:` blocks for every scope level and injects secret references using strict validation rules defined in the source code. Understanding how to properly pass environment variables and configure secrets ensures your workflows remain secure and maintainable.

## Understanding Environment Variable Scopes in gh-aw

`gh-aw` supports three distinct levels of environment variable scoping, each rendered into specific locations in the generated YAML.

### Workflow-Level Environment Variables

Variables defined at the root of your front-matter are available to every job and step in the workflow. The compiler writes these into the top-level `env:` block in [`pkg/workflow/yaml_generation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/yaml_generation.go).

```yaml
---
name: Production Deploy
env:
  NODE_ENV: production
  API_VERSION: v2
  DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
---

```

### Job-Level Environment Variables

Scoped to a specific job, these variables override workflow-level values for that job only. The compiler inserts these under each job definition in [`pkg/workflow/yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/yaml.go).

```yaml
---
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      BUILD_MODE: debug
      CACHE_KEY: ${{ secrets.CACHE_KEY }}
    steps:
      - uses: actions/checkout@v4

```

### Step-Level Environment Variables

Used for temporary overrides or passing sensitive data to specific commands without exposing them to the entire job. The compiler emits these directly before the `run:` stanza in [`pkg/workflow/prompt_step.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/prompt_step.go).

```yaml
---
steps:
  - name: Deploy to staging
    run: ./deploy.sh --env $DEPLOY_ENV
    env:
      DEPLOY_ENV: staging
      AUTH_TOKEN: ${{ secrets.STAGING_TOKEN }}

```

## How to Configure Secrets in gh-aw Workflows

Secrets require special handling to prevent accidental exposure in logs or source control.

### Declaring Secrets in Front-Matter

First, create the secret in your GitHub repository under **Settings → Secrets → Actions**. Then reference it exclusively within an `env:` block using the GitHub Actions expression syntax:

```yaml
---
env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
---

```

The compiler validates these references in [`secret_validation_test.go`](https://github.com/github/gh-aw/blob/main/secret_validation_test.go), ensuring that secrets are only used inside `env:` blocks and never in plain text or other contexts.

### Using Secrets with Safe-Inputs

Safe-inputs expose values to external tools (such as Playwright or HTTP clients) through environment variables without risking template injection. Declare the secret in the tool's `env` map:

```yaml
---
tools:
  playwright:
    version: "v1.41.0"
    env:
      PLAYWRIGHT_TOKEN: ${{ secrets.PLAYWRIGHT_TOKEN }}
---

```

The compiler extracts this map, adds it to the step's `env:` section in the generated YAML, and ensures the secret remains scoped to that specific tool execution according to the safe-inputs specification.

## Security Validation and Template Injection Protection

`gh-aw` implements strict security controls to prevent credential leakage and injection attacks.

The **template injection validator** in [`pkg/workflow/template_injection_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/template_injection_validation.go) ensures that only variables explicitly listed in an `env:` block are used inside step commands. This prevents attackers from injecting malicious code through environment variable interpolation.

Additionally, the compiler rejects any secret references found outside of `env:` blocks. If you attempt to use `${{ secrets.NAME }}` directly in a `run:` command without first assigning it to an environment variable, the compilation fails with a validation error defined in the security architecture specification ([`specs/security-architecture-spec.md`](https://github.com/github/gh-aw/blob/main/specs/security-architecture-spec.md)).

## Complete Example: Environment Variables and Secrets in Practice

Here is a production-ready workflow demonstrating all three scoping levels and proper secret handling:

```yaml
---
name: Deploy to Production
engine: copilot
env:
  DEPLOY_ENV: production
  API_BASE_URL: "https://api.example.com"
  DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      NODE_VERSION: "18"
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Deploy application
        run: |
          echo "Deploying to $DEPLOY_ENV"
          ./scripts/deploy.sh --token $DEPLOY_TOKEN
        env:
          DEBUG: "true"
---

```

**Compilation process:**

1. `gh-aw` reads the front-matter and builds a workflow-level `env:` block containing `DEPLOY_ENV`, `API_BASE_URL`, and the secret reference `DEPLOY_TOKEN` (handled in [`pkg/workflow/yaml_generation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/yaml_generation.go)).
2. The `deploy` job inherits these variables and adds `NODE_VERSION` to its local scope ([`pkg/workflow/yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/yaml.go)).
3. The final step adds `DEBUG` to its environment while accessing the inherited `DEPLOY_TOKEN` and `DEPLOY_ENV` variables ([`pkg/workflow/prompt_step.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/prompt_step.go)).
4. The secret remains encrypted in GitHub's secret store and is only expanded at runtime by GitHub Actions, never appearing in the compiled YAML or source markdown.

## Summary

- **Scope variables appropriately** using workflow-level, job-level, or step-level `env:` blocks depending on visibility requirements.
- **Reference secrets exclusively within `env:` blocks** using the `${{ secrets.NAME }}` syntax to pass security validation.
- **Leverage safe-inputs** to expose secrets to external tools without risking template injection attacks.
- **Trust the compiler** to enforce security rules through [`template_injection_validation.go`](https://github.com/github/gh-aw/blob/main/template_injection_validation.go) and [`secret_validation_test.go`](https://github.com/github/gh-aw/blob/main/secret_validation_test.go), rejecting unsafe patterns before they reach production.

## Frequently Asked Questions

### How do I override a workflow-level environment variable for a specific job?

Define an `env:` block under the specific job in your front-matter. Variables declared at the job level override workflow-level values for that job only. The compiler handles this scoping in [`pkg/workflow/yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/yaml.go) by inserting the job-specific `env:` map under the job definition in the generated YAML.

### Can I use secrets directly in run commands without assigning them to environment variables?

No. The `gh-aw` compiler explicitly forbids this pattern. If you attempt to use `${{ secrets.NAME }}` directly in a `run:` command, the validation logic in [`secret_validation_test.go`](https://github.com/github/gh-aw/blob/main/secret_validation_test.go) triggers a compilation error. You must first assign the secret to an environment variable in the step's `env:` block, then reference that variable in your commands.

### What happens if I declare a secret in the workflow-level env block?

The secret reference is propagated to all jobs and steps that inherit that environment block, but the actual secret value remains encrypted in GitHub's secret store. The compiler in [`pkg/workflow/yaml_generation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/yaml_generation.go) writes the `${{ secrets.NAME }}` expression into the top-level `env:` block of the generated YAML. GitHub Actions only expands this expression at runtime, ensuring the secret never appears in logs or source code.

### How does gh-aw protect against template injection attacks when using environment variables?

The tool implements a strict validation layer in [`pkg/workflow/template_injection_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/template_injection_validation.go) that ensures only variables explicitly listed in an `env:` block are used inside step commands. This prevents attackers from injecting malicious code through environment variable interpolation. Additionally, the safe-inputs system isolates secrets to specific tools, further reducing the attack surface.