Every third-party uses: line in a GitHub Actions workflow runs someone else's code in your job. Depending on the workflow, that code can reach your GITHUB_TOKEN, the secrets passed to that step, and your checked-out source.
In March 2025, the widely used tj-actions/changed-files Action was compromised: its tags were pointed at a malicious commit that printed CI secrets into workflow logs (CISA alert, CVE-2025-30066). That project was actively maintained — a maintenance check would not have prevented it — and a fixed release followed.
That last part is the point of this post. When something goes wrong in an Action that is still maintained, a fix comes out and you upgrade. When the Action is no longer maintained, there is no next release.
The question workflow scanners don't ask
There are good tools for securing your workflows:
| Tool | What it checks |
|---|---|
| OpenSSF Scorecard | Your repo's practices: dangerous workflow patterns, token permissions, pinned dependencies |
| zizmor | Your workflow files: injection risks, pull_request_target misuse, and more |
| actionlint | Your workflow syntax and expressions |
All three look at your workflow. Scorecard does have a Maintained check, and it gives an archived repository the lowest score — but only for the repository you run it on. Few teams run Scorecard against each of the 30-odd Actions their workflows reference.
Pinning Actions by commit SHA is good practice. It stops a tag from being silently repointed. But it doesn't tell you whether anyone will ship a fix for the code you pinned.
uzomuzo does that inventory for you: it reads your workflows, lists every Action they reference, and reports the maintenance status of each. It complements the tools above — it doesn't replace them.
What we found in Grafana (April 2026)
Grafana is one of the most popular open-source projects on GitHub. In April 2026 we scanned the Actions referenced in its workflows. One of them was archived: tibdex/github-app-token.
- The repository was archived in July 2025 (its last push is July 7, 2025). Its README says the Action is deprecated and points to GitHub's official
actions/create-github-app-token— the maintainer did exactly the right thing. - Grafana used it in 14 places across 13 workflow files, including
release-build.ymlandrelease-npm.yml. - It declares
runs.using: node20. GitHub removed Node 20 from Actions runners on September 23, 2026, and an archived Action can't be updated to Node 24. Anyone still using it after that date has a broken workflow.
An Action runs in your job, with access to its workspace, not in an isolated sandbox. Depending on the workflow, it can reach GITHUB_TOKEN (with whatever permissions the job grants), the secrets passed to it, and the checked-out source. Release jobs often hold sensitive credentials. That doesn't mean anything was exposed here — it means an archived Action in a release job deserves a look.
What happened next
We filed an issue on April 5, 2026.
Grafana had already begun moving to actions/create-github-app-token in one workflow in October 2025. A community member opened a PR for the remaining files three days after our issue. A maintainer closed it, explaining that the team had a new recommended approach using their own internal workflow. We don't know whether our issue influenced their timing.
On September 15, 2026, the issue was closed as completed: "This has been done, we no longer use that action :)" — a week before Node 20 was removed.
When we scanned Grafana's workflows again on October 26, 2026, tibdex/github-app-token no longer appeared, and none of the 32 Actions was archived or got a replace verdict.
Reporting doesn't always lead to a fix. Of the four upstream reports we filed in April, two have been resolved and two have had no response yet. Whether and how to fix something is the maintainers' call. What a scanner can do is make sure the question gets asked.
Scan your own Actions
Install
go install github.com/future-architect/uzomuzo-oss/cmd/uzomuzo@latest
Or download a binary from GitHub Releases. You need v0.8.0 or later for --fail-on archived and the · archived marker. The examples below assume GITHUB_TOKEN is set: scanning a repository's Actions reads workflow files through the GitHub API, and the token also gives uzomuzo the repository signals (archived, commit history) it needs.
Check a repository
export GITHUB_TOKEN=... # a read-only token is enough
# Actions referenced directly in your workflows
uzomuzo scan https://github.com/your-org/your-repo --include-actions
# Also include Actions used inside composite Actions
uzomuzo scan https://github.com/your-org/your-repo --include-actions --show-transitive
A composite Action can itself uses: other Actions. Those run in your job too. The difference is who can fix them: you can swap a direct Action today, but a transitive one needs the parent Action's maintainer to act.
You can also scan a single Action or workflow file:
uzomuzo scan https://github.com/tibdex/github-app-token
uzomuzo scan --file .github/workflows/ci.yml
What an archived Action looks like
$ uzomuzo scan https://github.com/tibdex/github-app-token https://github.com/actions/github-script
STATUS PURL LIFECYCLE BUILD
🔴 replace https://github.com/tibdex/github-app-token Stalled · archived —
⚠️ caution https://github.com/actions/github-script Stalled —
Both Actions are Stalled: neither has had much recent activity. Only one is archived, and that's the one marked · archived and given a replace verdict.
The lifecycle label stays Stalled rather than EOL-Confirmed on purpose. uzomuzo uses EOL-Confirmed only for an explicit end-of-life signal, such as an npm deprecation or a Packagist "abandoned" flag. Archiving alone isn't one: some archived repositories are packages that moved to a monorepo and keep shipping releases from there. So the archive is shown as a separate fact next to the label instead. (tibdex's README does say "This action is deprecated", but uzomuzo deliberately doesn't treat README prose as a signal — free text is easy to misread.)
If you read the earlier post in this series: its table showed archived repositories as EOL-Confirmed. Current versions show them as above.
Gate your CI
uzomuzo scan https://github.com/your-org/your-repo --include-actions --fail-on archived
--fail-on archived fails the build when any referenced Action's repository is archived — and, like every --fail-on value, when a version is known to be malicious. It doesn't fire on Actions that are merely quiet. That matters: --fail-on stalled would also catch archived Actions, but in our October scan of Grafana it would have failed on four Actions that are simply less active, including actions/github-script. --fail-on eol-confirmed alone wouldn't fail on an archived Action at all.
Start with a report-only run, look at what comes back, then turn the gate on.
Run it on a schedule
The uzomuzo repository has a monthly workflow that generates an SBOM with aquasecurity/trivy-action, runs uzomuzo scan --sbom, and opens an issue with the results — SCA and lifecycle checks side by side. That workflow covers package dependencies, not Actions. To cover Actions as well, add a step that runs uzomuzo scan https://github.com/$GITHUB_REPOSITORY --include-actions --fail-on archived with GITHUB_TOKEN set.
Two layers, two questions
Scorecard, zizmor, and actionlint answer: "Is my workflow configured safely?"
uzomuzo answers: "Is the code my workflow runs still maintained?"
A safely configured workflow can still depend on an Action that will never get another release. That isn't a known vulnerability. It's something to review before it becomes a broken build — or worse.
Earlier post: "Your dependencies are 48% unmaintained — and SCA tools can't see it"
Source and docs: github.com/future-architect/uzomuzo-oss (Apache 2.0)
Kota Kanbe — Creator of vuls, an open-source vulnerability scanner.

Top comments (1)
The tj-actions detail cuts the other way too: it was actively maintained and still had its tags repointed. Maintenance did not stop that one — it just got a fix shipped same-day. So the two checks answer different questions: the SHA pin answers "can this change under me", the maintenance check answers "will anyone ship the next fix", and neither covers the other. The gate we landed on: every Action in a workflow has a name next to it of the person who notices when it dies. If the name is nobody, it is our code and gets treated like it.