Security AdvisoryGHSA-x3cj-mm38-329gSEVERITY: Critical

Self-Referential Composite Action Executes Long-Lived PAT on Scheduled Runs

TARGET REPOSITORYgouef / githubtoplanguages
PATCHED VERSIONSReview in progress / Pending patch
CWE CLASSIFICATION:CWE-829: Inclusion of Functionality from Untrusted Control Sphere
Sep 2026•8 min read•Madhan Alagarsamy
Official GitHub Advisory
Self-Referential Composite Action Executes Long-Lived PAT on Scheduled Runs

Executive Summary

In gouef/githubtoplanguages, the scheduled workflow top-languages.yml invoked its own composite action using a mutable branch reference (uses: gouef/githubtoplanguages@main) while passing secrets.USER_GITHUB_TOKEN (a long-lived classic PAT with write privileges) into the environment. Because GitHub Actions resolves mutable branch refs to the latest branch head on each run, any merged change to action.yml automatically executes with the PAT's account-level privileges on scheduled cron runs, requiring no workflow modification or review approval.

Coordinated Disclosure Timeline

2 weeks ago:

Identified self-referential mutable composite action pattern with injected classic PAT.

2 weeks ago:

Executed controlled replication PoC (runner-selfref-poc) proving weaponized action execution and Gist creation via injected PAT.

2 weeks ago:

Conducted live on-target probe (PR #6) confirming fork-PR approval boundaries and establishing the insider/merge attack vector.

2 weeks ago:

Reported privately to maintainer (@JanGalek) via GitHub Security Advisories; accepted and CVE requested.

2 weeks ago:

Official GitHub Security Advisory GHSA-x3cj-mm38-329g published on GitHub Advisory Database as Critical severity.

01.The Self-Referential Pattern (.github/workflows/top-languages.yml)

The repository's core automation workflow runs on a daily schedule (0 0 1 * *) and on pushes to main. Instead of referencing local steps or a pinned commit SHA, it calls its own action via a mutable remote branch reference:

.github/workflows/top-languages.yml:51-68
yaml
jobs:
  build-and-run:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Run custom action
        uses: gouef/githubtoplanguages@main # Self-reference to this repo's mutable main
        env:
          GITHUB_TOKEN: ${{ secrets.USER_GITHUB_TOKEN }} # Long-lived classic PAT

02.The Root Cause Chain & Token-Level Persistence

GitHub Actions downloads composite actions fresh on every invocation, resolving @main to the current head commit. Because action.yml uses runs: { using: composite } with arbitrary bash steps (git config, go build, ./app, git push), whoever controls the content of action.yml controls arbitrary code execution on the runner. Passing USER_GITHUB_TOKEN into every step grants account-level write access that outlives the ephemeral runner VM until revoked.

Reproduction & Proof of Concept (PoC)

Validation was conducted through both a controlled lab environment and live on-target assessment.

  1. Controlled PoC Setup: Created runner-selfref-poc replicating top-languages.yml with uses: madhanalagarsamy/runner-selfref-poc@main and injected test PAT.
  2. Baseline verification: Confirmed action executed from main at SHA c38501c with token authenticated as owner.
  3. Weaponization: Merged a malicious curl step into action.yml only (zero edits to .github/workflows/*).
  4. Re-run execution: The scheduled workflow automatically pulled updated SHA 53d2f02; the malicious step ran and successfully executed a privileged write API call (created private Gist ddbb9dac...).
  5. Live on-target validation (PR #6): Probed fork-PR behavior; confirmed GitHub's first-time contributor approval gate blocks external unapproved execution, proving the primary attack vector is insider merge or maintainer account compromise.
Weaponized action.yml payload step
yaml
- name: MALICIOUS STEP (merged via action.yml@main)
  shell: bash
  run: |
    echo "=== ATTACKER CODE EXECUTED ON NEXT TRIGGERED RUN ==="
    curl -s -X POST \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      -H "Accept: application/vnd.github+json" \
      https://api.github.com/gists \
      -d '{"description":"poc","public":false,"files":{"pwn.txt":{"content":"pwned"}}}'

Impact & Blast Radius

Critical Severity (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H). Attackers obtaining a merged commit to action.yml gain persistent, unattended execution bound to a broad write-capable classic Personal Access Token. This enables full source code manipulation, commit poisoning, backdoor insertion into release artifacts, exfiltration of all repository secrets, and supply-chain compromise for any third-party workflow invoking gouef/githubtoplanguages@main.

Remediation & Mitigation

1) Pin composite actions to immutable commit SHAs (e.g. uses: gouef/githubtoplanguages@<full-commit-sha>) or use relative local action syntax (uses: ./.github/actions/...). 2) Replace classic PATs with fine-grained least-privilege tokens or the default ephemeral GITHUB_TOKEN. 3) Enforce CODEOWNERS and branch protection rules requiring mandatory peer review on .github/** and action.yml. 4) Revoke and rotate existing USER_GITHUB_TOKEN.
Recommended remediation: SHA pinning & least-privilege token binding
Hardened workflow configuration
yaml
steps:
  - uses: actions/checkout@v4
  # Option A: Local action reference
  - name: Run hardened local action
    uses: ./ # uses local checkout rather than pulling mutable @main
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # ephemeral repository token

References & Documentation

#CI/CD Security#GitHub Actions#Supply Chain#PAT Theft#CWE-829#Persistence#Disclosure