The blog post at https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised revealed that a popular GitHub action was compromised, leading to the leakage of environment variables in the pipeline logs. One line stood out to me:

This step is especially important for public repositories since their logs are publicly accessible.

I decided to investigate further to determine whether environment variables are still exposed in public repositories. The process was as follows:

  1. Gather a list of repositories that use tj-actions/changed-files
  2. For each repository, find the workflow(s) that use tj-actions/changed-files
  3. For each workflow, gather the runs that occurred during the compromised time frame
  4. For each run, retrieve the logs and check whether environment variables were exposed

This investigation resulted in the discovery of several exposed environment variables, some of which included credentials for AWS, Google Cloud, NPM, and others.

Method

1. Scrape Repositories

The GitHub repository provides an overview of repositories that depend on the tj-actions/changed-files package at https://github.com/tj-actions/changed-files/network/dependents.

However, GitHub’s API does not have an endpoint to retrieve these repositories, so scraping the list was necessary. Using the Python package github-dependents-info got me a list of 14,699 repositories.

2. Find Workflow Names

Next, it was necessary to identify which workflows in the repositories use the tj-actions/changed-files package. Some repositories had already patched their CI pipelines by removing the dependency completely, so the latest commit before March 15th was selected. With this commit hash, the files inside the .github/workflows folder were checked to extract the exact workflow and job names that used the compromised package.

Example:

3. Filter Workflow Runs and Jobs

The tj-actions/changed-files package was compromised for a short period, after which it was patched. Specifically, the compromise occurred between March 14th, 18:00 GMT+1, and March 16th, 05:00 GMT+1, so logs during that specific time frame are interesting. Therefore, the workflow runs were filtered based on these timestamps, and also checked whether the job was actually ran.

Example:

4. Search the Job Logs

With all the necessary data in hand, the next step was to download the logs. Fortunately, the GitHub API provides an endpoint to download all logs of a certain run. The logs were automatically downloaded, the logfile of the related job was found, and it was checked if the tj-actions/changed-files had a base64 encoded string in the output. An example can be seen here:

Example (requires authentication):

Results

A total of 2.802 unique environment variables were found in 281 different repositories. Most of them (2.677) were temporary GitHub tokens that are automatically generated and revoked after the pipeline run completes [ref].

Filtering those out leaves 125 environment variables from 48 unique repositories. The interesting ones were verified, revealing several valid credentials: GitHub personal tokens, GitHub app tokens, GitLab tokens, Cloudflare tokens, NPM tokens, AWS credentials, Google Cloud credentials, Azure credentials, Docker credentials, and more.

Responsible Disclosure

I responsibly disclosed the findings to the affected repositories. Most repository owners responded quickly and took immediate action to rotate their credentials, for some I’m still waiting for response.