Part 3 of The Rise of Supply Chain Attacks: Why 2025–2026 Changed Everything
The first post in this series made the case that the supply chain is now the front door. The second argued that CVE-based defenses can’t catch attacks that don’t exploit vulnerabilities — they abuse trust.
This post is about what happens when the thing you trusted to be immutable isn’t.

On May 18, 2026, StepSecurity disclosed that actions-cool/issues-helper — a popular GitHub Action for managing GitHub Issues — had been compromised in a way that invalidated one of the most widely repeated pieces of supply chain security advice: pin your dependencies to a version tag.
The attacker did not need a zero-day. They did not need to compromise npm or publish a malicious package. They did not need to break any cryptographic primitive. They simply moved 53 tags to point at commits they controlled — and they did it in three minutes and sixteen seconds.
What a Tag Actually Is
Before the technical details, a framing point that most GitHub Actions documentation quietly glosses over.
When you write uses: actions-cool/issues-helper@v3 in a workflow, you are not pinning to an immutable artifact. You are resolving a mutable pointer — a Git tag — at the moment the workflow runs. If the tag has been moved since the last time you audited it, you get whatever commit the tag now points to. No warning. No diff. No error. The workflow runs exactly as before, except the code executing on your runner is different.
Git tags are not package hashes. They are not content-addressed. They carry no cryptographic guarantee. A tag named v3.8.0 is a label that can be detached from one commit and reattached to another by anyone with push access to the repository.
This is not a bug in Git. It is a documented feature. It is also the exact mechanism the attacker used.
What Happened to actions-cool/issues-helper
An attacker gained the ability to move tags in the actions-cool/issues-helper repository. The method of initial access has not been publicly confirmed — likely a compromised maintainer credential or a stolen token, consistent with the broader Mini Shai-Hulud / TeamPCP campaign that has been running since early 2026.
What followed was automated and fast:
- Every existing tag in the repository — all 53 — was re-pointed to imposter commits: commits that do not appear in the action’s normal commit history.
- All 53 imposter commits in
issues-helperwere created within a 3-minute, 16-second window. This is not a human operator working methodically. This is a script executing a prepared playbook. - A second action in the same organization,
actions-cool/maintain-one-comment, was also compromised by the same actor using the identical pattern — every tag moved to an imposter commit, with the same exfiltration domaint.m-kosche.com. - The 15 tags in
maintain-one-commentwere all moved within a 39-second window.
The imposter commits were styled to blend in. Each was generated with a fake "Build action for vX.Y.Z" commit message that mirrors the legitimate maintainer’s commit-message style. The only tell — visible only if you were already looking — was that every commit is dangling: none are reachable from the action’s default branch. GitHub itself displays a warning on such commits, but nothing in a standard workflow run surfaces that warning to the operator.
The Payload: Reading Secrets Directly from Memory
The malicious code the imposter commits introduced is worth examining closely, because it uses a technique that bypasses the controls most teams think are protecting them.
The malicious code, when executed inside a GitHub Actions runner, downloads the bun JavaScript runtime to the runner, reads memory from the Runner.Worker process — the process that holds the workflow’s decrypted secrets — to harvest credentials, and makes an outbound HTTPS call to an attacker-controlled domain to exfiltrate the stolen data.
The Runner.Worker process is the GitHub Actions component responsible for injecting secrets into the workflow environment. At the moment secrets are decrypted and made available to steps, they live in that process’s memory. The payload reads /proc/<Runner.Worker PID>/mem directly — the Linux kernel interface for accessing a process’s address space — and pipes the output through tr and grep filtering for "isSecret":true.
That filter string is significant. The GitHub Actions runner serializes secrets in a JSON structure with an isSecret field to distinguish secret values from regular environment variables. The payload spawns python3 and reads /proc/2119/mem — the address space of the Runner.Worker process — to scrape decrypted secrets directly out of runner memory. The attacker knows the internal data structure of the GitHub Actions runner. This is not a generic credential scraper. It is purpose-built for this target.
The implications for defense are uncomfortable. Restricting environment variable access, using GITHUB_TOKEN minimally, rotating secrets regularly — all of these are correct practices. None of them prevent an adversary from reading the Runner.Worker process memory from within the same runner, which is what this payload does. The secrets exist in memory because the runner decrypted them to make them available to the workflow. A step running in that workflow can access the same memory space.
This is not a new technique — it was used in the tj-actions/changed-files compromise in 2023 and has reappeared in multiple campaigns since. It keeps appearing because it works, and because the architectural constraint that makes it work — secrets must be decrypted somewhere, and that somewhere is the runner — cannot be patched away.
The Same Campaign, A New Surface
It is worth being explicit about the context: the exfiltration domain t.m-kosche.com and the bun + Runner.Worker memory-read payload connect this incident to the Mini Shai-Hulud / TeamPCP campaign that has been active since early 2026. The same group that compromised TanStack’s npm packages, Mistral AI, OpenSearch, Guardrails AI, and Microsoft’s durabletask PyPI package has now moved horizontally into GitHub Actions themselves.
This is the pattern I described in the first post in this series: industrialized, automated, and expanding. The playbook is being applied systematically across every layer of the modern software pipeline — package registries, CI/CD systems, and now the action runners that orchestrate those systems. npm packages feed into GitHub Actions workflows. Compromising the action that runs in those workflows closes the loop: the attacker reaches everything the workflow can reach, regardless of whether the packages the workflow installs are clean.
The choice of actions-cool/issues-helper is also instructive. This is not a deployment action or a secrets management action. It is an issue management utility — the kind of action that exists in thousands of repositories’ workflows precisely because it seems low-risk. Nobody audits their issue-labeling automation the same way they audit their deployment pipeline. The attacker knows this.
The “Pin to SHA” Advice Is Right but Incomplete
The standard hardening recommendation for GitHub Actions — pin third-party actions to a full commit SHA rather than a mutable tag — would have protected workflows from this specific attack. A workflow using actions-cool/issues-helper@<known-good-SHA> would have continued to reference the known-good commit even after all the tags were moved.
This advice is correct. It is also insufficient on its own for three reasons.
First, humans don’t maintain SHA pins. In practice, SHA-pinned workflows go stale. The SHA pinned six months ago may reference a version with known issues or missing features. Developers unpin to get updates, or skip pinning entirely because Dependabot and Renovate handle version bumps by default with tag references, not SHA references. The operational reality is that most workflows in production are tag-referenced, not SHA-referenced.
Second, SHA pinning without verification is still vulnerable to imposter commits. The attack generated unique imposter commits per tag. If an attacker can move tags, they can also create commits that look legitimate and attempt to socially engineer an operator into updating their pinned SHA. SHA pinning prevents automatic compromise; it does not prevent a targeted campaign that generates convincing commit history artifacts.
Third, SHA pinning gives you no visibility into what changed. If you discover your pinned SHA is no longer reachable from the action’s default branch — the precise indicator of an imposter commit — you need tooling to detect that. Most teams do not have it. The detection signal exists; the instrumentation to surface it is not standard practice.
The correct framing is: SHA pinning is a necessary condition for defending against tag-moving attacks, but it becomes a meaningful defense only when paired with continuous verification that your pinned SHAs are still legitimate — still reachable from the action’s main branch history.
What the Detection Gap Looks Like in Practice
StepSecurity’s Harden-Runner detected this attack at runtime: the bun download, the Runner.Worker memory read, and the outbound connection to t.m-kosche.com were all captured and flagged. Harden-Runner flagged the actions-cool/issues-helper@v3 step with an “Imposter Commit” badge and a “Suspicious Processes” warning the moment the workflow ran.
That is a runtime detection — caught while executing. The question worth asking is what the detection gap looks like for teams without runtime monitoring on their runners.
For those teams, the workflow runs. The bun binary appears in /home/runner/.bun/bin/bun — it looks like a build dependency. The python3 process reads /proc/mem — it looks like a script. The outbound call to t.m-kosche.com happens on port 443 — it looks like any other HTTPS request. Nothing in the default GitHub Actions logs surfaces any of this as anomalous, because GitHub Actions does not provide process-level telemetry or egress monitoring by default.
The attacker’s credentials are exfiltrated before the workflow finishes. The workflow completes successfully. The run log shows green. No alert fires.
This is the detection gap that makes GitHub Actions a high-value target: the default observability posture is almost entirely blind to in-runner behavior.
The Practical Response
The controls that follow from this incident are specific and implementable. Prioritize them in this order:
Audit your uses: references today. Run git grep -r "uses:" .github/ across your repositories and identify every action pinned to a mutable tag. Sort by the ones with the broadest workflow access — actions that run in workflows with id-token: write, contents: write, or access to environment secrets deserve the most urgency.
Convert high-privilege workflows to SHA pins. For any workflow that can publish packages, deploy infrastructure, or access production secrets, the actions in that workflow should be pinned to a full commit SHA. Dependabot supports this with versioning-strategy: lockfile-only for Actions. Tools like pin-github-action can automate the conversion.
Verify pinned SHAs are reachable from the main branch. A SHA that does not appear in git log --ancestry-path <sha>..HEAD on the action’s repository is an imposter commit by definition. This check can be scripted and run in CI or as a pre-merge gate. It is the minimal viable version of what StepSecurity’s imposter commit detection does.
Apply egress restrictions to your runners. The exfiltration in this attack required an outbound HTTPS connection to t.m-kosche.com. If your runners operate behind an egress firewall or use Harden-Runner’s allowed-domains policy, that connection fails even if the malicious code runs. This is defense-in-depth: the imposter commit executes, the memory read happens, but the credentials have nowhere to go. Combined with alerting on the blocked connection, you get both prevention and detection.
Treat the GITHUB_TOKEN permissions block as a security boundary, not a convenience setting. The default GITHUB_TOKEN in many repositories has contents: write and packages: write permissions inherited from the repository default. An action running in such a workflow can publish packages, commit to branches, and create releases under the repository’s identity. Set permissions: {} at the workflow level and grant only what each individual job explicitly needs.
Add Runner.Worker memory access to your anomaly baseline. If you have any process-level monitoring on your runners — auditd, eBPF-based tools, or a security agent — add /proc/*/mem reads originating from non-runner processes to your detection ruleset. This specific IOC has appeared in multiple campaigns and will continue to appear because the underlying technique works.
The Broader Pattern
Three posts into this series and the pattern is consistent enough to name explicitly.
The attacks are not finding exotic vulnerabilities. They are finding trust relationships that defenders assume are stronger than they are: the assumption that a signed package version is immutable, the assumption that a CVE scan covers all relevant risk, the assumption that a version tag means a pinned artifact.
The attacker in the actions-cool incident did not need elevated GitHub permissions or a novel exploit. They needed the ability to move tags in a repository. That is a standard repository write permission. Once they had it, 53 tags were poisoned in the time it takes to make a cup of coffee.
The mental model that needs to change is not about any single control. It is about treating the entire CI/CD pipeline — the package it installs, the action it runs, the token it carries, the runner it executes on — as a threat surface that requires the same continuous scrutiny as production infrastructure. Not a periodic audit. Not a scanner that runs on merge. Continuous, runtime-aware, behaviorally-informed scrutiny.
The front door is open. The people walking through it know exactly which rooms are unlocked.
IOCs for this incident — the exfiltration domain, imposter commit SHAs for all 68 affected tags across both actions, and runner-level payload markers — are documented in full in StepSecurity’s original disclosure. The Mini Shai-Hulud campaign tracker is maintained by Socket at socket.dev/supply-chain-attacks/mini-shai-hulud.