Skip to content

Template injection validator incorrectly flags env: blocks due to YAML serialization order #11378

@Mossaka

Description

@Mossaka

Bug Description

The template injection validator incorrectly flags expressions in env: blocks as unsafe, even though using environment variables is the documented safe pattern. This happens because:

  1. The validator regex expects env: to appear before run: in the YAML
  2. The YAML serializer outputs run: before env: for custom job steps
  3. The regex then captures env: blocks as part of run: blocks, flagging safe code as unsafe

Reproduction Steps

  1. Create a workflow with a custom job that uses step outputs in env blocks:
---
name: Test Workflow
on: workflow_dispatch
permissions:
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Build
        id: build
        uses: docker/build-push-action@v5
        with:
          context: ./
          
      - name: Sign image
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
        run: |
          echo "Signing image with digest: $DIGEST"
---
Test prompt
  1. Run gh aw compile

  2. Observe the error:

✗ error: template injection vulnerabilities detected in compiled workflow

  steps.*.outputs context (1 occurrence(s)):
    - ${{ steps.build.outputs.digest }}
      in: DIGEST: ${{ steps.build.outputs.digest }}

Expected Behavior

The workflow should compile successfully because the expression is used in an env: block, which is the documented safe pattern.

Actual Behavior

The compiler flags the expression as unsafe, even though it's in an env: block.

Root Cause Analysis

1. Validator Regex Issue

In pkg/workflow/template_injection_validation.go:69, the regex for multi-line run blocks:

runBlockRegex = regexp.MustCompile(`(?m)^\s+run:\s*\|\s*\n((?:[ \t]+.+\n?)+?)\s*(?:^[ \t]*-\s|\z)|^\s+run:\s*(.+)$`)

This regex captures content until it finds:

  • A line starting with - (next step)
  • End of string

But env: lines at the same indentation don't match either pattern, so they get included in the capture.

2. YAML Serialization Order Issue

The compiled YAML has different key ordering:

For AI agent steps (correct order - validation passes):

      - env:
          DRY_RUN: ${{ github.event.inputs.dry_run }}
        name: Check dry run mode
        run: |
          ...

For custom job steps (incorrect order - validation fails):

      - name: Sign image
        run: |
          echo "$DIGEST"
        env:
          DIGEST: ${{ steps.build.outputs.digest }}

3. Test Case Confirms the Issue

In pkg/workflow/template_injection_validation_test.go:382-394, the safe test case has env: BEFORE run::

        env:
          GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
        run: |
          bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"

And the test passes. But when run: comes before env: (as in the custom jobs serialization), the validation fails.

Suggested Fixes

Either:

  1. Fix the YAML serializer to output env: before run: for custom job steps (consistent with AI agent steps)

  2. Fix the regex to properly detect YAML step boundaries by stopping at any YAML key at the same indentation level (env:, if:, name:, with:, etc.), not just - markers

  3. Use proper YAML parsing instead of regex to extract run block content

Environment

  • gh-aw version: v0.37.3
  • OS: Linux

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions