Pass secret to script called by GitHub Actions workflow

Summary

This incident centers on a GitHub Actions reusable workflow that needed to pass a repository‑specific secret into a dynamically supplied setup.py script. The workflow design prevented direct access to secrets.* inside the with: block of a reusable workflow call, causing failures, blocked outputs, and missing environment variables.

Root Cause

The failure occurred because GitHub Actions does not allow secrets to be interpolated inside with: inputs of a reusable workflow. Additionally:

  • Job outputs containing secrets are automatically suppressed for security.
  • secrets: inherit only exposes secrets to the called workflow, not to arbitrary scripts unless explicitly exported.
  • Reusable workflows cannot receive secrets via with:, only via the secrets: block.

Why This Happens in Real Systems

Real CI/CD systems enforce strict boundaries around secret propagation:

  • Preventing accidental logging of secrets passed as workflow inputs.
  • Blocking secrets from being forwarded as job outputs, which could leak them.
  • Restricting secret expansion in YAML to avoid injection vulnerabilities.
  • Ensuring reusable workflows have explicit secret contracts, not implicit ones.

These guardrails often collide with flexible or dynamic workflow patterns.

Real-World Impact

This issue leads to several operational problems:

  • Scripts silently fail because expected environment variables are missing.
  • Reusable workflows become harder to generalize, forcing duplication.
  • Teams attempt insecure workarounds, such as echoing secrets into logs.
  • Debugging becomes painful because GitHub Actions hides secret‑related output.

Example or Code (if necessary and relevant)

A correct pattern for passing a secret to a reusable workflow:

jobs:
  build-and-deploy:
    uses: org/workflows/.github/workflows/build.yml@v1.0.1
    secrets:
      TOKEN: ${{ secrets.token }}
    with:
      setup: setup.py

Inside the reusable workflow:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: python setup.py "$TOKEN"
        env:
          TOKEN: ${{ secrets.TOKEN }}

How Senior Engineers Fix It

Experienced engineers rely on GitHub’s intended secret‑passing mechanism:

  • Declare secrets explicitly in the reusable workflow’s secrets: block.
  • Expose secrets as environment variables, not workflow inputs.
  • Avoid passing secrets through job outputs, since GitHub blocks them.
  • Keep secret handling inside the called workflow, not the caller.
  • Document the contract: which secrets the reusable workflow expects.

This keeps the workflow secure, predictable, and maintainable.

Why Juniors Miss It

Less experienced engineers often struggle because:

  • GitHub Actions has non‑obvious rules about where secrets can and cannot be used.
  • The error messages are cryptic, especially around suppressed outputs.
  • It’s natural to assume secrets behave like normal variables in YAML.
  • Reusable workflows introduce another layer of indirection, making debugging harder.
  • They try to “force” secrets through inputs instead of using the dedicated secrets mechanism.

The key takeaway: GitHub Actions secrets only flow through the secrets: block, never through with: or job outputs.

Leave a Comment