Check a GitHub Action for Secret-Stealing Steps
A third-party action runs on your runner with your repository's secrets, and a workflow someone slipped into a repository runs with them too. Paste the action's or the repository's GitHub URL and SkillTotal reads its actions and workflows for steps that collect secrets and send them to a host outside GitHub. Each finding cites the file and line.
Last updated
Check an action before you use it, or a repository after an incident
For an action, take the repository from its uses: line (uses: <owner>/<action>@<ref> lives at github.com/<owner>/<action>); for your own project, take its repository URL. Paste it into the scanner on the home page. The repository is shallow-cloned and read, files under .github/workflows/ included. Nothing is executed and no LLM is involved, so the same commit gives the same result every time. If a malicious step was removed after it ran, paste the URL of the commit that added it (github.com/<owner>/<repo>/commit/<sha>) to scan the repository as it was then. The GitHub repo scanner page lists the other link forms it accepts.
What it checks in workflows
Every secret dumped and uploaded
ST-WORKFLOW-SECRETS-DUMP-EXFIL. In each .yml or .yaml file under .github/workflows/, the scanner looks for toJSON(secrets), which puts every secret the workflow can read into one value. It reports the workflow when a run: step in the same file also calls a network client (curl, wget, Invoke-WebRequest, nc and similar) at a host that is not GitHub. The finding points at both lines, the dump and the upload. A named secret passed to a deploy command is not reported, and neither is a dump with no upload.
Credential sweep sent off GitHub
ST-WORKFLOW-HISTORY-EXFIL. A workflow that searches for the key prefixes of two or more providers (sk-ant-, sk-proj-, AKIA, ghp_, AIza, xoxb- and others) and has a run: step sending data to a non-GitHub host, such as a bare IP address. This is how the GhostAction workflows read the full git history for keys and posted what they found. A secret-scanning job that keeps its results on GitHub is not reported.
A published action that sends the token away
ST-ACTION-SECRET-EXFIL. When the repository is itself a composite action (an action.yml or action.yaml), each run: step is decoded from YAML into the command the runner's shell receives. A step that sends GITHUB_TOKEN, an Actions OIDC or runtime token, ${{ github.token }} or a ${{ secrets.* }} value to a non-GitHub host through a network client is reported. That step runs on the runner of every repository that uses the action. Passing the token to gh or git against GitHub, or to the documented Actions service URLs, is not matched. The same decoded steps are also checked for a download piped into a shell (ST-SHELL-PIPE-EXEC).
How these findings count
The three exfiltration rules are malicious indicators. When one fires, the report's verdict is malicious, the risk score is at least 50, and the GitHub Action fails the job at any severity. The engine reads the rest of the CI configuration, including actions kept under .github/actions/, as the project's own build setup, so other rule matches there are listed as notes to review and do not change the score.
After an incident like wfx: what to check
In the November 2026 wfx campaign, hijacked GitHub accounts added steps to existing workflows. The steps passed toJSON(secrets) to a payload that uploaded them, and the run was deleted afterwards. If your organization may be affected, check these:
- Search pull requests and the organization audit log for the probe: a pull request titled
docs: dependency audit notesor a branch namedwfx-docs. Include closed pull requests and deleted branches. - Look for gaps in each workflow's run numbers. Run numbers keep counting after a run is deleted, and on plans with an organization audit log the deletion is recorded there as well.
- Read
git log -p -- .github/workflows/fortoJSON(secrets)and for edits by accounts that do not usually change CI, then scan the commits you find. - In Azure, list the federated credentials on app registrations and managed identities and look for names that start with
wfx-ci-. A federated credential lets a workflow sign in without a secret, so rotating secrets does not remove it. - In AWS, search CloudTrail for role sessions whose session name starts with
wfx-. - If any of these turns up, treat every secret the repository and its organization expose to Actions as exposed and rotate them. Delete the federated credentials you did not create.
# Run numbers of one workflow, newest first. A missing number is a deleted run.
gh run list --workflow ci.yml --limit 500 --json number --jq '.[].number'# Azure: federated credentials on an app registration
az ad app federated-credential list --id <app-id> --query "[].{name:name, subject:subject}"
# AWS: role sessions started from a web identity token (GitHub OIDC)
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentityRun it in CI
The SkillTotal GitHub Action runs the same engine on the runner. Scanning . includes .github/workflows/, so a pull request that adds a toJSON(secrets) upload fails the check. Add one step to a workflow:
- uses: pezhik/skilltotal@v1.1.0
with: { source: ., fail-on: high }The full workflow with permissions, SARIF upload and the pull request comment is on the GitHub Action page. A gate in the repository checks pull requests. An account that can push to the default branch can also edit the gate, so put .github/workflows/ under CODEOWNERS and require reviews on the default branch.
FAQ
- What does toJSON(secrets) do in a workflow?
- It turns the whole secrets context, every secret the workflow can read, into one JSON string. GitHub masks secret values in the run log, but a step that sends that string to a server delivers every value in full.
- Does the scan run my workflows?
- No. The repository is cloned and its files are read. No workflow, action or script is executed, and nothing is sent to an LLM.
- Can it find a step that was deleted together with its run?
- A scan reads the files at one commit. If the step is gone from the current branch, scan the commit that added it by pasting the commit URL. Deleted runs show up as gaps in run numbers, and in the organization audit log where the plan has one.
- Does it check unpinned actions or pull_request_target triggers?
- No. SkillTotal looks for workflows and actions that move secrets off GitHub. For hardening checks such as unpinned third-party actions, pull_request_target triggers and expression injection in run steps, run a workflow linter next to it.
- Can a scan prove a workflow is safe?
- No scanner can. SkillTotal reports what it finds, with the file and line of each match, so you decide from the evidence.