Using GitHub secrets may break job outputs

·

2 min read

If you don't want to spend hours debugging GitHub CI jobs, avoid using job outputs that may contain secrets inside. Job outputs will not be passed to dependent jobs if they contain secrets in any part of output value.

There is a tiny note about this behavior in docs.

Job outputs containing expressions are evaluated on the runner at the end of each job. Outputs containing secrets are redacted on the runner and not sent to GitHub Actions.

You can test this by setting GitHub secret MY_SECRET and using it in workflow like this:

name: 06 - Debug
on:
  pull_request:
    types: [opened, synchronize, reopened]
jobs:
  demo1:
    runs-on: ubuntu-latest
    outputs:
      my_output: ${{ steps.set_output.outputs.my_output }}
    steps:
      - id: set_output
        run: echo 'my_output=${{ secrets.MY_SECRET }}' >> "$GITHUB_OUTPUT"
  demo2:
    runs-on: ubuntu-latest
    needs: [demo1]
    steps:
      - run: echo 'MY_SECRET=${{ needs.demo1.outputs.my_secret }}'
      - run: echo 'MY_SECRET=${{ secrets.MY_SECRET }}'

You will see a warning annotation at workflow run summary page:

demo1

Skip output 'my_output' since it may contain secret.

In the workflow run log you will see that trying to use demo1 job output will result in empty string.

I learned this the hard way. I was using secrets like DB_NAME=my-app and then in output of build job I tried to return output like image_tag: ghcr.io/${{ github.repository }}:my-app-${{ github.run_id }}. It did not show up as output from this job because it contained my-app inside. I should have used GitHub variables for storing non-sensitive values like database name and similar.